#include "component.h" #include "collection.h" constexpr std::string_view masonJSON("mason.json"); constexpr std::string_view dependencies("dependencies"); constexpr std::array sourceVariants({ "source", "src", "from", "in", "file" }); constexpr std::array targetVariants({ "target", "dst", "to", "out", "destination" }); constexpr std::array stringStates { "initial", "reading", "ready", "building", "error", "done" }; constexpr std::array stringTypes { "file", "directory", "mason", "unknown" }; Component::Component( const std::filesystem::path& path, Collection* collection, const std::shared_ptr& logger, const std::optional& 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 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 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 src = getFileSource(object); if (!src.has_value()) { warn("Couldn't understand file source processing file array in " + location.string() + ", ignoring"); return false; } std::optional 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 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 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& 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 Component::getFileSource(const nlohmann::json& object) const { for (const std::string_view& key : sourceVariants) { std::optional value = tryStringValue(object, key.data()); if (value.has_value()) return value; } return std::nullopt; } std::optional Component::getFileTarget(const nlohmann::json& object) const { for (const std::string_view& key : targetVariants) { std::optional value = tryStringValue(object, key.data()); if (value.has_value()) return value; } return std::nullopt; } std::optional 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() + '\"') {}