497 lines
16 KiB
C++
497 lines
16 KiB
C++
#include "component.h"
|
|
|
|
#include "collection.h"
|
|
|
|
constexpr std::string_view masonJSON("mason.json");
|
|
|
|
constexpr std::string_view dependencies("dependencies");
|
|
|
|
constexpr std::array<std::string_view, 5> sourceVariants({
|
|
"source",
|
|
"src",
|
|
"from",
|
|
"in",
|
|
"file"
|
|
});
|
|
|
|
constexpr std::array<std::string_view, 5> targetVariants({
|
|
"target",
|
|
"dst",
|
|
"to",
|
|
"out",
|
|
"destination"
|
|
});
|
|
|
|
constexpr std::array<std::string_view, Component::done + 1> stringStates {
|
|
"initial",
|
|
"reading",
|
|
"ready",
|
|
"building",
|
|
"error",
|
|
"done"
|
|
};
|
|
|
|
constexpr std::array<std::string_view, Component::unknown + 1> stringTypes {
|
|
"file",
|
|
"directory",
|
|
"mason",
|
|
"unknown"
|
|
};
|
|
|
|
Component::Component(
|
|
const std::filesystem::path& path,
|
|
Collection* collection,
|
|
const std::shared_ptr<Logger>& logger,
|
|
const std::optional<std::string>& name
|
|
):
|
|
Loggable(logger),
|
|
state(initial),
|
|
type(unknown),
|
|
collection(collection),
|
|
location(path),
|
|
manifest(std::nullopt),
|
|
name(name)
|
|
{}
|
|
|
|
void Component::read() {
|
|
if (state != initial)
|
|
throw WrongState(state, "read");
|
|
|
|
state = reading;
|
|
|
|
std::filesystem::file_status status = std::filesystem::status(location);
|
|
switch (status.type()) {
|
|
case std::filesystem::file_type::directory:
|
|
type = directory;
|
|
state = ready;
|
|
if (!tryReadingBuildScenarios() && !name.has_value())
|
|
name = location.filename();
|
|
|
|
break;
|
|
case std::filesystem::file_type::regular:
|
|
type = file;
|
|
state = ready;
|
|
if (!name.has_value())
|
|
name = location.filename();
|
|
|
|
break;
|
|
default:
|
|
warn(location.string() + " doesn't appear to be a file nor a directory");
|
|
state = error;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Component::tryReadingBuildScenarios() {
|
|
if (readAsMason())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Component::readAsMason() {
|
|
std::filesystem::path masonManifest = location/masonJSON;
|
|
try {
|
|
std::ifstream file(masonManifest);
|
|
if (file.is_open())
|
|
manifest = nlohmann::json::parse(file);
|
|
else
|
|
minor("Couldn't open " + masonManifest.string());
|
|
} catch (const nlohmann::json::exception& e) {
|
|
major("Couldn't parse " + masonManifest.string());
|
|
}
|
|
|
|
if (manifest.has_value()) {
|
|
const nlohmann::json& mnfst = manifest.value();
|
|
if (!mnfst.is_object())
|
|
return errorScenario(masonManifest.string() + " is unexpected root type");
|
|
|
|
nlohmann::json::const_iterator itr = mnfst.find(dependencies);
|
|
if (itr == mnfst.end()) {
|
|
info(masonManifest.string() + " doesn't have dependencies");
|
|
} else {
|
|
const nlohmann::json& deps = *itr;
|
|
if (!readMasonDependencies(deps, masonManifest))
|
|
return false;
|
|
}
|
|
|
|
if (!name.has_value()) {
|
|
std::optional<std::string> manifestName = tryStringValue(mnfst, "name");
|
|
if (manifestName.has_value())
|
|
name = manifestName.value();
|
|
|
|
if (!name.has_value()) {
|
|
major("Couldn't define name of the project at " + location.string() + ", using directory name as project name");
|
|
name = location.filename();
|
|
}
|
|
}
|
|
|
|
type = mason;
|
|
info(location.string() + " seems to be a valid mason project");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Component::readMasonDependencies(const nlohmann::json& deps, const std::string& manifestPath) {
|
|
if (!deps.is_array())
|
|
return errorScenario(manifestPath + " dependencies are not organized as an array");
|
|
|
|
for (const nlohmann::json& dep : deps) {
|
|
switch (dep.type()) {
|
|
case nlohmann::json::value_t::string:
|
|
collection->addSource({dep});
|
|
break;
|
|
case nlohmann::json::value_t::object: {
|
|
std::optional<std::string> path = tryStringValue(dep, "path");
|
|
if (!path.has_value())
|
|
return errorScenario(manifestPath + " object of unexpected format in dependecies");
|
|
|
|
collection->addSource({path.value(), tryStringValue(dep, "target"), tryStringValue(dep, "name")});
|
|
}
|
|
break;
|
|
default:
|
|
return errorScenario(manifestPath + " has unexpected entries in dependecies");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Component::errorScenario(const std::string& message) {
|
|
major(message);
|
|
manifest = std::nullopt;
|
|
return false;
|
|
return false;
|
|
}
|
|
|
|
void Component::build(const std::filesystem::path& destination, const std::string& target, bool useName) {
|
|
if (state != ready)
|
|
throw WrongState(state, "build");
|
|
|
|
state = building;
|
|
info("Building " + location.string() + " to " + destination.string());
|
|
|
|
switch (type) {
|
|
case file:
|
|
if (useName)
|
|
buildAsFile(destination/name.value());
|
|
else
|
|
buildAsFile(destination);
|
|
|
|
break;
|
|
case directory:
|
|
if (useName)
|
|
buildAsDirectory(destination/name.value());
|
|
else
|
|
buildAsDirectory(destination);
|
|
break;
|
|
case mason:
|
|
if (useName)
|
|
buildAsMason(destination/name.value(), target);
|
|
else
|
|
buildAsMason(destination, target);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Component::buildAsFile(const std::filesystem::path& destination) {
|
|
copyFile(location, destination);
|
|
|
|
state = done;
|
|
}
|
|
|
|
void Component::buildAsDirectory(const std::filesystem::path& destination) {
|
|
std::filesystem::copy(location, destination,
|
|
std::filesystem::copy_options::recursive |
|
|
std::filesystem::copy_options::overwrite_existing
|
|
);
|
|
|
|
state = done;
|
|
}
|
|
|
|
std::string Component::getLibrariesPath() const {
|
|
//TODO may be it makes sence to cache
|
|
if (type != mason)
|
|
throw WrongType(type, "getLibrariesPath");
|
|
|
|
const nlohmann::json& mnfst = manifest.value();
|
|
nlohmann::json::const_iterator itr = mnfst.find("libraries");
|
|
if (itr == mnfst.end())
|
|
return "";
|
|
|
|
const nlohmann::json& libs = *itr;
|
|
if (libs.is_string())
|
|
return libs;
|
|
|
|
warn("Mason manifest " + (location/masonJSON).string()
|
|
+ " has field \"libraries\" which is not string, ignoring it");
|
|
return "";
|
|
}
|
|
|
|
std::filesystem::path Component::getLocation() const {
|
|
return location;
|
|
}
|
|
|
|
bool Component::successfullyRead() const {
|
|
return state > reading && state != error;
|
|
}
|
|
|
|
bool Component::successfullyBuilt() const {
|
|
return state == done;
|
|
}
|
|
|
|
void Component::buildAsMason(const std::filesystem::path& destination, std::string target) {
|
|
const nlohmann::json& mnfst = manifest.value();
|
|
nlohmann::json::const_iterator itr = mnfst.find("build");
|
|
if (itr == mnfst.end()) {
|
|
warn("Mason project at " + location.string() + " has no \"build\" section in its manifest");
|
|
state = done;
|
|
return;
|
|
}
|
|
|
|
const nlohmann::json& build = *itr;
|
|
if (!build.is_array()) {
|
|
warn("Mason project at " + location.string() + " has unexpected type \"build\" section in its manifest");
|
|
state = done;
|
|
return;
|
|
}
|
|
|
|
target = validateMasonTarget(target);
|
|
|
|
for (const nlohmann::json& entry : build) {
|
|
switch (entry.type()) {
|
|
case nlohmann::json::value_t::string:
|
|
copyFile(location/entry, destination/entry);
|
|
break;
|
|
case nlohmann::json::value_t::array:
|
|
masonFilesArray(entry, destination);
|
|
break;
|
|
case nlohmann::json::value_t::object:
|
|
if (allowedForMasonTarget(entry, target)) {
|
|
nlohmann::json::const_iterator fitr = entry.find("files");
|
|
if (fitr != entry.end()) {
|
|
const nlohmann::json& files = *fitr;
|
|
if (files.is_array())
|
|
masonFilesArray(files, destination);
|
|
else
|
|
warn(std::string("Unexpected ") + entry.type_name()
|
|
+ " of files subsection of build section of its manifest in "
|
|
+ location.string() + ", ignoring");
|
|
} else {
|
|
masonFileObject(entry, destination);
|
|
}
|
|
|
|
}
|
|
break;
|
|
default:
|
|
warn(std::string("Unexpected ") + entry.type_name() + " in build section of manifest in " + location.string() + ", ignoring");
|
|
break;
|
|
}
|
|
}
|
|
|
|
state = done;
|
|
}
|
|
|
|
void Component::copyFile(const std::filesystem::path& from, const std::filesystem::path& to) const {
|
|
std::filesystem::create_directories(to.parent_path());
|
|
|
|
if (std::filesystem::exists(to))
|
|
minor("File " + to.string() + " already exists, overwriting from " + from.string());
|
|
else
|
|
debug("Copying " + from.string() + " to " + to.string());
|
|
|
|
std::filesystem::copy(from, to, std::filesystem::copy_options::overwrite_existing);
|
|
}
|
|
|
|
void Component::masonFilesArray(const nlohmann::json& files, const std::filesystem::path& destination) {
|
|
for (const nlohmann::json& entry : files) {
|
|
switch (entry.type()) {
|
|
case nlohmann::json::value_t::string:
|
|
copyFile(location/entry, destination/entry);
|
|
break;
|
|
case nlohmann::json::value_t::object:
|
|
masonFileObject(entry, destination);
|
|
break;
|
|
default:
|
|
warn(std::string("Unexpected ") + entry.type_name()
|
|
+ " in files subsection of build section of manifest in "
|
|
+ location.string() + ", ignoring");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Component::masonFileObject(const nlohmann::json& object, const std::filesystem::path& destination) {
|
|
std::optional<std::string> src = getFileSource(object);
|
|
if (!src.has_value()) {
|
|
warn("Couldn't understand file source processing file array in " + location.string() + ", ignoring");
|
|
return false;
|
|
}
|
|
std::optional<std::string> dst = getFileTarget(object);
|
|
if (!src.has_value()) {
|
|
warn("Couldn't understand file destination processing file array in " + location.string() + ", ignoring");
|
|
return false;
|
|
}
|
|
|
|
copyFile(location/src.value(), destination/dst.value());
|
|
return true;
|
|
}
|
|
|
|
std::string Component::validateMasonTarget(std::string target) const {
|
|
const nlohmann::json& mnfst = manifest.value();
|
|
std::set<std::string> allowedTarges;
|
|
|
|
nlohmann::json::const_iterator itr = mnfst.find("targets");
|
|
if (itr != mnfst.end()) {
|
|
const nlohmann::json& targets = *itr;
|
|
if (targets.is_array()) {
|
|
for (const nlohmann::json& entry : targets) {
|
|
if (entry.is_string())
|
|
allowedTarges.emplace(entry);
|
|
}
|
|
} else {
|
|
warn("Mason project at " + location.string() + " has unexpected type \"targets\" section in its manifest, ignoring");
|
|
}
|
|
} else {
|
|
minor("Mason project at " + location.string() + " has no \"targets\" section, trying to figure out utilized targets from \"build\" section");
|
|
const nlohmann::json& build = mnfst.at("build");
|
|
for (const nlohmann::json& entry : build) {
|
|
if (entry.is_object())
|
|
getMasonTargets(entry, allowedTarges);
|
|
}
|
|
}
|
|
|
|
if (allowedTarges.count(target) > 0)
|
|
return target;
|
|
|
|
minor("Mason project at " + location.string() + " doesn't seem to support target \"" + target + "\"");
|
|
std::optional<std::string> defaultTarget = tryStringValue(mnfst, "defaultTarget");
|
|
if (defaultTarget.has_value()) {
|
|
target = defaultTarget.value();
|
|
minor("Mason project at " + location.string() + " will use project default target \"" + target + "\" instead of specified");
|
|
} else {
|
|
target = "default";
|
|
warn("Mason project at " + location.string() + " will use hardcoded default target \""
|
|
+ target + "\" because field \"defaultTarget\" is either abscent or invalid");
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
//this method could utilize Component::getMasonTargets but it doesn't because this way has less overhead
|
|
bool Component::allowedForMasonTarget(const nlohmann::json& object, const std::string& target) const {
|
|
nlohmann::json::const_iterator itr = object.find("target");
|
|
if (itr != object.end()) {
|
|
const nlohmann::json& tg = *itr;
|
|
if (tg.is_string())
|
|
return tg == target;
|
|
else
|
|
warn("\"target\" in build section has unexpected type, ignoring");
|
|
}
|
|
|
|
itr = object.find("targets");
|
|
if (itr != object.end()) {
|
|
const nlohmann::json& targets = *itr;
|
|
if (targets.is_array()) {
|
|
for (const nlohmann::json& entry : targets) {
|
|
if (entry.is_string()) {
|
|
if (entry == target)
|
|
return true;
|
|
} else {
|
|
warn("one of the targets in \"targets\" in build section has unexpected type, ignoring it");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} else {
|
|
warn("\"targets\" in build section has unexpected type, ignoring");
|
|
}
|
|
}
|
|
|
|
//TODO may be notTarget and notTargets?
|
|
|
|
return true;
|
|
}
|
|
|
|
void Component::getMasonTargets(const nlohmann::json& object, std::set<std::string>& out) const {
|
|
nlohmann::json::const_iterator itr = object.find("target");
|
|
if (itr != object.end()) {
|
|
const nlohmann::json& tg = *itr;
|
|
if (tg.is_string())
|
|
out.emplace(tg);
|
|
else
|
|
warn("\"target\" in build section has unexpected type, ignoring");
|
|
}
|
|
|
|
itr = object.find("targets");
|
|
if (itr != object.end()) {
|
|
const nlohmann::json& targets = *itr;
|
|
if (targets.is_array()) {
|
|
for (const nlohmann::json& entry : targets) {
|
|
if (entry.is_string())
|
|
out.emplace(entry);
|
|
else
|
|
warn("one of the targets in \"targets\" in build section has unexpected type, ignoring it");
|
|
}
|
|
|
|
} else {
|
|
warn("\"targets\" in build section has unexpected type, ignoring");
|
|
}
|
|
}
|
|
|
|
//TODO may be notTarget and notTargets?
|
|
}
|
|
|
|
std::optional<std::string> Component::getFileSource(const nlohmann::json& object) const {
|
|
for (const std::string_view& key : sourceVariants) {
|
|
std::optional<std::string> value = tryStringValue(object, key.data());
|
|
if (value.has_value())
|
|
return value;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Component::getFileTarget(const nlohmann::json& object) const {
|
|
for (const std::string_view& key : targetVariants) {
|
|
std::optional<std::string> value = tryStringValue(object, key.data());
|
|
if (value.has_value())
|
|
return value;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Component::tryStringValue(const nlohmann::json& object, const std::string& key) {
|
|
nlohmann::json::const_iterator itr = object.find(key);
|
|
if (itr != object.end()) {
|
|
const nlohmann::json& value = *itr;
|
|
if (value.is_string())
|
|
return value;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
Component::State Component::getState() const {
|
|
return state;
|
|
}
|
|
|
|
Component::Type Component::getType() const {
|
|
return type;
|
|
}
|
|
|
|
Component::WrongState::WrongState(State state, const std::string& action):
|
|
std::runtime_error("An attempt to perform action \"" + action
|
|
+ "\" on a wrong component state \"" + stringStates[state].data() + '\"')
|
|
{}
|
|
|
|
Component::WrongType::WrongType(Type type, const std::string& action):
|
|
std::runtime_error("An attempt to perform an action \"" + action
|
|
+ "\" on a wrong component type \"" + stringTypes[type].data() + '\"')
|
|
{}
|