{ | |||||
"$schema": "./res/library-schema.json", | |||||
"name": "dds", | |||||
"uses": [ | |||||
"spdlog/spdlog", | |||||
"Microsoft/wil", | |||||
"range-v3/range-v3", | |||||
"nlohmann/json", | |||||
"neo/sqlite3", | |||||
"neo/fun", | |||||
"semver/semver", | |||||
"pubgrub/pubgrub", | |||||
"vob/json5" | |||||
] | |||||
} |
{ | |||||
"type": "object", | |||||
"description": "DDS Library Manifest", | |||||
"additionalProperties": false, | |||||
"patternProperties": { | |||||
"^\\$": {} | |||||
}, | |||||
"required": [ | |||||
"name" | |||||
], | |||||
"properties": { | |||||
"name": { | |||||
"type": "string", | |||||
"description": "The name of the library within the package.", | |||||
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||||
}, | |||||
"uses": { | |||||
"type": "array", | |||||
"items": { | |||||
"type": "string", | |||||
"description": "A library that is used by this library. Should be of the form `namespace/name`.", | |||||
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||||
} | |||||
}, | |||||
"links": { | |||||
"type": "array", | |||||
"items": { | |||||
"type": "string", | |||||
"description": "A library that is linked to this library. Should be of the form `namespace/name`.", | |||||
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$" | |||||
} | |||||
} | |||||
} | |||||
} |
"properties": { | "properties": { | ||||
"name": { | "name": { | ||||
"type": "string", | "type": "string", | ||||
"description": "The name of the package. Must be a valid Semantic Version string.", | |||||
"description": "The name of the package", | |||||
"pattern": "^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$" | "pattern": "^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$" | ||||
}, | }, | ||||
"version": { | "version": { | ||||
"type": "string", | "type": "string", | ||||
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", | "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", | ||||
"description": "The version of the package. Required", | |||||
"description": "The version of the package. Must be a valid Semantic Version string.", | |||||
"default": "0.1.0" | "default": "0.1.0" | ||||
}, | }, | ||||
"namespace": { | "namespace": { |
return "sdist-ident-mismatch.html"; | return "sdist-ident-mismatch.html"; | ||||
case errc::corrupted_build_db: | case errc::corrupted_build_db: | ||||
return "corrupted-build-db.html"; | return "corrupted-build-db.html"; | ||||
case errc::invalid_lib_manifest: | |||||
return "invalid-lib-manifest.html"; | |||||
case errc::invalid_pkg_manifest: | case errc::invalid_pkg_manifest: | ||||
return "invalid-pkg-manifest.html"; | return "invalid-pkg-manifest.html"; | ||||
case errc::invalid_version_range_string: | case errc::invalid_version_range_string: | ||||
The catalog database schema doesn't match what dds expects. This indicates that | The catalog database schema doesn't match what dds expects. This indicates that | ||||
the database file has been modified in a way that dds cannot automatically fix | the database file has been modified in a way that dds cannot automatically fix | ||||
and handle. | and handle. | ||||
)"; | |||||
case errc::invalid_lib_manifest: | |||||
return R"( | |||||
A library manifest is malformed Refer to the documentation and above error | |||||
message for more details. | |||||
)"; | )"; | ||||
case errc::invalid_pkg_manifest: | case errc::invalid_pkg_manifest: | ||||
return R"( | return R"( | ||||
"that was expected of it"; | "that was expected of it"; | ||||
case errc::corrupted_build_db: | case errc::corrupted_build_db: | ||||
return "The build database file is corrupted"; | return "The build database file is corrupted"; | ||||
case errc::invalid_lib_manifest: | |||||
return "The library manifest is invalid"; | |||||
case errc::invalid_pkg_manifest: | case errc::invalid_pkg_manifest: | ||||
return "The package manifest is invalid"; | return "The package manifest is invalid"; | ||||
case errc::invalid_version_range_string: | case errc::invalid_version_range_string: | ||||
"`dds` bug. Please report it.)"; | "`dds` bug. Please report it.)"; | ||||
case errc::invalid_version_string: | case errc::invalid_version_string: | ||||
return "Attempted to parse an invalid version string. <- (Seeing this text is a `dds` " | return "Attempted to parse an invalid version string. <- (Seeing this text is a `dds` " | ||||
"bug. " | |||||
"Please report it.)"; | |||||
"bug. Please report it.)"; | |||||
case errc::invalid_config_key: | case errc::invalid_config_key: | ||||
return "Found an invalid configuration key. <- (Seeing this text is a `dds` bug. " | return "Found an invalid configuration key. <- (Seeing this text is a `dds` bug. " | ||||
"Please " | |||||
"report it.)"; | |||||
"Please report it.)"; | |||||
case errc::invalid_lib_filesystem: | case errc::invalid_lib_filesystem: | ||||
case errc::invalid_pkg_filesystem: | case errc::invalid_pkg_filesystem: | ||||
return "The filesystem structure of the package/library is invalid. <- (Seeing this " | return "The filesystem structure of the package/library is invalid. <- (Seeing this " | ||||
"text " | |||||
"is a `dds` bug. Please report it.)"; | |||||
"text is a `dds` bug. Please report it.)"; | |||||
case errc::invalid_pkg_id: | case errc::invalid_pkg_id: | ||||
return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | ||||
"report it.)"; | "report it.)"; | ||||
"it.)"; | "it.)"; | ||||
case errc::sdist_exists: | case errc::sdist_exists: | ||||
return "The source ditsribution already exists at the destination <- (Seeing this " | return "The source ditsribution already exists at the destination <- (Seeing this " | ||||
"text is " | |||||
"a `dds` bug. Please report it.)"; | |||||
"text is a `dds` bug. Please report it.)"; | |||||
case errc::unknown_test_driver: | case errc::unknown_test_driver: | ||||
return "The specified Test-Driver is not known to `dds`"; | return "The specified Test-Driver is not known to `dds`"; | ||||
case errc::dependency_resolve_failure: | case errc::dependency_resolve_failure: |
corrupted_build_db, | corrupted_build_db, | ||||
invalid_lib_manifest, | |||||
invalid_pkg_manifest, | invalid_pkg_manifest, | ||||
invalid_version_range_string, | invalid_version_range_string, | ||||
invalid_version_string, | invalid_version_string, |
#include "./manifest.hpp" | #include "./manifest.hpp" | ||||
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <range/v3/view/transform.hpp> | |||||
#include <dds/util/json5_read.hpp> | |||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <spdlog/fmt/fmt.h> | |||||
#include <json5/parse_data.hpp> | |||||
#include <range/v3/view/transform.hpp> | |||||
#include <spdlog/spdlog.h> | |||||
using namespace dds; | using namespace dds; | ||||
library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||||
library_manifest library_manifest::load_from_dds_file(path_ref fpath) { | |||||
spdlog::warn( | |||||
"Using deprecated library.dds parsing (on file {}). This will be removed soon. Migrate!", | |||||
fpath.string()); | |||||
auto kvs = lm::parse_file(fpath); | auto kvs = lm::parse_file(fpath); | ||||
library_manifest ret; | library_manifest ret; | ||||
ret.name = fpath.parent_path().filename().string(); | ret.name = fpath.parent_path().filename().string(); | ||||
extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | ||||
return ret; | return ret; | ||||
} | } | ||||
library_manifest library_manifest::load_from_file(path_ref fpath) { | |||||
auto content = slurp_file(fpath); | |||||
auto data = json5::parse_data(content); | |||||
if (!data.is_object()) { | |||||
throw_user_error<errc::invalid_lib_manifest>("Root value must be an object"); | |||||
} | |||||
library_manifest lib; | |||||
using namespace json_read::ops; | |||||
json_read::decompose( // | |||||
data.as_object(), | |||||
object(key("name", require_string(put_into{lib.name}, "`name` must be a string")), | |||||
key("uses", | |||||
array_each{require_string( | |||||
[&](auto&& uses) { | |||||
lib.uses.push_back(lm::split_usage_string(uses.as_string())); | |||||
return json_read::accept_t{}; | |||||
}, | |||||
"All `uses` items must be strings")}), | |||||
key("links", | |||||
array_each{require_string( | |||||
[&](auto&& links) { | |||||
lib.links.push_back(lm::split_usage_string(links.as_string())); | |||||
return json_read::accept_t{}; | |||||
}, | |||||
"All `links` items must be strings")}))); | |||||
if (lib.name.empty()) { | |||||
throw_user_error<errc::invalid_lib_manifest>( | |||||
"The 'name' field is required (Reading library manifest [{}])", fpath.string()); | |||||
} | |||||
return lib; | |||||
} | |||||
std::optional<fs::path> library_manifest::find_in_directory(path_ref dirpath) { | |||||
auto fnames = { | |||||
"library.json5", | |||||
"library.jsonc", | |||||
"library.json", | |||||
}; | |||||
for (auto c : fnames) { | |||||
auto cand = dirpath / c; | |||||
if (fs::is_regular_file(cand)) { | |||||
return cand; | |||||
} | |||||
} | |||||
auto dds_file = dirpath / "library.dds"; | |||||
if (fs::is_regular_file(dds_file)) { | |||||
return dds_file; | |||||
} | |||||
return std::nullopt; | |||||
} | |||||
std::optional<library_manifest> library_manifest::load_from_directory(path_ref dirpath) { | |||||
auto found = find_in_directory(dirpath); | |||||
if (!found.has_value()) { | |||||
return std::nullopt; | |||||
} | |||||
if (found->extension() == ".dds") { | |||||
return load_from_dds_file(*found); | |||||
} else { | |||||
return load_from_file(*found); | |||||
} | |||||
} |
/** | /** | ||||
* Load the library manifest from an existing file | * Load the library manifest from an existing file | ||||
*/ | */ | ||||
static library_manifest load_from_file(const fs::path&); | |||||
static library_manifest load_from_file(path_ref); | |||||
static library_manifest load_from_dds_file(path_ref); | |||||
/** | |||||
* Find a library manifest within a directory. This will search for a few | |||||
* file candidates and return the result from the first matching. If none | |||||
* match, it will return nullopt. | |||||
*/ | |||||
static std::optional<fs::path> find_in_directory(path_ref); | |||||
static std::optional<library_manifest> load_from_directory(path_ref); | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
auto sources = collect_pf_sources(lib_dir); | auto sources = collect_pf_sources(lib_dir); | ||||
library_manifest man; | library_manifest man; | ||||
man.name = lib_dir.filename().string(); | |||||
auto man_path = lib_dir / "library.dds"; | |||||
if (fs::is_regular_file(man_path)) { | |||||
man = library_manifest::load_from_file(man_path); | |||||
man.name = lib_dir.filename().string(); | |||||
auto found = library_manifest::find_in_directory(lib_dir); | |||||
if (found) { | |||||
if (found->extension() == ".dds") { | |||||
man = library_manifest::load_from_dds_file(*found); | |||||
} else { | |||||
man = library_manifest::load_from_file(*found); | |||||
} | |||||
} | } | ||||
auto lib = library_root(lib_dir, std::move(sources), std::move(man)); | auto lib = library_root(lib_dir, std::move(sources), std::move(man)); |
const auto& obj = data.as_object(); | const auto& obj = data.as_object(); | ||||
package_manifest ret; | package_manifest ret; | ||||
using namespace j5_read::ops; | |||||
j5_read::destructure( | |||||
using namespace json_read::ops; | |||||
json_read::decompose( | |||||
obj, | obj, | ||||
object( | object( | ||||
key("name", require_string(put_into{ret.pkg_id.name}, "`name` must be a string")), | key("name", require_string(put_into{ret.pkg_id.name}, "`name` must be a string")), | ||||
[&](auto&& version_str_) { | [&](auto&& version_str_) { | ||||
auto& version = version_str_.as_string(); | auto& version = version_str_.as_string(); | ||||
ret.pkg_id.version = semver::version::parse(version); | ret.pkg_id.version = semver::version::parse(version); | ||||
return j5_read::accept_t{}; | |||||
return json_read::accept_t{}; | |||||
}, | }, | ||||
"`version` must be a string")), | "`version` must be a string")), | ||||
key("depends", object([&](auto key, auto&& range_str_) { | key("depends", object([&](auto key, auto&& range_str_) { | ||||
range_str_.as_string(), | range_str_.as_string(), | ||||
pkg_name); | pkg_name); | ||||
} | } | ||||
return j5_read::accept_t{}; | |||||
return json_read::accept_t{}; | |||||
})), | })), | ||||
key("test_driver", | key("test_driver", | ||||
require_string( | require_string( | ||||
test_driver, | test_driver, | ||||
dym); | dym); | ||||
} | } | ||||
return j5_read::accept_t{}; | |||||
return json_read::accept_t{}; | |||||
}, | }, | ||||
"`test_driver` must be a valid test driver name string")), | "`test_driver` must be a valid test driver name string")), | ||||
reject_key)); | reject_key)); |
namespace dds { | namespace dds { | ||||
namespace j5_read { | |||||
namespace json_read { | |||||
struct reject_t { | struct reject_t { | ||||
std::string message; | std::string message; | ||||
} | } | ||||
}; | }; | ||||
template <typename... Hs> | |||||
then(Hs...) -> then<Hs...>; | |||||
template <typename... KeyHandlers> | template <typename... KeyHandlers> | ||||
struct object { | struct object { | ||||
std::tuple<KeyHandlers...> _keys; | std::tuple<KeyHandlers...> _keys; | ||||
} | } | ||||
}; | }; | ||||
template <typename... Ks> | |||||
object(Ks...) -> object<Ks...>; | |||||
template <typename Handler> | |||||
struct array_each { | |||||
Handler _hs; | |||||
result_var operator()(const json5::data& arr) { | |||||
if (!arr.is_array()) { | |||||
return pass_t{}; | |||||
} | |||||
for (const auto& elem : arr.as_array()) { | |||||
result_var res = _hs(elem); | |||||
if (std::holds_alternative<reject_t>(res)) { | |||||
return res; | |||||
} | |||||
} | |||||
return accept_t{}; | |||||
} | |||||
}; | |||||
template <typename Handler> | |||||
array_each(Handler) -> array_each<Handler>; | |||||
template <typename Handler> | template <typename Handler> | ||||
struct key { | struct key { | ||||
} // namespace ops | } // namespace ops | ||||
template <typename Handler> | template <typename Handler> | ||||
auto destructure(const json5::data& dat, Handler&& h) { | |||||
auto decompose(const json5::data& dat, Handler&& h) { | |||||
result_var res = h(dat); | result_var res = h(dat); | ||||
if (std::holds_alternative<reject_t>(res)) { | if (std::holds_alternative<reject_t>(res)) { | ||||
throw std::runtime_error(std::get<reject_t>(res).message); | throw std::runtime_error(std::get<reject_t>(res).message); | ||||
} | } | ||||
} | } | ||||
} // namespace j5_read | |||||
} // namespace json_read | |||||
} // namespace dds | } // namespace dds |