| Depends: ms-wil 2019.11.10 | Depends: ms-wil 2019.11.10 | ||||
| Depends: range-v3 0.9.1 | Depends: range-v3 0.9.1 | ||||
| Depends: nlohmann-json 3.7.1 | Depends: nlohmann-json 3.7.1 | ||||
| Depends: neo-sqlite3 0.2.0 | |||||
| Depends: semver 0.1.0 | |||||
| Depends: neo-sqlite3 0.2.2 | |||||
| Depends: semver 0.2.0 | |||||
| Test-Driver: Catch-Main | Test-Driver: Catch-Main |
| # XXX: Don't depend on a moving revision! | # XXX: Don't depend on a moving revision! | ||||
| Remote-Package: neo-buffer 0.1.0; git url=https://github.com/vector-of-bool/neo-buffer.git ref=develop | Remote-Package: neo-buffer 0.1.0; git url=https://github.com/vector-of-bool/neo-buffer.git ref=develop | ||||
| Remote-Package: neo-sqlite3 0.2.0; git url=https://github.com/vector-of-bool/neo-sqlite3.git ref=0.2.0 | |||||
| Remote-Package: semver 0.1.0; git url=https://github.com/vector-of-bool/semver.git ref=0.1.0 | |||||
| Remote-Package: neo-sqlite3 0.2.2; git url=https://github.com/vector-of-bool/neo-sqlite3.git ref=0.2.2 | |||||
| Remote-Package: semver 0.2.0; git url=https://github.com/vector-of-bool/semver.git ref=0.2.0 |
| #include "./catalog.hpp" | |||||
| #include <neo/sqlite3/exec.hpp> | |||||
| #include <neo/sqlite3/iter_tuples.hpp> | |||||
| #include <neo/sqlite3/single.hpp> | |||||
| #include <nlohmann/json.hpp> | |||||
| #include <range/v3/range/conversion.hpp> | |||||
| #include <range/v3/view/join.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| using namespace dds; | |||||
| namespace sqlite3 = neo::sqlite3; | |||||
| using namespace sqlite3::literals; | |||||
| namespace { | |||||
| void migrate_repodb_1(sqlite3::database& db) { | |||||
| db.exec(R"( | |||||
| CREATE TABLE dds_cat_pkgs ( | |||||
| pkg_id INTEGER PRIMARY KEY AUTOINCREMENT, | |||||
| name TEXT NOT NULL, | |||||
| version TEXT NOT NULL, | |||||
| git_url TEXT, | |||||
| git_ref TEXT, | |||||
| lm_name TEXT, | |||||
| lm_namespace TEXT, | |||||
| UNIQUE(name, version), | |||||
| CONSTRAINT has_remote_info CHECK( | |||||
| ( | |||||
| git_url NOT NULL | |||||
| AND git_ref NOT NULL | |||||
| ) | |||||
| ), | |||||
| CONSTRAINT valid_lm_info CHECK( | |||||
| ( | |||||
| lm_name NOT NULL | |||||
| AND lm_namespace NOT NULL | |||||
| ) | |||||
| + | |||||
| ( | |||||
| lm_name ISNULL | |||||
| AND lm_namespace ISNULL | |||||
| ) | |||||
| = 1 | |||||
| ) | |||||
| ); | |||||
| CREATE TABLE dds_cat_pkg_deps ( | |||||
| dep_id INTEGER PRIMARY KEY AUTOINCREMENT, | |||||
| pkg_id INTEGER NOT NULL REFERENCES dds_cat_pkgs(pkg_id), | |||||
| dep_name TEXT NOT NULL, | |||||
| low TEXT NOT NULL, | |||||
| high TEXT NOT NULL, | |||||
| UNIQUE(pkg_id, dep_name) | |||||
| ); | |||||
| )"); | |||||
| } | |||||
| void ensure_migrated(sqlite3::database& db) { | |||||
| sqlite3::transaction_guard tr{db}; | |||||
| db.exec(R"( | |||||
| PRAGMA foreign_keys = 1; | |||||
| CREATE TABLE IF NOT EXISTS dds_cat_meta AS | |||||
| WITH init(meta) AS (VALUES ('{"version": 0}')) | |||||
| SELECT * FROM init; | |||||
| )"); | |||||
| auto meta_st = db.prepare("SELECT meta FROM dds_cat_meta"); | |||||
| auto [meta_json] = sqlite3::unpack_single<std::string>(meta_st); | |||||
| auto meta = nlohmann::json::parse(meta_json); | |||||
| if (!meta.is_object()) { | |||||
| throw std::runtime_error("Corrupted repository database file."); | |||||
| } | |||||
| auto version_ = meta["version"]; | |||||
| if (!version_.is_number_integer()) { | |||||
| throw std::runtime_error("Corrupted repository database file [bad dds_meta.version]"); | |||||
| } | |||||
| int version = version_; | |||||
| if (version < 1) { | |||||
| migrate_repodb_1(db); | |||||
| } | |||||
| meta["version"] = 1; | |||||
| exec(db, "UPDATE dds_cat_meta SET meta=?", std::forward_as_tuple(meta.dump())); | |||||
| } | |||||
| } // namespace | |||||
| catalog catalog::open(const std::string& db_path) { | |||||
| auto db = sqlite3::database::open(db_path); | |||||
| try { | |||||
| ensure_migrated(db); | |||||
| } catch (const sqlite3::sqlite3_error& e) { | |||||
| spdlog::critical( | |||||
| "Failed to load the repository databsae. It appears to be invalid/corrupted. The " | |||||
| "exception message is: {}", | |||||
| e.what()); | |||||
| throw; | |||||
| } | |||||
| return catalog(std::move(db)); | |||||
| } | |||||
| catalog::catalog(sqlite3::database db) | |||||
| : _db(std::move(db)) {} | |||||
| void catalog::_store_pkg(const package_info& pkg, const git_remote_listing& git) { | |||||
| auto lm_usage = git.auto_lib.value_or(lm::usage{}); | |||||
| sqlite3::exec( // | |||||
| _stmt_cache, | |||||
| R"( | |||||
| INSERT INTO dds_cat_pkgs ( | |||||
| name, | |||||
| version, | |||||
| git_url, | |||||
| git_ref, | |||||
| lm_name, | |||||
| lm_namespace | |||||
| ) VALUES ( | |||||
| ?1, | |||||
| ?2, | |||||
| ?3, | |||||
| ?4, | |||||
| CASE WHEN ?5 = '' THEN NULL ELSE ?5 END, | |||||
| CASE WHEN ?6 = '' THEN NULL ELSE ?6 END | |||||
| ) | |||||
| )"_sql, | |||||
| std::forward_as_tuple( // | |||||
| pkg.ident.name, | |||||
| pkg.ident.version.to_string(), | |||||
| git.url, | |||||
| git.ref, | |||||
| lm_usage.name, | |||||
| lm_usage.namespace_)); | |||||
| } | |||||
| void catalog::store(const package_info& pkg) { | |||||
| sqlite3::transaction_guard tr{_db}; | |||||
| std::visit([&](auto&& remote) { _store_pkg(pkg, remote); }, pkg.remote); | |||||
| auto db_pkg_id = _db.last_insert_rowid(); | |||||
| auto& new_dep_st = _stmt_cache(R"( | |||||
| INSERT INTO dds_cat_pkg_deps ( | |||||
| pkg_id, | |||||
| dep_name, | |||||
| low, | |||||
| high | |||||
| ) VALUES ( | |||||
| ?, | |||||
| ?, | |||||
| ?, | |||||
| ? | |||||
| ) | |||||
| )"_sql); | |||||
| for (const auto& dep : pkg.deps) { | |||||
| new_dep_st.reset(); | |||||
| sqlite3::exec(new_dep_st, | |||||
| std::forward_as_tuple(db_pkg_id, | |||||
| dep.name, | |||||
| dep.version.to_string(), | |||||
| "[placeholder]")); | |||||
| } | |||||
| } | |||||
| std::optional<package_info> catalog::get(const package_id& pk_id) const noexcept { | |||||
| auto& st = _stmt_cache(R"( | |||||
| SELECT | |||||
| pkg_id, | |||||
| name, | |||||
| version, | |||||
| git_url, | |||||
| git_ref, | |||||
| lm_name, | |||||
| lm_namespace | |||||
| FROM dds_cat_pkgs | |||||
| WHERE name = ? AND version = ? | |||||
| )"_sql); | |||||
| st.reset(); | |||||
| st.bindings = std::forward_as_tuple(pk_id.name, pk_id.version.to_string()); | |||||
| auto opt_tup = sqlite3::unpack_single_opt<std::int64_t, | |||||
| std::string, | |||||
| std::string, | |||||
| std::optional<std::string>, | |||||
| std::optional<std::string>, | |||||
| std::optional<std::string>, | |||||
| std::optional<std::string>>(st); | |||||
| if (!opt_tup) { | |||||
| return std::nullopt; | |||||
| } | |||||
| const auto& [pkg_id, name, version, git_url, git_ref, lm_name, lm_namespace] = *opt_tup; | |||||
| assert(pk_id.name == name); | |||||
| assert(pk_id.version == semver::version::parse(version)); | |||||
| assert(git_url); | |||||
| assert(git_ref); | |||||
| auto deps = sqlite3::exec_iter<std::string, std::string>( // | |||||
| _stmt_cache, | |||||
| R"( | |||||
| SELECT dep_name, low | |||||
| FROM dds_cat_pkg_deps | |||||
| WHERE pkg_id = ? | |||||
| )"_sql, | |||||
| std::tie(pkg_id)) | |||||
| | ranges::views::transform([](auto&& pair) { | |||||
| const auto& [name, ver] = pair; | |||||
| return dependency{name, semver::version::parse(ver)}; | |||||
| }) // | |||||
| | ranges::to_vector; | |||||
| return package_info{ | |||||
| pk_id, | |||||
| deps, | |||||
| git_remote_listing{ | |||||
| *git_url, | |||||
| *git_ref, | |||||
| lm_name ? std::make_optional(lm::usage{*lm_name, *lm_namespace}) : std::nullopt, | |||||
| }, | |||||
| }; | |||||
| } | |||||
| std::vector<package_id> catalog::by_name(std::string_view sv) const noexcept { | |||||
| return sqlite3::exec_iter<std::string, std::string>( // | |||||
| _stmt_cache, | |||||
| R"( | |||||
| SELECT name, version | |||||
| FROM dds_cat_pkgs | |||||
| WHERE name = ? | |||||
| )"_sql, | |||||
| std::tie(sv)) // | |||||
| | ranges::views::transform([](auto& pair) { | |||||
| auto& [name, ver] = pair; | |||||
| return package_id{name, semver::version::parse(ver)}; | |||||
| }) | |||||
| | ranges::to_vector; | |||||
| } | |||||
| std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const noexcept { | |||||
| return sqlite3::exec_iter<std::string, | |||||
| std::string>( // | |||||
| _stmt_cache, | |||||
| R"( | |||||
| WITH this_pkg_id AS ( | |||||
| SELECT pkg_id | |||||
| FROM dds_cat_pkgs | |||||
| WHERE name = ? AND version = ? | |||||
| ) | |||||
| SELECT dep_name, low | |||||
| FROM dds_cat_pkg_deps | |||||
| WHERE pkg_id IN this_pkg_id | |||||
| ORDER BY dep_name | |||||
| )"_sql, | |||||
| std::forward_as_tuple(pkg.name, pkg.version.to_string())) // | |||||
| | ranges::views::transform([](auto&& pair) { | |||||
| auto& [name, ver] = pair; | |||||
| return dependency{name, semver::version::parse(ver)}; | |||||
| }) // | |||||
| | ranges::to_vector; | |||||
| } | |||||
| namespace { | |||||
| void check_json(bool b, std::string_view what) { | |||||
| if (!b) { | |||||
| throw std::runtime_error("Unable to read repository JSON: " + std::string(what)); | |||||
| } | |||||
| } | |||||
| } // namespace | |||||
| void catalog::import_json_str(std::string_view content) { | |||||
| using nlohmann::json; | |||||
| auto root = json::parse(content); | |||||
| check_json(root.is_object(), "Root of JSON must be an object (key-value mapping)"); | |||||
| auto version = root["version"]; | |||||
| check_json(version.is_number_integer(), "/version must be an integral value"); | |||||
| check_json(version <= 1, "/version is too new. We don't know how to parse this."); | |||||
| auto packages = root["packages"]; | |||||
| check_json(packages.is_object(), "/packages must be an object"); | |||||
| sqlite3::transaction_guard tr{_db}; | |||||
| for (const auto& [pkg_name_, versions_map] : packages.items()) { | |||||
| std::string pkg_name = pkg_name_; | |||||
| check_json(versions_map.is_object(), | |||||
| fmt::format("/packages/{} must be an object", pkg_name)); | |||||
| for (const auto& [version_, pkg_info] : versions_map.items()) { | |||||
| auto version = semver::version::parse(version_); | |||||
| check_json(pkg_info.is_object(), | |||||
| fmt::format("/packages/{}/{} must be an object", pkg_name, version_)); | |||||
| auto deps = pkg_info["depends"]; | |||||
| check_json(deps.is_object(), | |||||
| fmt::format("/packages/{}/{}/depends must be an object", | |||||
| pkg_name, | |||||
| version_)); | |||||
| package_info info{{pkg_name, version}, {}, {}}; | |||||
| for (const auto& [dep_name, dep_version] : deps.items()) { | |||||
| check_json(dep_version.is_string(), | |||||
| fmt::format("/packages/{}/{}/depends/{} must be a string", | |||||
| pkg_name, | |||||
| version_, | |||||
| dep_name)); | |||||
| info.deps.push_back({ | |||||
| std::string(dep_name), | |||||
| semver::version::parse(std::string(dep_version)), | |||||
| }); | |||||
| } | |||||
| auto git_remote = pkg_info["git"]; | |||||
| if (!git_remote.is_null()) { | |||||
| check_json(git_remote.is_object(), "`git` must be an object"); | |||||
| std::string url = git_remote["url"]; | |||||
| std::string ref = git_remote["ref"]; | |||||
| auto lm_usage = git_remote["auto-lib"]; | |||||
| std::optional<lm::usage> autolib; | |||||
| if (!lm_usage.is_null()) { | |||||
| autolib = lm::split_usage_string(std::string(lm_usage)); | |||||
| } | |||||
| info.remote = git_remote_listing{url, ref, autolib}; | |||||
| } else { | |||||
| throw std::runtime_error( | |||||
| fmt::format("No remote info for /packages/{}/{}", pkg_name, version_)); | |||||
| } | |||||
| store(info); | |||||
| } | |||||
| } | |||||
| } |
| #pragma once | |||||
| #include <dds/deps.hpp> | |||||
| #include <dds/package_id.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <dds/catalog/git.hpp> | |||||
| #include <neo/sqlite3/database.hpp> | |||||
| #include <neo/sqlite3/statement.hpp> | |||||
| #include <neo/sqlite3/statement_cache.hpp> | |||||
| #include <neo/sqlite3/transaction.hpp> | |||||
| #include <variant> | |||||
| #include <vector> | |||||
| namespace dds { | |||||
| struct package_info { | |||||
| package_id ident; | |||||
| std::vector<dependency> deps; | |||||
| std::variant<git_remote_listing> remote; | |||||
| }; | |||||
| class catalog { | |||||
| neo::sqlite3::database _db; | |||||
| mutable neo::sqlite3::statement_cache _stmt_cache{_db}; | |||||
| explicit catalog(neo::sqlite3::database db); | |||||
| catalog(const catalog&) = delete; | |||||
| void _store_pkg(const package_info&, const git_remote_listing&); | |||||
| public: | |||||
| catalog(catalog&&) = default; | |||||
| catalog& operator=(catalog&&) = default; | |||||
| static catalog open(const std::string& db_path); | |||||
| static catalog open(path_ref db_path) { return open(db_path.string()); } | |||||
| void store(const package_info& info); | |||||
| std::optional<package_info> get(const package_id& id) const noexcept; | |||||
| std::vector<package_id> by_name(std::string_view sv) const noexcept; | |||||
| std::vector<dependency> dependencies_of(const package_id& pkg) const noexcept; | |||||
| void import_json_str(std::string_view json_str); | |||||
| void import_json_file(path_ref json_path) { | |||||
| auto content = dds::slurp_file(json_path); | |||||
| import_json_str(content); | |||||
| } | |||||
| }; | |||||
| } // namespace dds |
| #include <dds/catalog/catalog.hpp> | |||||
| #include <catch2/catch.hpp> | |||||
| using namespace std::literals; | |||||
| TEST_CASE("Create a simple database") { | |||||
| // Just create and run migrations on an in-memory database | |||||
| auto repo = dds::catalog::open(":memory:"s); | |||||
| } | |||||
| class catalog_test_case { | |||||
| public: | |||||
| dds::catalog db = dds::catalog::open(":memory:"s); | |||||
| }; | |||||
| TEST_CASE_METHOD(catalog_test_case, "Store a simple package") { | |||||
| db.store(dds::package_info{ | |||||
| dds::package_id("foo", semver::version::parse("1.2.3")), | |||||
| {}, | |||||
| dds::git_remote_listing{"http://example.com", "master", std::nullopt}, | |||||
| }); | |||||
| CHECK_THROWS(db.store(dds::package_info{ | |||||
| dds::package_id("foo", semver::version::parse("1.2.3")), | |||||
| {}, | |||||
| dds::git_remote_listing{"http://example.com", "master", std::nullopt}, | |||||
| })); | |||||
| auto pkgs = db.by_name("foo"); | |||||
| REQUIRE(pkgs.size() == 1); | |||||
| } | |||||
| TEST_CASE_METHOD(catalog_test_case, "Package requirements") { | |||||
| db.store(dds::package_info{ | |||||
| dds::package_id{"foo", semver::version::parse("1.2.3")}, | |||||
| { | |||||
| {"bar", semver::version::parse("1.2.5")}, | |||||
| {"baz", semver::version::parse("5.3.2")}, | |||||
| }, | |||||
| dds::git_remote_listing{"http://example.com", "master", std::nullopt}, | |||||
| }); | |||||
| auto pkgs = db.by_name("foo"); | |||||
| REQUIRE(pkgs.size() == 1); | |||||
| CHECK(pkgs[0].name == "foo"); | |||||
| auto deps = db.dependencies_of(pkgs[0]); | |||||
| CHECK(deps.size() == 2); | |||||
| CHECK(deps[0].name == "bar"); | |||||
| CHECK(deps[1].name == "baz"); | |||||
| } | |||||
| TEST_CASE_METHOD(catalog_test_case, "Parse JSON repo") { | |||||
| db.import_json_str(R"({ | |||||
| "version": 1, | |||||
| "packages": { | |||||
| "foo": { | |||||
| "1.2.3": { | |||||
| "depends": { | |||||
| "bar": "4.2.1" | |||||
| }, | |||||
| "git": { | |||||
| "url": "http://example.com", | |||||
| "ref": "master" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| })"); | |||||
| auto pkgs = db.by_name("foo"); | |||||
| REQUIRE(pkgs.size() == 1); | |||||
| CHECK(pkgs[0].name == "foo"); | |||||
| CHECK(pkgs[0].version == semver::version::parse("1.2.3")); | |||||
| auto deps = db.dependencies_of(pkgs[0]); | |||||
| REQUIRE(deps.size() == 1); | |||||
| CHECK(deps[0].name == "bar"); | |||||
| CHECK(deps[0].version == semver::version::parse("4.2.1")); | |||||
| } |
| #include "./get.hpp" | |||||
| #include <dds/catalog/catalog.hpp> | |||||
| #include <dds/proc.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| using namespace dds; | |||||
| namespace { | |||||
| temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_listing& git) { | |||||
| auto tmpdir = dds::temporary_dir::create(); | |||||
| using namespace std::literals; | |||||
| spdlog::info("Cloning Git repository: {} [{}] ...", git.url, git.ref); | |||||
| auto command = {"git"s, | |||||
| "clone"s, | |||||
| "--depth=1"s, | |||||
| "--branch"s, | |||||
| git.ref, | |||||
| git.url, | |||||
| tmpdir.path().generic_string()}; | |||||
| auto git_res = run_proc(command); | |||||
| if (!git_res.okay()) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Git clone operation failed [Git command: {}] [Exitted {}]:\n{}", | |||||
| quote_command(command), | |||||
| git_res.retc, | |||||
| git_res.output)); | |||||
| } | |||||
| spdlog::info("Create sdist from clone ..."); | |||||
| if (git.auto_lib.has_value()) { | |||||
| spdlog::info("Generating library data automatically"); | |||||
| auto pkg_strm = dds::open(tmpdir.path() / "package.dds", std::ios::binary | std::ios::out); | |||||
| pkg_strm << "Name: " << listing.ident.name << '\n' // | |||||
| << "Version: " << listing.ident.version.to_string() << '\n' // | |||||
| << "Namespace: " << git.auto_lib->namespace_; | |||||
| auto lib_strm = dds::open(tmpdir.path() / "library.dds", std::ios::binary | std::ios::out); | |||||
| lib_strm << "Name: " << git.auto_lib->name; | |||||
| } | |||||
| sdist_params params; | |||||
| params.project_dir = tmpdir.path(); | |||||
| auto sd_tmp_dir = dds::temporary_dir::create(); | |||||
| params.dest_path = sd_tmp_dir.path(); | |||||
| params.force = true; | |||||
| auto sd = create_sdist(params); | |||||
| return {sd_tmp_dir, sd}; | |||||
| } | |||||
| } // namespace | |||||
| temporary_sdist dds::get_package_sdist(const package_info& pkg) { | |||||
| auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote); | |||||
| if (!(tsd.sdist.manifest.pk_id == pkg.ident)) { | |||||
| throw std::runtime_error(fmt::format( | |||||
| "The package name@version in the generated sdist does not match the name listed in " | |||||
| "the remote listing file (expected '{}', but got '{}')", | |||||
| pkg.ident.to_string(), | |||||
| tsd.sdist.manifest.pk_id.to_string())); | |||||
| } | |||||
| return tsd; | |||||
| } |
| #pragma once | |||||
| #include <dds/sdist.hpp> | |||||
| #include <dds/temp.hpp> | |||||
| namespace dds { | |||||
| struct package_info; | |||||
| struct temporary_sdist { | |||||
| temporary_dir tmpdir; | |||||
| dds::sdist sdist; | |||||
| }; | |||||
| temporary_sdist get_package_sdist(const package_info&); | |||||
| } // namespace dds |
| #pragma once | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <libman/package.hpp> | |||||
| #include <optional> | |||||
| #include <string> | |||||
| namespace dds { | |||||
| struct git_remote_listing { | |||||
| std::string url; | |||||
| std::string ref; | |||||
| std::optional<lm::usage> auto_lib; | |||||
| void clone(path_ref path) const; | |||||
| }; | |||||
| } // namespace dds |
| #include <dds/build.hpp> | #include <dds/build.hpp> | ||||
| #include <dds/repo/remote.hpp> | |||||
| #include <dds/catalog/catalog.hpp> | |||||
| #include <dds/catalog/get.hpp> | |||||
| #include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
| #include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
| #include <dds/toolchain/from_dds.hpp> | #include <dds/toolchain/from_dds.hpp> | ||||
| } | } | ||||
| }; | }; | ||||
| struct repo_where_flag : path_flag { | |||||
| repo_where_flag(args::Group& grp) | |||||
| struct repo_path_flag : path_flag { | |||||
| repo_path_flag(args::Group& grp) | |||||
| : path_flag{grp, | : path_flag{grp, | ||||
| "dir", | "dir", | ||||
| "Path to the DDS repository directory", | "Path to the DDS repository directory", | ||||
| dds::repository::default_local_path()} {} | dds::repository::default_local_path()} {} | ||||
| }; | }; | ||||
| struct catalog_path_flag : path_flag { | |||||
| catalog_path_flag(args::Group& cmd) | |||||
| : path_flag(cmd, | |||||
| "catalog-path", | |||||
| "Override the path to the catalog database", | |||||
| {"catalog", 'c'}, | |||||
| dds::dds_data_dir() / "catalog.db") {} | |||||
| dds::catalog open() { return dds::catalog::open(Get()); } | |||||
| }; | |||||
| /** | /** | ||||
| * Base class holds the actual argument parser | * Base class holds the actual argument parser | ||||
| */ | */ | ||||
| dds::fs::current_path()}; | dds::fs::current_path()}; | ||||
| }; | }; | ||||
| /* | |||||
| ###### ### ######## ### ## ####### ###### | |||||
| ## ## ## ## ## ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## ## ## #### | |||||
| ## ######### ## ######### ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## ## ## ## ## | |||||
| ###### ## ## ## ## ## ######## ####### ###### | |||||
| */ | |||||
| struct cli_catalog { | |||||
| cli_base& base; | |||||
| args::Command cmd{base.cmd_group, "catalog", "Manage the package catalog"}; | |||||
| common_flags _common{cmd}; | |||||
| args::Group cat_group{cmd, "Catalog subcommands"}; | |||||
| struct { | |||||
| cli_catalog& parent; | |||||
| args::Command cmd{parent.cat_group, "create", "Create a catalog database"}; | |||||
| common_flags _common{cmd}; | |||||
| catalog_path_flag cat_path{cmd}; | |||||
| int run() { | |||||
| // Simply opening the DB will initialize the catalog | |||||
| cat_path.open(); | |||||
| return 0; | |||||
| } | |||||
| } create{*this}; | |||||
| struct { | |||||
| cli_catalog& parent; | |||||
| args::Command cmd{parent.cat_group, "import", "Import entries into a catalog"}; | |||||
| common_flags _common{cmd}; | |||||
| catalog_path_flag cat_path{cmd}; | |||||
| args::ValueFlagList<std::string> | |||||
| json_paths{cmd, | |||||
| "json", | |||||
| "Import catalog entries from the given JSON files", | |||||
| {"json", 'j'}}; | |||||
| int run() { | |||||
| auto cat = cat_path.open(); | |||||
| for (const auto& json_fpath : json_paths.Get()) { | |||||
| cat.import_json_file(json_fpath); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } import{*this}; | |||||
| struct { | |||||
| cli_catalog& parent; | |||||
| args::Command cmd{parent.cat_group, "get", "Obtain an sdist from a catalog listing"}; | |||||
| common_flags _common{cmd}; | |||||
| catalog_path_flag cat_path{cmd}; | |||||
| path_flag out{cmd, | |||||
| "out", | |||||
| "The directory where the source distributions will be placed", | |||||
| {"out-dir", 'o'}, | |||||
| dds::fs::current_path()}; | |||||
| args::PositionalList<std::string> requirements{cmd, | |||||
| "requirement", | |||||
| "The package IDs to obtain"}; | |||||
| int run() { | |||||
| auto cat = cat_path.open(); | |||||
| for (const auto& req : requirements.Get()) { | |||||
| auto id = dds::package_id::parse(req); | |||||
| auto info = cat.get(id); | |||||
| if (!info) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("No package in the catalog matched the ID '{}'", req)); | |||||
| } | |||||
| auto tsd = dds::get_package_sdist(*info); | |||||
| auto out_path = out.Get(); | |||||
| auto dest = out_path / id.to_string(); | |||||
| spdlog::info("Create sdist at {}", dest.string()); | |||||
| dds::fs::remove_all(dest); | |||||
| dds::safe_rename(tsd.sdist.path, dest); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } get{*this}; | |||||
| struct { | |||||
| cli_catalog& parent; | |||||
| args::Command cmd{parent.cat_group, "add", "Manually add an entry to the catalog database"}; | |||||
| common_flags _common{cmd}; | |||||
| catalog_path_flag cat_path{cmd}; | |||||
| args::Positional<std::string> pkg_id{cmd, | |||||
| "id", | |||||
| "The name@version ID of the package to add", | |||||
| args::Options::Required}; | |||||
| string_flag auto_lib{cmd, | |||||
| "auto-lib", | |||||
| "Set the auto-library information for this package", | |||||
| {"auto-lib"}}; | |||||
| args::ValueFlagList<std::string> deps{cmd, | |||||
| "depends", | |||||
| "The dependencies of this package", | |||||
| {"depends", 'd'}}; | |||||
| string_flag git_url{cmd, "git-url", "The Git url for the package", {"git-url"}}; | |||||
| string_flag git_ref{cmd, | |||||
| "git-ref", | |||||
| "The Git ref to from which the source distribution should be created", | |||||
| {"git-ref"}}; | |||||
| int run() { | |||||
| auto ident = dds::package_id::parse(pkg_id.Get()); | |||||
| std::vector<dds::dependency> deps; | |||||
| for (const auto& dep : this->deps.Get()) { | |||||
| auto dep_id = dds::package_id::parse(dep); | |||||
| deps.push_back({dep_id.name, dep_id.version}); | |||||
| } | |||||
| dds::package_info info{ident, std::move(deps), {}}; | |||||
| if (git_url) { | |||||
| if (!git_ref) { | |||||
| throw std::runtime_error( | |||||
| "`--git-ref` must be specified when using `--git-url`"); | |||||
| } | |||||
| auto git = dds::git_remote_listing{git_url.Get(), git_ref.Get(), std::nullopt}; | |||||
| if (auto_lib) { | |||||
| git.auto_lib = lm::split_usage_string(auto_lib.Get()); | |||||
| } | |||||
| info.remote = std::move(git); | |||||
| } | |||||
| cat_path.open().store(info); | |||||
| return 0; | |||||
| } | |||||
| } add{*this}; | |||||
| int run() { | |||||
| if (create.cmd) { | |||||
| return create.run(); | |||||
| } else if (import.cmd) { | |||||
| return import.run(); | |||||
| } else if (get.cmd) { | |||||
| return get.run(); | |||||
| } else if (add.cmd) { | |||||
| return add.run(); | |||||
| } else { | |||||
| assert(false); | |||||
| std::terminate(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| /* | /* | ||||
| ######## ######## ######## ####### | ######## ######## ######## ####### | ||||
| ## ## ## ## ## ## ## | ## ## ## ## ## ## ## | ||||
| args::Command cmd{base.cmd_group, "repo", "Manage the package repository"}; | args::Command cmd{base.cmd_group, "repo", "Manage the package repository"}; | ||||
| common_flags _common{cmd}; | common_flags _common{cmd}; | ||||
| repo_where_flag where{cmd}; | |||||
| repo_path_flag where{cmd}; | |||||
| args::Group repo_group{cmd, "Repo subcommands"}; | args::Group repo_group{cmd, "Repo subcommands"}; | ||||
| int run() { | int run() { | ||||
| auto list_contents = [&](dds::repository repo) { | auto list_contents = [&](dds::repository repo) { | ||||
| auto same_name | |||||
| = [](auto&& a, auto&& b) { return a.manifest.pk_id.name == b.manifest.pk_id.name; }; | |||||
| auto same_name = [](auto&& a, auto&& b) { | |||||
| return a.manifest.pk_id.name == b.manifest.pk_id.name; | |||||
| }; | |||||
| auto all = repo.iter_sdists(); | auto all = repo.iter_sdists(); | ||||
| auto grp_by_name = all // | auto grp_by_name = all // | ||||
| common_project_flags project{cmd}; | common_project_flags project{cmd}; | ||||
| repo_where_flag repo_where{cmd}; | |||||
| args::Flag force{cmd, | |||||
| repo_path_flag repo_where{cmd}; | |||||
| args::Flag force{cmd, | |||||
| "replace-if-exists", | "replace-if-exists", | ||||
| "Replace an existing export in the repository", | "Replace an existing export in the repository", | ||||
| {"replace"}}; | {"replace"}}; | ||||
| args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | ||||
| args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | ||||
| toolchain_flag tc_filepath{cmd}; | toolchain_flag tc_filepath{cmd}; | ||||
| args::Flag enable_warnings{cmd, | args::Flag enable_warnings{cmd, | ||||
| "enable_warnings", | "enable_warnings", | ||||
| "Enable compiler warnings", | "Enable compiler warnings", | ||||
| "Ensure we have local copies of the project dependencies"}; | "Ensure we have local copies of the project dependencies"}; | ||||
| common_flags _common{cmd}; | common_flags _common{cmd}; | ||||
| repo_where_flag repo_where{cmd}; | |||||
| path_flag remote_listing_file{ | |||||
| cmd, | |||||
| "remote-listing", | |||||
| "Path to a file containing listing of remote sdists and how to obtain them", | |||||
| {'R', "remote-list"}, | |||||
| "remote.dds"}; | |||||
| repo_path_flag repo_where{cmd}; | |||||
| catalog_path_flag catalog_path{cmd}; | |||||
| int run() { | int run() { | ||||
| auto man = parent.load_package_manifest(); | |||||
| auto rd = dds::remote_directory::load_from_file(remote_listing_file.Get()); | |||||
| bool failed = false; | |||||
| auto man = parent.load_package_manifest(); | |||||
| auto catalog = catalog_path.open(); | |||||
| bool failed = false; | |||||
| dds::repository::with_repository( // | dds::repository::with_repository( // | ||||
| repo_where.Get(), | repo_where.Get(), | ||||
| dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | ||||
| for (auto& dep : man.dependencies) { | for (auto& dep : man.dependencies) { | ||||
| auto exists = !!repo.find(dep.name, dep.version); | auto exists = !!repo.find(dep.name, dep.version); | ||||
| if (!exists) { | if (!exists) { | ||||
| spdlog::info("Pull remote: {} {}", dep.name, dep.version.to_string()); | |||||
| auto opt_remote = rd.find(dep.name, dep.version); | |||||
| if (opt_remote) { | |||||
| auto tsd = opt_remote->pull_sdist(); | |||||
| spdlog::info("Pull remote: {}@{}", dep.name, dep.version.to_string()); | |||||
| auto opt_pkg = catalog.get(dds::package_id{dep.name, dep.version}); | |||||
| if (opt_pkg) { | |||||
| auto tsd = dds::get_package_sdist(*opt_pkg); | |||||
| repo.add_sdist(tsd.sdist, dds::if_exists::ignore); | repo.add_sdist(tsd.sdist, dds::if_exists::ignore); | ||||
| } else { | } else { | ||||
| spdlog::error("No remote listing for {} {}", | |||||
| spdlog::error("No remote listing for {}@{}", | |||||
| dep.name, | dep.name, | ||||
| dep.version.to_string()); | dep.version.to_string()); | ||||
| failed = true; | failed = true; | ||||
| "If specified, will not generate an INDEX.lmi", | "If specified, will not generate an INDEX.lmi", | ||||
| {"skip-lmi"}}; | {"skip-lmi"}}; | ||||
| repo_where_flag repo_where{cmd}; | |||||
| repo_path_flag repo_where{cmd}; | |||||
| toolchain_flag tc_filepath{cmd}; | toolchain_flag tc_filepath{cmd}; | ||||
| spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v"); | spdlog::set_pattern("[%H:%M:%S] [%^%-5l%$] %v"); | ||||
| args::ArgumentParser parser("DDS - The drop-dead-simple library manager"); | args::ArgumentParser parser("DDS - The drop-dead-simple library manager"); | ||||
| cli_base cli{parser}; | |||||
| cli_build build{cli}; | |||||
| cli_sdist sdist{cli}; | |||||
| cli_repo repo{cli}; | |||||
| cli_deps deps{cli}; | |||||
| cli_base cli{parser}; | |||||
| cli_build build{cli}; | |||||
| cli_sdist sdist{cli}; | |||||
| cli_repo repo{cli}; | |||||
| cli_deps deps{cli}; | |||||
| cli_catalog catalog{cli}; | |||||
| try { | try { | ||||
| parser.ParseCLI(argc, argv); | parser.ParseCLI(argc, argv); | ||||
| } catch (const args::Help&) { | } catch (const args::Help&) { | ||||
| return repo.run(); | return repo.run(); | ||||
| } else if (deps.cmd) { | } else if (deps.cmd) { | ||||
| return deps.run(); | return deps.run(); | ||||
| } else if (catalog.cmd) { | |||||
| return catalog.run(); | |||||
| } else { | } else { | ||||
| assert(false); | assert(false); | ||||
| std::terminate(); | std::terminate(); |
| #include "./remote.hpp" | |||||
| #include <dds/deps.hpp> | |||||
| #include <dds/proc.hpp> | |||||
| #include <dds/repo/repo.hpp> | |||||
| #include <dds/sdist.hpp> | |||||
| #include <dds/temp.hpp> | |||||
| #include <dds/toolchain/toolchain.hpp> | |||||
| #include <dds/util/shlex.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| #include <libman/parse.hpp> | |||||
| #include <algorithm> | |||||
| using namespace dds; | |||||
| namespace { | |||||
| struct read_listing_item { | |||||
| std::string_view _key; | |||||
| std::set<remote_listing, remote_listing_compare_t>& out; | |||||
| bool operator()(std::string_view context, std::string_view key, std::string_view value) { | |||||
| if (key != _key) { | |||||
| return false; | |||||
| } | |||||
| auto nested = lm::nested_kvlist::parse(value); | |||||
| auto pk_id = package_id::parse(nested.primary); | |||||
| put_listing(context, std::move(pk_id), nested.pairs); | |||||
| return true; | |||||
| } | |||||
| void put_listing(std::string_view context, package_id pk_id, const lm::pair_list& pairs) { | |||||
| if (pairs.find("git")) { | |||||
| std::string url; | |||||
| std::string ref; | |||||
| std::optional<lm::usage> auto_id; | |||||
| lm::read(fmt::format("{}: Parsing Git remote listing", context), | |||||
| pairs, | |||||
| lm::read_required("url", url), | |||||
| lm::read_required("ref", ref), | |||||
| lm::read_check_eq("git", ""), | |||||
| lm::read_opt("auto", auto_id, &lm::split_usage_string), | |||||
| lm::reject_unknown()); | |||||
| auto did_insert = out.emplace(remote_listing{std::move(pk_id), | |||||
| git_remote_listing{url, ref, auto_id}}) | |||||
| .second; | |||||
| if (!did_insert) { | |||||
| spdlog::warn("Duplicate remote package defintion for {}", pk_id.to_string()); | |||||
| } | |||||
| } else { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Unable to determine remote type of package {}", pk_id.to_string())); | |||||
| } | |||||
| } | |||||
| }; | |||||
| temporary_sdist do_pull_sdist(const remote_listing& listing, const git_remote_listing& git) { | |||||
| auto tmpdir = dds::temporary_dir::create(); | |||||
| using namespace std::literals; | |||||
| spdlog::info("Cloning repository: {} [{}] ...", git.url, git.ref); | |||||
| auto command = {"git"s, | |||||
| "clone"s, | |||||
| "--depth=1"s, | |||||
| "--branch"s, | |||||
| git.ref, | |||||
| git.url, | |||||
| tmpdir.path().generic_string()}; | |||||
| auto git_res = run_proc(command); | |||||
| if (!git_res.okay()) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Git clone operation failed [Git command: {}] [Exitted {}]:\n{}", | |||||
| quote_command(command), | |||||
| git_res.retc, | |||||
| git_res.output)); | |||||
| } | |||||
| spdlog::info("Create sdist from clone ..."); | |||||
| if (git.auto_lib.has_value()) { | |||||
| spdlog::info("Generating library data automatically"); | |||||
| auto pkg_strm = dds::open(tmpdir.path() / "package.dds", std::ios::binary | std::ios::out); | |||||
| pkg_strm << "Name: " << listing.pk_id.name << '\n' // | |||||
| << "Version: " << listing.pk_id.version.to_string() << '\n' // | |||||
| << "Namespace: " << git.auto_lib->namespace_; | |||||
| auto lib_strm = dds::open(tmpdir.path() / "library.dds", std::ios::binary | std::ios::out); | |||||
| lib_strm << "Name: " << git.auto_lib->name; | |||||
| } | |||||
| sdist_params params; | |||||
| params.project_dir = tmpdir.path(); | |||||
| auto sd_tmp_dir = dds::temporary_dir::create(); | |||||
| params.dest_path = sd_tmp_dir.path(); | |||||
| params.force = true; | |||||
| auto sd = create_sdist(params); | |||||
| return {sd_tmp_dir, sd}; | |||||
| } | |||||
| } // namespace | |||||
| temporary_sdist remote_listing::pull_sdist() const { | |||||
| auto tsd = visit([&](auto&& actual) { return do_pull_sdist(*this, actual); }); | |||||
| if (!(tsd.sdist.manifest.pk_id == pk_id)) { | |||||
| throw std::runtime_error(fmt::format( | |||||
| "The package name@version in the generated sdist does not match the name listed in " | |||||
| "the remote listing file (expected '{}', but got '{}')", | |||||
| pk_id.to_string(), | |||||
| tsd.sdist.manifest.pk_id.to_string())); | |||||
| } | |||||
| return tsd; | |||||
| } | |||||
| remote_directory remote_directory::load_from_file(path_ref filepath) { | |||||
| auto kvs = lm::parse_file(filepath); | |||||
| listing_set listings; | |||||
| lm::read(fmt::format("Loading remote package listing from {}", filepath.string()), | |||||
| kvs, | |||||
| read_listing_item{"Remote-Package", listings}, | |||||
| lm::reject_unknown()); | |||||
| return {std::move(listings)}; | |||||
| } | |||||
| const remote_listing* remote_directory::find(std::string_view name, semver::version ver) const | |||||
| noexcept { | |||||
| auto found = _remotes.find(std::tie(name, ver)); | |||||
| if (found == _remotes.end()) { | |||||
| return nullptr; | |||||
| } | |||||
| return &*found; | |||||
| } | |||||
| void remote_directory::ensure_all_local(const repository&) const { | |||||
| spdlog::critical("Dependency download is not fully implemented!"); | |||||
| } |
| #pragma once | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <dds/sdist.hpp> | |||||
| #include <dds/temp.hpp> | |||||
| #include <libman/library.hpp> | |||||
| #include <semver/version.hpp> | |||||
| #include <set> | |||||
| #include <string> | |||||
| #include <tuple> | |||||
| #include <utility> | |||||
| #include <variant> | |||||
| namespace dds { | |||||
| struct temporary_sdist { | |||||
| temporary_dir tmpdir; | |||||
| dds::sdist sdist; | |||||
| }; | |||||
| struct git_remote_listing { | |||||
| std::string url; | |||||
| std::string ref; | |||||
| std::optional<lm::usage> auto_lib; | |||||
| void clone(path_ref path) const; | |||||
| }; | |||||
| struct remote_listing { | |||||
| package_id pk_id; | |||||
| std::variant<git_remote_listing> remote; | |||||
| template <typename Func> | |||||
| decltype(auto) visit(Func&& fn) const { | |||||
| return std::visit(std::forward<Func>(fn), remote); | |||||
| } | |||||
| temporary_sdist pull_sdist() const; | |||||
| }; | |||||
| inline constexpr struct remote_listing_compare_t { | |||||
| using is_transparent = int; | |||||
| bool operator()(const remote_listing& lhs, const remote_listing& rhs) const { | |||||
| return lhs.pk_id < rhs.pk_id; | |||||
| } | |||||
| template <typename Name, typename Version> | |||||
| bool operator()(const remote_listing& lhs, const std::tuple<Name, Version>& rhs) const { | |||||
| auto&& [name, ver] = rhs; | |||||
| return lhs.pk_id < package_id{name, ver}; | |||||
| } | |||||
| template <typename Name, typename Version> | |||||
| bool operator()(const std::tuple<Name, Version>& lhs, const remote_listing& rhs) const { | |||||
| auto&& [name, ver] = lhs; | |||||
| return package_id{name, ver} < rhs.pk_id; | |||||
| } | |||||
| } remote_listing_compare; | |||||
| class remote_directory { | |||||
| using listing_set = std::set<remote_listing, remote_listing_compare_t>; | |||||
| listing_set _remotes; | |||||
| remote_directory(listing_set s) | |||||
| : _remotes(std::move(s)) {} | |||||
| public: | |||||
| static remote_directory load_from_file(path_ref); | |||||
| void ensure_all_local(const class repository& repo) const; | |||||
| const remote_listing* find(std::string_view name, semver::version ver) const noexcept; | |||||
| }; | |||||
| } // namespace dds |
| from tests.fileutil import set_contents | from tests.fileutil import set_contents | ||||
| def test_lib_with_just_app(dds: DDS, scope: ExitStack): | |||||
| scope.enter_context( | |||||
| def test_lib_with_just_app(dds: DDS): | |||||
| dds.scope.enter_context( | |||||
| set_contents( | set_contents( | ||||
| dds.source_root / 'src/foo.main.cpp', | dds.source_root / 'src/foo.main.cpp', | ||||
| b'int main() {}', | b'int main() {}', |
| from tests import dds, DDS | |||||
| from tests.fileutil import ensure_dir | |||||
| def test_create_catalog(dds: DDS): | |||||
| dds.scope.enter_context(ensure_dir(dds.build_dir)) | |||||
| dds.catalog_create() | |||||
| assert dds.catalog_path.is_file() |
| import json | |||||
| from tests import dds, DDS | |||||
| from tests.fileutil import ensure_dir | |||||
| def test_get(dds: DDS): | |||||
| dds.scope.enter_context(ensure_dir(dds.build_dir)) | |||||
| dds.catalog_create() | |||||
| json_path = dds.build_dir / 'catalog.json' | |||||
| import_data = { | |||||
| 'version': 1, | |||||
| 'packages': { | |||||
| 'neo-sqlite3': { | |||||
| '0.2.2': { | |||||
| 'depends': {}, | |||||
| 'git': { | |||||
| 'url': 'https://github.com/vector-of-bool/neo-sqlite3.git', | |||||
| 'ref': '0.2.2', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| dds.scope.enter_context( | |||||
| dds.set_contents(json_path, | |||||
| json.dumps(import_data).encode())) | |||||
| dds.catalog_import(json_path) | |||||
| dds.catalog_get('neo-sqlite3@0.2.2') | |||||
| assert (dds.source_root / 'neo-sqlite3@0.2.2').is_dir() | |||||
| assert (dds.source_root / 'neo-sqlite3@0.2.2/package.dds').is_file() |
| import json | |||||
| from tests import dds, DDS | |||||
| from tests.fileutil import ensure_dir | |||||
| def test_import_json(dds: DDS): | |||||
| dds.scope.enter_context(ensure_dir(dds.build_dir)) | |||||
| dds.catalog_create() | |||||
| json_fpath = dds.build_dir / 'data.json' | |||||
| import_data = { | |||||
| 'version': 1, | |||||
| 'packages': { | |||||
| 'foo': { | |||||
| '1.2.4': { | |||||
| 'git': { | |||||
| 'url': 'http://example.com', | |||||
| 'ref': 'master', | |||||
| }, | |||||
| 'depends': {}, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| dds.scope.enter_context( | |||||
| dds.set_contents(json_fpath, | |||||
| json.dumps(import_data).encode())) | |||||
| dds.catalog_import(json_fpath) |
| def repo_dir(self) -> Path: | def repo_dir(self) -> Path: | ||||
| return self.scratch_dir / 'repo' | return self.scratch_dir / 'repo' | ||||
| @property | |||||
| def catalog_path(self) -> Path: | |||||
| return self.scratch_dir / 'catalog.db' | |||||
| @property | @property | ||||
| def deps_build_dir(self) -> Path: | def deps_build_dir(self) -> Path: | ||||
| return self.scratch_dir / 'deps-build' | return self.scratch_dir / 'deps-build' | ||||
| def run(self, cmd: proc.CommandLine, *, | def run(self, cmd: proc.CommandLine, *, | ||||
| cwd: Path = None) -> subprocess.CompletedProcess: | cwd: Path = None) -> subprocess.CompletedProcess: | ||||
| cmdline = list(proc.flatten_cmd(cmd)) | cmdline = list(proc.flatten_cmd(cmd)) | ||||
| res = self.run_unchecked(cmd) | |||||
| res = self.run_unchecked(cmd, cwd=cwd) | |||||
| if res.returncode != 0: | if res.returncode != 0: | ||||
| raise subprocess.CalledProcessError( | raise subprocess.CalledProcessError( | ||||
| res.returncode, [self.dds_exe] + cmdline, res.stdout) | res.returncode, [self.dds_exe] + cmdline, res.stdout) | ||||
| return self.run([ | return self.run([ | ||||
| 'deps', | 'deps', | ||||
| 'get', | 'get', | ||||
| f'--catalog={self.catalog_path}', | |||||
| self.repo_dir_arg, | self.repo_dir_arg, | ||||
| ]) | ]) | ||||
| f'We don\'t know the executable suffix for the platform "{os.name}"' | f'We don\'t know the executable suffix for the platform "{os.name}"' | ||||
| ) | ) | ||||
| def catalog_create(self) -> subprocess.CompletedProcess: | |||||
| self.scratch_dir.mkdir(parents=True, exist_ok=True) | |||||
| return self.run( | |||||
| ['catalog', 'create', f'--catalog={self.catalog_path}'], | |||||
| cwd=self.test_dir) | |||||
| def catalog_import(self, json_path: Path) -> subprocess.CompletedProcess: | |||||
| self.scratch_dir.mkdir(parents=True, exist_ok=True) | |||||
| return self.run([ | |||||
| 'catalog', | |||||
| 'import', | |||||
| f'--catalog={self.catalog_path}', | |||||
| f'--json={json_path}', | |||||
| ]) | |||||
| def catalog_get(self, req: str) -> subprocess.CompletedProcess: | |||||
| return self.run([ | |||||
| 'catalog', | |||||
| 'get', | |||||
| f'--catalog={self.catalog_path}', | |||||
| req, | |||||
| ]) | |||||
| def set_contents(self, path: Union[str, Path], | def set_contents(self, path: Union[str, Path], | ||||
| content: bytes) -> ContextManager[Path]: | content: bytes) -> ContextManager[Path]: | ||||
| return fileutil.set_contents(self.source_root / path, content) | return fileutil.set_contents(self.source_root / path, content) |
| @dds_conf | @dds_conf | ||||
| def test_deps_build(dds: DDS): | def test_deps_build(dds: DDS): | ||||
| dds.catalog_import(dds.source_root / 'catalog.json') | |||||
| assert not dds.repo_dir.exists() | assert not dds.repo_dir.exists() | ||||
| dds.deps_get() | dds.deps_get() | ||||
| assert dds.repo_dir.exists(), '`deps get` did not generate a repo directory' | assert dds.repo_dir.exists(), '`deps get` did not generate a repo directory' | ||||
| @dds_fixture_conf_1('use-remote') | @dds_fixture_conf_1('use-remote') | ||||
| def test_use_nlohmann_json_remote(dds: DDS): | def test_use_nlohmann_json_remote(dds: DDS): | ||||
| dds.catalog_import(dds.source_root / 'catalog.json') | |||||
| dds.deps_get() | dds.deps_get() | ||||
| dds.deps_build() | dds.deps_build() | ||||
| dds.build(apps=True) | dds.build(apps=True) |
| { | |||||
| "version": 1, | |||||
| "packages": { | |||||
| "neo-buffer": { | |||||
| "0.1.0": { | |||||
| "git": { | |||||
| "url": "https://github.com/vector-of-bool/neo-buffer.git", | |||||
| "ref": "develop" | |||||
| }, | |||||
| "depends": {} | |||||
| } | |||||
| }, | |||||
| "range-v3": { | |||||
| "0.9.1": { | |||||
| "git": { | |||||
| "url": "https://github.com/ericniebler/range-v3.git", | |||||
| "ref": "0.9.1", | |||||
| "auto-lib": "Niebler/range-v3" | |||||
| }, | |||||
| "depends": {} | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| Remote-Package: neo-buffer@0.1.0; git url=https://github.com/vector-of-bool/neo-buffer.git ref=develop | |||||
| Remote-Package: range-v3@0.9.1; git url=https://github.com/ericniebler/range-v3.git ref=0.9.1 auto=Niebler/range-v3 |
| { | |||||
| "version": 1, | |||||
| "packages": {} | |||||
| } |
| { | |||||
| "version": 1, | |||||
| "packages": { | |||||
| "nlohmann-json": { | |||||
| "3.7.1": { | |||||
| "git": { | |||||
| "url": "https://github.com/vector-of-bool/json.git", | |||||
| "ref": "dds/3.7.1" | |||||
| }, | |||||
| "depends": {} | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| Remote-Package: nlohmann-json@3.7.1; git url=https://github.com/vector-of-bool/json.git ref=dds/3.7.1 |
| { | |||||
| "version": 1, | |||||
| "packages": { | |||||
| "spdlog": { | |||||
| "1.4.2": { | |||||
| "git": { | |||||
| "url": "https://github.com/gabime/spdlog.git", | |||||
| "ref": "v1.4.2", | |||||
| "auto-lib": "spdlog/spdlog" | |||||
| }, | |||||
| "depends": {} | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| Remote-Package: spdlog@1.4.2; git url=https://github.com/gabime/spdlog.git ref=v1.4.2 auto=spdlog/spdlog |
| def test_get_build_use_spdlog(dds: DDS): | def test_get_build_use_spdlog(dds: DDS): | ||||
| dds.catalog_import(dds.source_root / 'catalog.json') | |||||
| dds.deps_get() | dds.deps_get() | ||||
| tc_fname = 'gcc.tc.dds' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.dds' | tc_fname = 'gcc.tc.dds' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.dds' | ||||
| tc = str(dds.test_dir / tc_fname) | tc = str(dds.test_dir / tc_fname) |