Browse Source

Update neo-sqlite3, generalize package remotes via URLs

This is a large changeset that changes the way we store package
remote info. In these changes, package remotes are entirely
encoded in a single URL. This will help reduce complexity
down the road when multiple different remote types are supported.

The kind of a remote is specified by the URL's scheme, and the
URL parsing differs based on the scheme. For now, only git+http
and git+https are supported.

This comes along with a change to the format of the catalog JSON.
Remote information is now entirely encoded in a URL string.
default_compile_flags
vector-of-bool 4 years ago
parent
commit
c160953106
31 changed files with 3695 additions and 4162 deletions
  1. +2403
    -3032
      catalog.json
  2. +845
    -772
      catalog.old.json
  3. +14
    -0
      docs/err/invalid-remote-url.rst
  4. +2
    -1
      library.jsonc
  5. +3
    -2
      package.jsonc
  6. +128
    -92
      src/dds/catalog/catalog.cpp
  7. +5
    -8
      src/dds/catalog/catalog.test.cpp
  8. +66
    -62
      src/dds/catalog/import.cpp
  9. +47
    -65
      src/dds/catalog/import.test.cpp
  10. +2
    -2
      src/dds/catalog/init_catalog.cpp
  11. +20
    -0
      src/dds/catalog/package_info.cpp
  12. +5
    -1
      src/dds/catalog/package_info.hpp
  13. +44
    -3
      src/dds/catalog/remote/git.cpp
  14. +2
    -0
      src/dds/catalog/remote/git.hpp
  15. +26
    -29
      src/dds/db/database.cpp
  16. +6
    -0
      src/dds/error/errors.cpp
  17. +1
    -0
      src/dds/error/errors.hpp
  18. +4
    -3
      src/dds/library/root.cpp
  19. +5
    -4
      src/dds/repo/repo.cpp
  20. +3
    -3
      src/dds/source/root.cpp
  21. +0
    -10
      src/dds/util/ranges.hpp
  22. +3
    -7
      tests/catalog/get_test.py
  23. +3
    -9
      tests/catalog/import_test.py
  24. +4
    -13
      tests/deps/build-deps/project/catalog.json
  25. +3
    -10
      tests/deps/git-remote/catalog.json
  26. +1
    -1
      tests/deps/no-deps/catalog.json
  27. +14
    -18
      tests/deps/use-cryptopp/project/catalog.json
  28. +2
    -5
      tests/deps/use-remote/catalog.json
  29. +2
    -6
      tests/deps/use-spdlog/project/catalog.json
  30. +1
    -1
      tools/ci.py
  31. +31
    -3
      tools/gen-catalog-json.py

+ 2403
- 3032
catalog.json
File diff suppressed because it is too large
View File


+ 845
- 772
catalog.old.json
File diff suppressed because it is too large
View File


+ 14
- 0
docs/err/invalid-remote-url.rst View File

Error: Invalid Remote/Package URL
#################################

``dds`` encodes a lot of information about remotes repositories and remote
packages in URLs. If you received this error, it may be because:

1. The URL syntax is invalid. Make sure that you have spelled it correctly.
2. The URL scheme (the part at the beginning, before the ``://``) is unsupported
by ``dds``. ``dds`` only supports a subset of possible URL schemes in
different contexts. Check the output carefully and read the documentation
about the task you are trying to solve.
3. There are missing URL components that the task is expecting. For example,
``git`` remote URLs require that the URL have a URL fragment specifying the
tag/branch to clone. (The fragment is the final ``#`` component.)

+ 2
- 1
library.jsonc View File

"microsoft/wil", "microsoft/wil",
"range-v3/range-v3", "range-v3/range-v3",
"nlohmann/json", "nlohmann/json",
"neo/sqlite3",
"neo/fun", "neo/fun",
"neo/sqlite3",
"vob/semver", "vob/semver",
"vob/pubgrub", "vob/pubgrub",
"vob/json5", "vob/json5",
"vob/semester", "vob/semester",
"hanickadot/ctre", "hanickadot/ctre",
// "neo/io", // "neo/io",
"neo/url",
// Explicit zlib link is required due to linker input order bug. // Explicit zlib link is required due to linker input order bug.
// Can be removed after alpha.5 // Can be removed after alpha.5
"zlib/zlib", "zlib/zlib",

+ 3
- 2
package.jsonc View File

"ms-wil@2020.3.16", "ms-wil@2020.3.16",
"range-v3@0.11.0", "range-v3@0.11.0",
"nlohmann-json@3.7.1", "nlohmann-json@3.7.1",
"neo-sqlite3@0.2.3",
"neo-fun^0.3.2",
"neo-sqlite3@0.4.1",
"neo-fun~0.5.3",
"neo-compress^0.1.0", "neo-compress^0.1.0",
"neo-url~0.1.2",
"semver@0.2.2", "semver@0.2.2",
"pubgrub@0.2.1", "pubgrub@0.2.1",
"vob-json5@0.1.5", "vob-json5@0.1.5",

+ 128
- 92
src/dds/catalog/catalog.cpp View File

#include <dds/error/errors.hpp> #include <dds/error/errors.hpp>
#include <dds/solve/solve.hpp> #include <dds/solve/solve.hpp>
#include <dds/util/log.hpp> #include <dds/util/log.hpp>
#include <dds/util/ranges.hpp>


#include <json5/parse_data.hpp> #include <json5/parse_data.hpp>
#include <neo/assert.hpp> #include <neo/assert.hpp>


using namespace dds; using namespace dds;


namespace sqlite3 = neo::sqlite3;
using namespace sqlite3::literals;
namespace nsql = neo::sqlite3;
using namespace neo::sqlite3::literals;


namespace { namespace {


void migrate_repodb_1(sqlite3::database& db) {
void migrate_repodb_1(nsql::database& db) {
db.exec(R"( db.exec(R"(
CREATE TABLE dds_cat_pkgs ( CREATE TABLE dds_cat_pkgs (
pkg_id INTEGER PRIMARY KEY AUTOINCREMENT, pkg_id INTEGER PRIMARY KEY AUTOINCREMENT,
)"); )");
} }


void migrate_repodb_2(sqlite3::database& db) {
void migrate_repodb_2(nsql::database& db) {
db.exec(R"( db.exec(R"(
ALTER TABLE dds_cat_pkgs ALTER TABLE dds_cat_pkgs
ADD COLUMN repo_transform TEXT NOT NULL DEFAULT '[]' ADD COLUMN repo_transform TEXT NOT NULL DEFAULT '[]'
)"); )");
} }


void migrate_repodb_3(nsql::database& db) {
db.exec(R"(
CREATE TABLE dds_cat_remotes (
remote_id INTEGER PRIMARY KEY AUTOINCREMENT,
ident TEXT NOT NULL UNIQUE,
gen_ident TEXT NOT NULL,
remote_url TEXT NOT NULL
);

CREATE TABLE dds_cat_pkgs_new (
pkg_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
version TEXT NOT NULL,
description TEXT NOT NULL,
remote_url TEXT NOT NULL,
remote_id INTEGER REFERENCES dds_cat_remotes DEFAULT NULL,
repo_transform TEXT NOT NULL DEFAULT '[]',
UNIQUE (name, version)
);

INSERT INTO dds_cat_pkgs_new(pkg_id,
name,
version,
description,
remote_url,
repo_transform)
SELECT pkg_id,
name,
version,
description,
'git+' || git_url || (
CASE
WHEN lm_name ISNULL THEN ''
ELSE ('?lm=' || lm_namespace || '/' || lm_name)
END
) || '#' || git_ref,
repo_transform
FROM dds_cat_pkgs;

CREATE TABLE dds_cat_pkg_deps_new (
dep_id INTEGER PRIMARY KEY AUTOINCREMENT,
pkg_id INTEGER NOT NULL REFERENCES dds_cat_pkgs_new(pkg_id),
dep_name TEXT NOT NULL,
low TEXT NOT NULL,
high TEXT NOT NULL,
UNIQUE(pkg_id, dep_name)
);

INSERT INTO dds_cat_pkg_deps_new SELECT * FROM dds_cat_pkg_deps;

DROP TABLE dds_cat_pkg_deps;
DROP TABLE dds_cat_pkgs;
ALTER TABLE dds_cat_pkgs_new RENAME TO dds_cat_pkgs;
ALTER TABLE dds_cat_pkg_deps_new RENAME TO dds_cat_pkg_deps;
)");
}

std::string transforms_to_json(const std::vector<fs_transformation>& trs) { std::string transforms_to_json(const std::vector<fs_transformation>& trs) {
std::string acc = "["; std::string acc = "[";
for (auto it = trs.begin(); it != trs.end(); ++it) { for (auto it = trs.begin(); it != trs.end(); ++it) {
const package_info& pkg, const package_info& pkg,
const git_remote_listing& git) { const git_remote_listing& git) {
auto lm_usage = git.auto_lib.value_or(lm::usage{}); auto lm_usage = git.auto_lib.value_or(lm::usage{});
sqlite3::exec( //
stmts,
R"(

std::string url = git.url;
if (url.starts_with("https://") || url.starts_with("http://")) {
url = "git+" + url;
}
if (git.auto_lib.has_value()) {
url += "?lm=" + git.auto_lib->namespace_ + "/" + git.auto_lib->name;
}
url += "#" + git.ref;

nsql::exec( //
stmts(R"(
INSERT OR REPLACE INTO dds_cat_pkgs ( INSERT OR REPLACE INTO dds_cat_pkgs (
name, name,
version, version,
git_url,
git_ref,
lm_name,
lm_namespace,
remote_url,
description, description,
repo_transform repo_transform
) VALUES ( ) VALUES (
?2, ?2,
?3, ?3,
?4, ?4,
CASE WHEN ?5 = '' THEN NULL ELSE ?5 END,
CASE WHEN ?6 = '' THEN NULL ELSE ?6 END,
?7,
?8
?5
) )
)"_sql,
std::forward_as_tuple( //
pkg.ident.name,
pkg.ident.version.to_string(),
git.url,
git.ref,
lm_usage.name,
lm_usage.namespace_,
pkg.description,
transforms_to_json(git.transforms)));
)"_sql),
pkg.ident.name,
pkg.ident.version.to_string(),
url,
pkg.description,
transforms_to_json(git.transforms));
} }


void do_store_pkg(neo::sqlite3::database& db, void do_store_pkg(neo::sqlite3::database& db,
assert(dep.versions.num_intervals() == 1); assert(dep.versions.num_intervals() == 1);
auto iv_1 = *dep.versions.iter_intervals().begin(); auto iv_1 = *dep.versions.iter_intervals().begin();
dds_log(trace, " Depends on: {}", dep.to_string()); dds_log(trace, " Depends on: {}", dep.to_string());
sqlite3::exec(new_dep_st,
std::forward_as_tuple(db_pkg_id,
dep.name,
iv_1.low.to_string(),
iv_1.high.to_string()));
nsql::exec(new_dep_st, db_pkg_id, dep.name, iv_1.low.to_string(), iv_1.high.to_string());
} }
} }


void store_init_packages(sqlite3::database& db, sqlite3::statement_cache& st_cache) {
void store_init_packages(nsql::database& db, nsql::statement_cache& st_cache) {
dds_log(debug, "Restoring initial package data"); dds_log(debug, "Restoring initial package data");
for (auto& pkg : init_catalog_packages()) { for (auto& pkg : init_catalog_packages()) {
do_store_pkg(db, st_cache, pkg); do_store_pkg(db, st_cache, pkg);
} }
} }


void ensure_migrated(sqlite3::database& db) {
sqlite3::transaction_guard tr{db};
void ensure_migrated(nsql::database& db) {
nsql::transaction_guard tr{db};
db.exec(R"( db.exec(R"(
PRAGMA foreign_keys = 1; PRAGMA foreign_keys = 1;
CREATE TABLE IF NOT EXISTS dds_cat_meta AS CREATE TABLE IF NOT EXISTS dds_cat_meta AS
SELECT * FROM init; SELECT * FROM init;
)"); )");
auto meta_st = db.prepare("SELECT meta FROM dds_cat_meta"); auto meta_st = db.prepare("SELECT meta FROM dds_cat_meta");
auto [meta_json] = sqlite3::unpack_single<std::string>(meta_st);
auto [meta_json] = nsql::unpack_single<std::string>(meta_st);


auto meta = nlohmann::json::parse(meta_json); auto meta = nlohmann::json::parse(meta_json);
if (!meta.is_object()) { if (!meta.is_object()) {
"The catalog database metadata is invalid [bad dds_meta.version]"); "The catalog database metadata is invalid [bad dds_meta.version]");
} }


constexpr int current_database_version = 2;
constexpr int current_database_version = 3;


int version = version_; int version = version_;


dds_log(debug, "Applying catalog migration 2"); dds_log(debug, "Applying catalog migration 2");
migrate_repodb_2(db); migrate_repodb_2(db);
} }
meta["version"] = 2;
exec(db, "UPDATE dds_cat_meta SET meta=?", std::forward_as_tuple(meta.dump()));
if (version < 3) {
dds_log(debug, "Applying catalog migration 3");
migrate_repodb_3(db);
}
meta["version"] = current_database_version;
exec(db.prepare("UPDATE dds_cat_meta SET meta=?"), meta.dump());


if (import_init_packages) { if (import_init_packages) {
dds_log( dds_log(
fs::create_directories(pardir); fs::create_directories(pardir);
} }
dds_log(debug, "Opening package catalog [{}]", db_path); dds_log(debug, "Opening package catalog [{}]", db_path);
auto db = sqlite3::database::open(db_path);
auto db = nsql::database::open(db_path);
try { try {
ensure_migrated(db); ensure_migrated(db);
} catch (const sqlite3::sqlite3_error& e) {
} catch (const nsql::sqlite3_error& e) {
dds_log(critical, dds_log(critical,
"Failed to load the repository database. It appears to be invalid/corrupted. The " "Failed to load the repository database. It appears to be invalid/corrupted. The "
"exception message is: {}", "exception message is: {}",
return catalog(std::move(db)); return catalog(std::move(db));
} }


catalog::catalog(sqlite3::database db)
catalog::catalog(nsql::database db)
: _db(std::move(db)) {} : _db(std::move(db)) {}


void catalog::store(const package_info& pkg) { void catalog::store(const package_info& pkg) {
sqlite3::transaction_guard tr{_db};
nsql::transaction_guard tr{_db};
do_store_pkg(_db, _stmt_cache, pkg); do_store_pkg(_db, _stmt_cache, pkg);
} }


pkg_id, pkg_id,
name, name,
version, version,
git_url,
git_ref,
lm_name,
lm_namespace,
remote_url,
description, description,
repo_transform repo_transform
FROM dds_cat_pkgs FROM dds_cat_pkgs
WHERE name = ? AND version = ? WHERE name = ? AND version = ?
)"_sql); )"_sql);
st.reset(); st.reset();
st.bindings = std::forward_as_tuple(pk_id.name, ver_str);
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>,
std::string,
std::string>(st);
st.bindings() = std::forward_as_tuple(pk_id.name, ver_str);
auto opt_tup = nsql::unpack_single_opt<std::int64_t,
std::string,
std::string,
std::string,
std::string,
std::string>(st);
if (!opt_tup) { if (!opt_tup) {
dym_target::fill([&] { dym_target::fill([&] {
auto all_ids = this->all(); auto all_ids = this->all();
}); });
return std::nullopt; return std::nullopt;
} }
const auto& [pkg_id,
name,
version,
git_url,
git_ref,
lm_name,
lm_namespace,
description,
repo_transform]
= *opt_tup;
const auto& [pkg_id, name, version, remote_url, description, repo_transform] = *opt_tup;
assert(pk_id.name == name); assert(pk_id.name == name);
assert(pk_id.version == semver::version::parse(version)); assert(pk_id.version == semver::version::parse(version));
assert(git_url);
assert(git_ref);


auto deps = dependencies_of(pk_id); auto deps = dependencies_of(pk_id);


pk_id, pk_id,
std::move(deps), std::move(deps),
std::move(description), std::move(description),
git_remote_listing{
*git_url,
*git_ref,
lm_name ? std::make_optional(lm::usage{*lm_namespace, *lm_name}) : std::nullopt,
{},
},
parse_remote_url(remote_url),
}; };


if (!repo_transform.empty()) { if (!repo_transform.empty()) {
}; };


std::vector<package_id> catalog::all() const noexcept { std::vector<package_id> catalog::all() const noexcept {
return view_safe(sqlite3::exec_iter<std::string, std::string>( //
_stmt_cache,
"SELECT name, version FROM dds_cat_pkgs"_sql))
return nsql::exec_tuples<std::string, std::string>(
_stmt_cache("SELECT name, version FROM dds_cat_pkgs"_sql))
| neo::lref //
| ranges::views::transform(pair_to_pkg_id) // | ranges::views::transform(pair_to_pkg_id) //
| ranges::to_vector; | ranges::to_vector;
} }


std::vector<package_id> catalog::by_name(std::string_view sv) const noexcept { std::vector<package_id> catalog::by_name(std::string_view sv) const noexcept {
return view_safe(sqlite3::exec_iter<std::string, std::string>( //
_stmt_cache,
R"(
return nsql::exec_tuples<std::string, std::string>( //
_stmt_cache(
R"(
SELECT name, version SELECT name, version
FROM dds_cat_pkgs FROM dds_cat_pkgs
WHERE name = ? WHERE name = ?
)"_sql,
std::tie(sv))) //
)"_sql),
sv) //
| neo::lref //
| ranges::views::transform(pair_to_pkg_id) // | ranges::views::transform(pair_to_pkg_id) //
| ranges::to_vector; | ranges::to_vector;
} }


std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const noexcept { std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const noexcept {
dds_log(trace, "Lookup dependencies of {}@{}", pkg.name, pkg.version.to_string()); dds_log(trace, "Lookup dependencies of {}@{}", pkg.name, pkg.version.to_string());
return view_safe(sqlite3::exec_iter<std::string,
std::string,
std::string>( //
_stmt_cache,
R"(
return nsql::exec_tuples<std::string,
std::string,
std::string>( //
_stmt_cache(
R"(
WITH this_pkg_id AS ( WITH this_pkg_id AS (
SELECT pkg_id SELECT pkg_id
FROM dds_cat_pkgs FROM dds_cat_pkgs
FROM dds_cat_pkg_deps FROM dds_cat_pkg_deps
WHERE pkg_id IN this_pkg_id WHERE pkg_id IN this_pkg_id
ORDER BY dep_name ORDER BY dep_name
)"_sql,
std::forward_as_tuple(pkg.name, pkg.version.to_string()))) //
)"_sql),
pkg.name,
pkg.version.to_string()) //
| neo::lref //
| ranges::views::transform([](auto&& pair) { | ranges::views::transform([](auto&& pair) {
auto& [name, low, high] = pair; auto& [name, low, high] = pair;
auto dep auto dep
dds_log(trace, "Importing JSON string into catalog"); dds_log(trace, "Importing JSON string into catalog");
auto pkgs = parse_packages_json(content); auto pkgs = parse_packages_json(content);


sqlite3::transaction_guard tr{_db};
nsql::transaction_guard tr{_db};
for (const auto& pkg : pkgs) { for (const auto& pkg : pkgs) {
store(pkg);
do_store_pkg(_db, _stmt_cache, pkg);
} }
} }


void catalog::import_initial() { void catalog::import_initial() {
sqlite3::transaction_guard tr{_db};
nsql::transaction_guard tr{_db};
dds_log(info, "Restoring built-in initial catalog contents"); dds_log(info, "Restoring built-in initial catalog contents");
store_init_packages(_db, _stmt_cache); store_init_packages(_db, _stmt_cache);
} }

+ 5
- 8
src/dds/catalog/catalog.test.cpp View File

dds::package_id("foo", semver::version::parse("1.2.3")), dds::package_id("foo", semver::version::parse("1.2.3")),
{}, {},
"example", "example",
dds::git_remote_listing{"http://example.com", "master", std::nullopt, {}},
dds::git_remote_listing{"git+http://example.com", "master", std::nullopt, {}},
}); });


auto pkgs = db.by_name("foo"); auto pkgs = db.by_name("foo");
dds::package_id("foo", semver::version::parse("1.2.3")), dds::package_id("foo", semver::version::parse("1.2.3")),
{}, {},
"example", "example",
dds::git_remote_listing{"http://example.com", "develop", std::nullopt, {}},
dds::git_remote_listing{"git+http://example.com", "develop", std::nullopt, {}},
})); }));
// The previous pkg_id is still a valid lookup key // The previous pkg_id is still a valid lookup key
info = db.get(pkgs[0]); info = db.get(pkgs[0]);
{"baz", {semver::version::parse("5.3.0"), semver::version::parse("6.0.0")}}, {"baz", {semver::version::parse("5.3.0"), semver::version::parse("6.0.0")}},
}, },
"example", "example",
dds::git_remote_listing{"http://example.com", "master", std::nullopt, {}},
dds::git_remote_listing{"git+http://example.com", "master", std::nullopt, {}},
}); });
auto pkgs = db.by_name("foo"); auto pkgs = db.by_name("foo");
REQUIRE(pkgs.size() == 1); REQUIRE(pkgs.size() == 1);


TEST_CASE_METHOD(catalog_test_case, "Parse JSON repo") { TEST_CASE_METHOD(catalog_test_case, "Parse JSON repo") {
db.import_json_str(R"({ db.import_json_str(R"({
"version": 1,
"version": 2,
"packages": { "packages": {
"foo": { "foo": {
"1.2.3": { "1.2.3": {
"depends": [ "depends": [
"bar~4.2.1" "bar~4.2.1"
], ],
"git": {
"url": "http://example.com",
"ref": "master"
}
url: "git+http://example.com#master"
} }
} }
} }

+ 66
- 62
src/dds/catalog/import.cpp View File

#include <fmt/core.h> #include <fmt/core.h>
#include <json5/parse_data.hpp> #include <json5/parse_data.hpp>
#include <neo/assert.hpp> #include <neo/assert.hpp>
#include <neo/url.hpp>
#include <semester/walk.hpp> #include <semester/walk.hpp>


#include <optional> #include <optional>
throw_user_error<dds::errc::invalid_catalog_json>(NEO_FWD(args)...); throw_user_error<dds::errc::invalid_catalog_json>(NEO_FWD(args)...);
} }


git_remote_listing parse_git_remote(const json5::data& data) {
git_remote_listing git;
auto make_dep = [](std::string const& str) {
using namespace semester::walk_ops;
try {
return dependency::parse_depends_string(str);
} catch (std::runtime_error const& e) {
import_error(std::string(walk.path()) + e.what());
}
};


auto convert_version_str = [](std::string_view str) {
using namespace semester::walk_ops; using namespace semester::walk_ops;
try {
return semver::version::parse(str);
} catch (const semver::invalid_version& e) {
import_error("{}: version string '{}' is invalid: {}", walk.path(), str, e.what());
}
};


walk(data,
require_obj{"Git remote should be an object"},
mapping{required_key{"url",
"A git 'url' string is required",
require_str("Git URL should be a string"),
put_into(git.url)},
required_key{"ref",
"A git 'ref' is required, and must be a tag or branch name",
require_str("Git ref should be a string"),
put_into(git.ref)},
if_key{"auto-lib",
require_str("'auto-lib' should be a string"),
put_into(git.auto_lib,
[](std::string const& str) {
try {
return lm::split_usage_string(str);
} catch (const std::runtime_error& e) {
import_error("{}: {}", walk.path(), e.what());
}
})},
if_key{"transform",
require_array{"Expect an array of transforms"},
for_each{put_into(std::back_inserter(git.transforms), [](auto&& dat) {
try {
return fs_transformation::from_json(dat);
} catch (const semester::walk_error& e) {
import_error(e.what());
}
})}}});

return git;
}
auto parse_remote = [](const std::string& str) {
using namespace semester::walk_ops;
try {
return parse_remote_url(str);
} catch (const neo::url_validation_error& e) {
import_error("{}: Invalid URL: {}", walk.path(), str);
} catch (const user_error<errc::invalid_remote_url>& e) {
import_error("{}: Invalid URL: {}", walk.path(), e.what());
}
};

auto parse_fs_transforms = [](auto&& tr_vec) {
using namespace semester::walk_ops;
return walk_seq{
require_array{"Expect an array of transforms"},
for_each{
put_into(std::back_inserter(tr_vec),
[&](auto&& dat) {
try {
return fs_transformation::from_json(dat);
} catch (const semester::walk_error& e) {
import_error(e.what());
}
}),
},
};
};


package_info package_info
parse_pkg_json_v1(std::string_view name, semver::version version, const json5::data& data) {
parse_pkg_json_v2(std::string_view name, semver::version version, const json5::data& data) {
package_info ret; package_info ret;
ret.ident = package_id{std::string{name}, version}; ret.ident = package_id{std::string{name}, version};
std::vector<fs_transformation> fs_trs;


using namespace semester::walk_ops; using namespace semester::walk_ops;


auto make_dep = [&](std::string const& str) {
try {
return dependency::parse_depends_string(str);
} catch (std::runtime_error const& e) {
import_error(std::string(walk.path()) + e.what());
}
};

auto check_one_remote = [&](auto&&) { auto check_one_remote = [&](auto&&) {
if (!semester::holds_alternative<std::monostate>(ret.remote)) { if (!semester::holds_alternative<std::monostate>(ret.remote)) {
return walk.reject("Cannot specify multiple remotes for a package"); return walk.reject("Cannot specify multiple remotes for a package");
for_each{require_str{"Each dependency should be a string"}, for_each{require_str{"Each dependency should be a string"},
put_into{std::back_inserter(ret.deps), make_dep}}}, put_into{std::back_inserter(ret.deps), make_dep}}},
if_key{ if_key{
"git",
"url",
require_str{"Remote URL should be a string"},
check_one_remote, check_one_remote,
put_into(ret.remote, parse_git_remote),
}});
put_into(ret.remote, parse_remote),
},
if_key{"transform", parse_fs_transforms(fs_trs)}});


if (semester::holds_alternative<std::monostate>(ret.remote)) { if (semester::holds_alternative<std::monostate>(ret.remote)) {
import_error("{}: Package listing for {} does not have any remote information", import_error("{}: Package listing for {} does not have any remote information",
ret.ident.to_string()); ret.ident.to_string());
} }


if (semester::holds_alternative<git_remote_listing>(ret.remote)) {
semester::get<git_remote_listing>(ret.remote).transforms = std::move(fs_trs);
} else {
if (!fs_trs.empty()) {
throw_user_error<errc::invalid_catalog_json>(
"{}: Filesystem transforms are not supported for this remote type", walk.path());
}
}

return ret; return ret;
} }


std::vector<package_info> parse_json_v1(const json5::data& data) {
std::vector<package_info> parse_json_v2(const json5::data& data) {
std::vector<package_info> acc_pkgs; std::vector<package_info> acc_pkgs;


std::string pkg_name; std::string pkg_name;
using namespace semester::walk_ops; using namespace semester::walk_ops;


auto convert_pkg_obj auto convert_pkg_obj
= [&](auto&& dat) { return parse_pkg_json_v1(pkg_name, pkg_version, dat); };

auto convert_version_str = [&](std::string_view str) {
try {
return semver::version::parse(str);
} catch (const semver::invalid_version& e) {
throw_user_error<errc::invalid_catalog_json>("{}: version string '{}' is invalid: {}",
walk.path(),
pkg_name,
str,
e.what());
}
};
= [&](auto&& dat) { return parse_pkg_json_v2(pkg_name, pkg_version, dat); };


auto import_pkg_versions auto import_pkg_versions
= walk_seq{require_obj{"Package entries must be JSON objects"}, = walk_seq{require_obj{"Package entries must be JSON objects"},


try { try {
if (version == 1.0) { if (version == 1.0) {
dds_log(trace, "Processing JSON data as v1 data");
return parse_json_v1(data);
throw_user_error<errc::invalid_catalog_json>(
"Support for catalog JSON v1 has been removed");
} else if (version == 2.0) {
dds_log(trace, "Processing JSON data as v2 data");
return parse_json_v2(data);
} else { } else {
throw_user_error<errc::invalid_catalog_json>("Unknown catalog JSON version '{}'", throw_user_error<errc::invalid_catalog_json>("Unknown catalog JSON version '{}'",
version); version);

+ 47
- 65
src/dds/catalog/import.test.cpp View File



TEST_CASE("An empty import is okay") { TEST_CASE("An empty import is okay") {
// An empty JSON with no packages in it // An empty JSON with no packages in it
auto pkgs = dds::parse_packages_json("{version: 1, packages: {}}");
auto pkgs = dds::parse_packages_json("{version: 2, packages: {}}");
CHECK(pkgs.empty()); CHECK(pkgs.empty());
} }


// Missing keys // Missing keys
"{}", "{}",
// Missing "packages" // Missing "packages"
"{version: 1}",
"{version: 2}",
// Bad version // Bad version
"{version: 1.7, packages: {}}",
"{version: 2.7, packages: {}}",
"{version: [], packages: {}}", "{version: [], packages: {}}",
"{version: null, packages: {}}", "{version: null, packages: {}}",
// 'packages' should be an object // 'packages' should be an object
"{version: 1, packages: []}",
"{version: 1, packages: null}",
"{version: 1, packages: 4}",
"{version: 1, packages: 'lol'}",
"{version: 2, packages: []}",
"{version: 2, packages: null}",
"{version: 2, packages: 4}",
"{version: 2, packages: 'lol'}",
// Objects in 'packages' should be objects // Objects in 'packages' should be objects
"{version:1, packages:{foo:null}}",
"{version:1, packages:{foo:[]}}",
"{version:1, packages:{foo:9}}",
"{version:1, packages:{foo:'lol'}}",
"{version:2, packages:{foo:null}}",
"{version:2, packages:{foo:[]}}",
"{version:2, packages:{foo:9}}",
"{version:2, packages:{foo:'lol'}}",
// Objects in 'packages' shuold have version strings // Objects in 'packages' shuold have version strings
"{version:1, packages:{foo:{'lol':{}}}}",
"{version:1, packages:{foo:{'1.2':{}}}}",
"{version:2, packages:{foo:{'lol':{}}}}",
"{version:2, packages:{foo:{'1.2':{}}}}",
// No remote // No remote
"{version:1, packages:{foo:{'1.2.3':{}}}}",
// Bad empty git
"{version:1, packages:{foo:{'1.2.3':{git:{}}}}}",
// Git `url` and `ref` should be a string
"{version:1, packages:{foo:{'1.2.3':{git:{url:2, ref:''}}}}}",
"{version:1, packages:{foo:{'1.2.3':{git:{url:'', ref:2}}}}}",
"{version:2, packages:{foo:{'1.2.3':{}}}}",
// Bad empty URL
"{version:2, packages:{foo:{'1.2.3':{url: ''}}}}",
// Git URL must have a fragment
"{version:2, packages:{foo:{'1.2.3':{url:'git+http://example.com'}}}}",
// 'auto-lib' should be a usage string // 'auto-lib' should be a usage string
"{version:1, packages:{foo:{'1.2.3':{git:{url:'', ref:'', 'auto-lib':3}}}}}",
"{version:1, packages:{foo:{'1.2.3':{git:{url:'', ref:'', 'auto-lib':'ffasdf'}}}}}",
"{version:2, packages:{foo:{'1.2.3':{url:'git+http://example.com?lm=lol#1.0}}}}",
// 'transform' should be an array // 'transform' should be an array
R"( R"(
{ {
version: 1,
version: 2,
packages: {foo: {'1.2.3': { packages: {foo: {'1.2.3': {
git: {
url: '',
ref: '',
'auto-lib': 'a/b',
transform: 'lol hi',
}
url: 'git+http://example.com#master,
transform: 'lol hi'
}}} }}}
} }
)", )",


std::string_view goods[] = { std::string_view goods[] = {
// Basic empty: // Basic empty:
"{version:1, packages:{}}",
"{version:2, packages:{}}",
// No versions for 'foo' is weird, but okay // No versions for 'foo' is weird, but okay
"{version:1, packages:{foo:{}}}",
"{version:2, packages:{foo:{}}}",
// Basic package with minimum info: // Basic package with minimum info:
"{version:1, packages:{foo:{'1.2.3':{git:{url:'', ref:''}}}}}",
"{version:2, packages:{foo:{'1.2.3':{url: 'git+http://example.com#master'}}}}",
// Minimal auto-lib: // Minimal auto-lib:
"{version:1, packages:{foo:{'1.2.3':{git:{url:'', ref:'', 'auto-lib':'a/b'}}}}}",
"{version:2, packages:{foo:{'1.2.3':{url: 'git+http://example.com?lm=a/b#master'}}}}",
// Empty transforms: // Empty transforms:
R"( R"(
{ {
version: 1,
version: 2,
packages: {foo: {'1.2.3': { packages: {foo: {'1.2.3': {
git: {
url: '',
ref: '',
'auto-lib': 'a/b',
transform: [],
}
url: 'git+http://example.com#master',
transform: [],
}}} }}}
} }
)", )",
// Basic transform: // Basic transform:
R"( R"(
{ {
version: 1,
version: 2,
packages: {foo: {'1.2.3': { packages: {foo: {'1.2.3': {
git: {
url: '',
ref: '',
'auto-lib': 'a/b',
transform: [{
copy: {
from: 'here',
to: 'there',
include: [
"*.c",
"*.cpp",
"*.h",
'*.txt'
]
}
}],
}
url: 'git+http://example.com#master',
transform: [{
copy: {
from: 'here',
to: 'there',
include: [
"*.c",
"*.cpp",
"*.h",
'*.txt'
]
}
}],
}}} }}}
} }
)", )",
TEST_CASE("Check a single object") { TEST_CASE("Check a single object") {
// An empty JSON with no packages in it // An empty JSON with no packages in it
auto pkgs = dds::parse_packages_json(R"({ auto pkgs = dds::parse_packages_json(R"({
version: 1,
version: 2,
packages: { packages: {
foo: { foo: {
'1.2.3': { '1.2.3': {
git: {
url: 'foo',
ref: 'fasdf',
'auto-lib': 'a/b',
}
url: 'git+http://example.com?lm=a/b#master',
} }
} }
} }
CHECK(std::holds_alternative<dds::git_remote_listing>(pkgs[0].remote)); CHECK(std::holds_alternative<dds::git_remote_listing>(pkgs[0].remote));


auto git = std::get<dds::git_remote_listing>(pkgs[0].remote); auto git = std::get<dds::git_remote_listing>(pkgs[0].remote);
CHECK(git.url == "foo");
CHECK(git.ref == "fasdf");
CHECK(git.url == "http://example.com");
CHECK(git.ref == "master");
REQUIRE(git.auto_lib); REQUIRE(git.auto_lib);
CHECK(git.auto_lib->namespace_ == "a"); CHECK(git.auto_lib->namespace_ == "a");
CHECK(git.auto_lib->name == "b"); CHECK(git.auto_lib->name == "b");

+ 2
- 2
src/dds/catalog/init_catalog.cpp
File diff suppressed because it is too large
View File


+ 20
- 0
src/dds/catalog/package_info.cpp View File

#include "./package_info.hpp"

#include <dds/error/errors.hpp>

#include <neo/url.hpp>

using namespace dds;

dds::remote_listing_var dds::parse_remote_url(std::string_view sv) {
auto url = neo::url::parse(sv);
if (url.scheme == "git+https" || url.scheme == "git+http" || url.scheme == "https+git"
|| url.scheme == "http+git" || url.scheme == "git") {
return git_remote_listing::from_url(sv);
} else {
throw_user_error<
errc::invalid_remote_url>("Unknown scheme '{}' for remote package URL '{}'",
url.scheme,
sv);
}
}

+ 5
- 1
src/dds/catalog/package_info.hpp View File



namespace dds { namespace dds {


using remote_listing_var = std::variant<std::monostate, git_remote_listing>;

remote_listing_var parse_remote_url(std::string_view url);

struct package_info { struct package_info {
package_id ident; package_id ident;
std::vector<dependency> deps; std::vector<dependency> deps;
std::string description; std::string description;


std::variant<std::monostate, git_remote_listing> remote;
remote_listing_var remote;
}; };


} // namespace dds } // namespace dds

+ 44
- 3
src/dds/catalog/remote/git.cpp View File

#include <dds/proc.hpp> #include <dds/proc.hpp>
#include <dds/util/log.hpp> #include <dds/util/log.hpp>


#include <neo/url.hpp>
#include <neo/url/query.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>


void dds::git_remote_listing::pull_to(const dds::package_id& pid, dds::path_ref dest) const {
using namespace dds;

void git_remote_listing::pull_to(const package_id& pid, path_ref dest) const {
fs::remove_all(dest); fs::remove_all(dest);
using namespace std::literals; using namespace std::literals;
dds_log(info, "Clone Git repository [{}] (at {}) to [{}]", url, ref, dest.string()); dds_log(info, "Clone Git repository [{}] (at {}) to [{}]", url, ref, dest.string());
if (auto_lib.has_value()) { if (auto_lib.has_value()) {
dds_log(info, "Generating library data automatically"); dds_log(info, "Generating library data automatically");


auto pkg_strm = dds::open(dest / "package.json5", std::ios::binary | std::ios::out);
auto pkg_strm = open(dest / "package.json5", std::ios::binary | std::ios::out);
auto man_json = nlohmann::json::object(); auto man_json = nlohmann::json::object();
man_json["name"] = pid.name; man_json["name"] = pid.name;
man_json["version"] = pid.version.to_string(); man_json["version"] = pid.version.to_string();
man_json["namespace"] = auto_lib->namespace_; man_json["namespace"] = auto_lib->namespace_;
pkg_strm << nlohmann::to_string(man_json); pkg_strm << nlohmann::to_string(man_json);


auto lib_strm = dds::open(dest / "library.json5", std::ios::binary | std::ios::out);
auto lib_strm = open(dest / "library.json5", std::ios::binary | std::ios::out);
auto lib_json = nlohmann::json::object(); auto lib_json = nlohmann::json::object();
lib_json["name"] = auto_lib->name; lib_json["name"] = auto_lib->name;
lib_strm << nlohmann::to_string(lib_json); lib_strm << nlohmann::to_string(lib_json);
} }
} }

git_remote_listing git_remote_listing::from_url(std::string_view sv) {
auto url = neo::url::parse(sv);
dds_log(trace, "Create Git remote listing from URL '{}'", sv);

auto ref = url.fragment;
url.fragment = {};
auto q = url.query;
url.query = {};

std::optional<lm::usage> auto_lib;

if (url.scheme.starts_with("git+")) {
url.scheme = url.scheme.substr(4);
} else if (url.scheme.ends_with("+git")) {
url.scheme = url.scheme.substr(0, url.scheme.size() - 4);
} else {
// Leave the URL as-is
}

if (q) {
neo::basic_query_string_view qsv{*q};
for (auto qstr : qsv) {
if (qstr.key_raw() != "lm") {
dds_log(warn, "Unknown query string parameter in package url: '{}'", qstr.string());
} else {
auto_lib = lm::split_usage_string(qstr.value_decoded());
}
}
}

if (!ref) {
throw_user_error<errc::invalid_remote_url>(
"Git URL requires a fragment specifying the Git ref to clone");
}
return {.url = url.to_string(), .ref = *ref, .auto_lib = auto_lib, .transforms = {}};
}

+ 2
- 0
src/dds/catalog/remote/git.hpp View File

std::vector<fs_transformation> transforms; std::vector<fs_transformation> transforms;


void pull_to(const package_id& pid, path_ref path) const; void pull_to(const package_id& pid, path_ref path) const;

static git_remote_listing from_url(std::string_view sv);
}; };


} // namespace dds } // namespace dds

+ 26
- 29
src/dds/db/database.cpp View File



using namespace dds; using namespace dds;


namespace sqlite3 = neo::sqlite3;
using sqlite3::exec;
using namespace sqlite3::literals;
namespace nsql = neo::sqlite3;
using nsql::exec;
using namespace nsql::literals;


namespace { namespace {


void migrate_1(sqlite3::database& db) {
void migrate_1(nsql::database& db) {
db.exec(R"( db.exec(R"(
CREATE TABLE dds_files ( CREATE TABLE dds_files (
file_id INTEGER PRIMARY KEY, file_id INTEGER PRIMARY KEY,
)"); )");
} }


void ensure_migrated(sqlite3::database& db) {
sqlite3::transaction_guard tr{db};
void ensure_migrated(nsql::database& db) {
nsql::transaction_guard tr{db};
db.exec(R"( db.exec(R"(
PRAGMA foreign_keys = 1; PRAGMA foreign_keys = 1;
CREATE TABLE IF NOT EXISTS dds_meta AS CREATE TABLE IF NOT EXISTS dds_meta AS
SELECT * FROM init; SELECT * FROM init;
)"); )");
auto meta_st = db.prepare("SELECT meta FROM dds_meta"); auto meta_st = db.prepare("SELECT meta FROM dds_meta");
auto [meta_json] = sqlite3::unpack_single<std::string>(meta_st);
auto [meta_json] = nsql::unpack_single<std::string>(meta_st);


auto meta = nlohmann::json::parse(meta_json); auto meta = nlohmann::json::parse(meta_json);
if (!meta.is_object()) { if (!meta.is_object()) {
migrate_1(db); migrate_1(db);
} }
meta["version"] = 1; meta["version"] = 1;
exec(db, "UPDATE dds_meta SET meta=?", std::forward_as_tuple(meta.dump()));
exec(db.prepare("UPDATE dds_meta SET meta=?"), meta.dump());
} }


} // namespace } // namespace


database database::open(const std::string& db_path) { database database::open(const std::string& db_path) {
auto db = sqlite3::database::open(db_path);
auto db = nsql::database::open(db_path);
try { try {
ensure_migrated(db); ensure_migrated(db);
} catch (const sqlite3::sqlite3_error& e) {
} catch (const nsql::sqlite3_error& e) {
dds_log( dds_log(
error, error,
"Failed to load the databsae. It appears to be invalid/corrupted. We'll delete it and " "Failed to load the databsae. It appears to be invalid/corrupted. We'll delete it and "
"create a new one. The exception message is: {}", "create a new one. The exception message is: {}",
e.what()); e.what());
fs::remove(db_path); fs::remove(db_path);
db = sqlite3::database::open(db_path);
db = nsql::database::open(db_path);
try { try {
ensure_migrated(db); ensure_migrated(db);
} catch (const sqlite3::sqlite3_error& e) {
} catch (const nsql::sqlite3_error& e) {
dds_log(critical, dds_log(critical,
"Failed to apply database migrations to recovery database. This is a critical " "Failed to apply database migrations to recovery database. This is a critical "
"error. The exception message is: {}", "error. The exception message is: {}",
return database(std::move(db)); return database(std::move(db));
} }


database::database(sqlite3::database db)
database::database(nsql::database db)
: _db(std::move(db)) {} : _db(std::move(db)) {}


std::int64_t database::_record_file(path_ref path_) { std::int64_t database::_record_file(path_ref path_) {
auto path = fs::weakly_canonical(path_); auto path = fs::weakly_canonical(path_);
sqlite3::exec(_stmt_cache(R"(
nsql::exec(_stmt_cache(R"(
INSERT OR IGNORE INTO dds_files (path) INSERT OR IGNORE INTO dds_files (path)
VALUES (?) VALUES (?)
)"_sql), )"_sql),
std::forward_as_tuple(path.generic_string()));
path.generic_string());
auto& st = _stmt_cache(R"( auto& st = _stmt_cache(R"(
SELECT file_id SELECT file_id
FROM dds_files FROM dds_files
WHERE path = ?1 WHERE path = ?1
)"_sql); )"_sql);
st.reset(); st.reset();
auto str = path.generic_string();
st.bindings[1] = str;
auto [rowid] = sqlite3::unpack_single<std::int64_t>(st);
auto str = path.generic_string();
st.bindings()[1] = str;
auto [rowid] = nsql::unpack_single<std::int64_t>(st);
return rowid; return rowid;
} }


INSERT OR REPLACE INTO dds_deps (input_file_id, output_file_id, input_mtime) INSERT OR REPLACE INTO dds_deps (input_file_id, output_file_id, input_mtime)
VALUES (?, ?, ?) VALUES (?, ?, ?)
)"_sql); )"_sql);
sqlite3::exec(st, std::forward_as_tuple(in_id, out_id, input_mtime.time_since_epoch().count()));
nsql::exec(st, in_id, out_id, input_mtime.time_since_epoch().count());
} }


void database::store_file_command(path_ref file, const command_info& cmd) { void database::store_file_command(path_ref file, const command_info& cmd) {
INTO dds_file_commands(file_id, command, output) INTO dds_file_commands(file_id, command, output)
VALUES (?1, ?2, ?3) VALUES (?1, ?2, ?3)
)"_sql); )"_sql);
sqlite3::exec(st,
std::forward_as_tuple(file_id,
std::string_view(cmd.command),
std::string_view(cmd.output)));
nsql::exec(st, file_id, std::string_view(cmd.command), std::string_view(cmd.output));
} }


void database::forget_inputs_of(path_ref file) { void database::forget_inputs_of(path_ref file) {
DELETE FROM dds_deps DELETE FROM dds_deps
WHERE output_file_id IN id_to_delete WHERE output_file_id IN id_to_delete
)"_sql); )"_sql);
sqlite3::exec(st, std::forward_as_tuple(fs::weakly_canonical(file).generic_string()));
nsql::exec(st, fs::weakly_canonical(file).generic_string());
} }


std::optional<std::vector<input_file_info>> database::inputs_of(path_ref file_) const { std::optional<std::vector<input_file_info>> database::inputs_of(path_ref file_) const {
WHERE output_file_id IN file WHERE output_file_id IN file
)"_sql); )"_sql);
st.reset(); st.reset();
st.bindings[1] = file.generic_string();
auto tup_iter = sqlite3::iter_tuples<std::string, std::int64_t>(st);
st.bindings()[1] = file.generic_string();
auto tup_iter = nsql::iter_tuples<std::string, std::int64_t>(st);


std::vector<input_file_info> ret; std::vector<input_file_info> ret;
for (auto& [path, mtime] : tup_iter) {
for (auto [path, mtime] : tup_iter) {
ret.emplace_back( ret.emplace_back(
input_file_info{path, fs::file_time_type(fs::file_time_type::duration(mtime))}); input_file_info{path, fs::file_time_type(fs::file_time_type::duration(mtime))});
} }
WHERE file_id IN file WHERE file_id IN file
)"_sql); )"_sql);
st.reset(); st.reset();
st.bindings[1] = file.generic_string();
auto opt_res = sqlite3::unpack_single_opt<std::string, std::string>(st);
st.bindings()[1] = file.generic_string();
auto opt_res = nsql::unpack_single_opt<std::string, std::string>(st);
if (!opt_res) { if (!opt_res) {
return std::nullopt; return std::nullopt;
} }

+ 6
- 0
src/dds/error/errors.cpp View File

return "no-catalog-remote-info.html"; return "no-catalog-remote-info.html";
case errc::git_clone_failure: case errc::git_clone_failure:
return "git-clone-failure.html"; return "git-clone-failure.html";
case errc::invalid_remote_url:
return "invalid-remote-url.html";
case errc::invalid_repo_transform: case errc::invalid_repo_transform:
return "invalid-repo-transform.html"; return "invalid-repo-transform.html";
case errc::sdist_ident_mismatch: case errc::sdist_ident_mismatch:
There are a variety of possible causes. It is best to check the output from There are a variety of possible causes. It is best to check the output from
Git in diagnosing this failure. Git in diagnosing this failure.
)"; )";
case errc::invalid_remote_url:
return R"(The given package/remote URL is invalid)";
case errc::invalid_repo_transform: case errc::invalid_repo_transform:
return R"( return R"(
A 'transform' property in a catalog entry contains an invalid transformation. A 'transform' property in a catalog entry contains an invalid transformation.
"packages"; "packages";
case errc::git_clone_failure: case errc::git_clone_failure:
return "A git-clone operation failed."; return "A git-clone operation failed.";
case errc::invalid_remote_url:
return "The given package/remote URL is not valid";
case errc::invalid_repo_transform: case errc::invalid_repo_transform:
return "A repository filesystem transformation is invalid"; return "A repository filesystem transformation is invalid";
case errc::sdist_ident_mismatch: case errc::sdist_ident_mismatch:

+ 1
- 0
src/dds/error/errors.hpp View File

no_catalog_remote_info, no_catalog_remote_info,


git_clone_failure, git_clone_failure,
invalid_remote_url,
invalid_repo_transform, invalid_repo_transform,
sdist_ident_mismatch, sdist_ident_mismatch,
sdist_exists, sdist_exists,

+ 4
- 3
src/dds/library/root.cpp View File

#include <dds/source/root.hpp> #include <dds/source/root.hpp>
#include <dds/util/algo.hpp> #include <dds/util/algo.hpp>
#include <dds/util/log.hpp> #include <dds/util/log.hpp>
#include <dds/util/ranges.hpp>


#include <neo/ref.hpp>
#include <range/v3/view/filter.hpp> #include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp> #include <range/v3/view/transform.hpp>




if (fs::is_directory(pf_libs_dir)) { if (fs::is_directory(pf_libs_dir)) {
extend(ret, extend(ret,
view_safe(fs::directory_iterator(pf_libs_dir)) //
| ranges::views::filter(has_library_dirs) //
fs::directory_iterator(pf_libs_dir) //
| neo::lref //
| ranges::views::filter(has_library_dirs) //
| ranges::views::transform( | ranges::views::transform(
[&](auto p) { return library_root::from_directory(fs::canonical(p)); })); [&](auto p) { return library_root::from_directory(fs::canonical(p)); }));
} }

+ 5
- 4
src/dds/repo/repo.cpp View File

#include <dds/source/dist.hpp> #include <dds/source/dist.hpp>
#include <dds/util/log.hpp> #include <dds/util/log.hpp>
#include <dds/util/paths.hpp> #include <dds/util/paths.hpp>
#include <dds/util/ranges.hpp>
#include <dds/util/string.hpp> #include <dds/util/string.hpp>


#include <neo/ref.hpp>
#include <range/v3/action/sort.hpp> #include <range/v3/action/sort.hpp>
#include <range/v3/action/unique.hpp> #include <range/v3/action/unique.hpp>
#include <range/v3/range/conversion.hpp> #include <range/v3/range/conversion.hpp>


auto entries = auto entries =
// Get the top-level `name-version` dirs // Get the top-level `name-version` dirs
view_safe(fs::directory_iterator(dirpath)) //
// // Convert each dir into an `sdist` object
fs::directory_iterator(dirpath) //
| neo::lref //
// Convert each dir into an `sdist` object
| ranges::views::transform(try_read_sdist) // | ranges::views::transform(try_read_sdist) //
// // Drop items that failed to load
// Drop items that failed to load
| ranges::views::filter([](auto&& opt) { return opt.has_value(); }) // | ranges::views::filter([](auto&& opt) { return opt.has_value(); }) //
| ranges::views::transform([](auto&& opt) { return *opt; }) // | ranges::views::transform([](auto&& opt) { return *opt; }) //
| to<sdist_set>(); | to<sdist_set>();

+ 3
- 3
src/dds/source/root.cpp View File

#include "./root.hpp" #include "./root.hpp"


#include <dds/util/ranges.hpp>

#include <neo/ref.hpp>
#include <range/v3/range/conversion.hpp> #include <range/v3/range/conversion.hpp>
#include <range/v3/view/filter.hpp> #include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp> #include <range/v3/view/transform.hpp>
using namespace ranges::views; using namespace ranges::views;
// Collect all source files from the directory // Collect all source files from the directory
return // return //
view_safe(fs::recursive_directory_iterator(path)) //
fs::recursive_directory_iterator(path) //
| neo::lref //
| filter([](auto&& entry) { return entry.is_regular_file(); }) // | filter([](auto&& entry) { return entry.is_regular_file(); }) //
| transform([&](auto&& entry) { return source_file::from_path(entry, path); }) // | transform([&](auto&& entry) { return source_file::from_path(entry, path); }) //
// source_file::from_path returns an optional. Drop nulls // source_file::from_path returns an optional. Drop nulls

+ 0
- 10
src/dds/util/ranges.hpp View File

#pragma once

namespace dds {

template <typename T>
constexpr auto& view_safe(T&& t) {
return t;
}

} // namespace dds

+ 3
- 7
tests/catalog/get_test.py View File



json_path = dds.build_dir / 'catalog.json' json_path = dds.build_dir / 'catalog.json'
import_data = { import_data = {
'version': 1,
'version': 2,
'packages': { 'packages': {
'neo-sqlite3': { 'neo-sqlite3': {
'0.3.0': { '0.3.0': {
'git': {
'url':
'https://github.com/vector-of-bool/neo-sqlite3.git',
'ref':
'0.3.0',
},
'url':
'git+https://github.com/vector-of-bool/neo-sqlite3.git#0.3.0',
}, },
}, },
}, },

+ 3
- 9
tests/catalog/import_test.py View File



json_fpath = dds.build_dir / 'data.json' json_fpath = dds.build_dir / 'data.json'
import_data = { import_data = {
'version': 1,
'version': 2,
'packages': { 'packages': {
'foo': { 'foo': {
'1.2.4': { '1.2.4': {
'git': {
'url': 'http://example.com',
'ref': 'master',
},
'url': 'git+http://example.com#master',
'depends': [], 'depends': [],
}, },
'1.2.5': { '1.2.5': {
'git': {
'url': 'http://example.com',
'ref': 'master',
},
'url': 'git+http://example.com#master',
}, },
}, },
}, },

+ 4
- 13
tests/deps/build-deps/project/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": { "packages": {
"neo-sqlite3": { "neo-sqlite3": {
"0.1.0": { "0.1.0": {
"git": {
"url": "https://github.com/vector-of-bool/neo-sqlite3.git",
"ref": "0.1.0"
},
"url": "git+https://github.com/vector-of-bool/neo-sqlite3.git#0.1.0"
}, },
"0.2.2": { "0.2.2": {
"git": {
"url": "https://github.com/vector-of-bool/neo-sqlite3.git",
"ref": "0.2.2"
},
"url": "git+https://github.com/vector-of-bool/neo-sqlite3.git#0.2.2"
}, },
"0.3.0": { "0.3.0": {
"git": {
"url": "https://github.com/vector-of-bool/neo-sqlite3.git",
"ref": "0.3.0"
},
"url": "git+https://github.com/vector-of-bool/neo-sqlite3.git#0.3.0"
} }
} }
} }

+ 3
- 10
tests/deps/git-remote/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": { "packages": {
"neo-fun": { "neo-fun": {
"0.3.2": { "0.3.2": {
"git": {
"url": "https://github.com/vector-of-bool/neo-fun.git",
"ref": "0.3.2"
}
"url": "git+https://github.com/vector-of-bool/neo-fun.git#0.3.2"
} }
}, },
"range-v3": { "range-v3": {
"0.9.1": { "0.9.1": {
"git": {
"url": "https://github.com/ericniebler/range-v3.git",
"ref": "0.9.1",
"auto-lib": "Niebler/range-v3"
}
"url": "git+https://github.com/ericniebler/range-v3.git?lm=Niebler/range-v3#0.9.1"
} }
} }
} }

+ 1
- 1
tests/deps/no-deps/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": {} "packages": {}
} }

+ 14
- 18
tests/deps/use-cryptopp/project/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": { "packages": {
"cryptopp": { "cryptopp": {
"8.2.0": { "8.2.0": {
"git": {
"url": "https://github.com/weidai11/cryptopp.git",
"ref": "CRYPTOPP_8_2_0",
"auto-lib": "cryptopp/cryptopp",
"transform": [
{
"move": {
"from": ".",
"to": "src/cryptopp",
"include": [
"*.c",
"*.cpp",
"*.h"
]
}
"url": "git+https://github.com/weidai11/cryptopp.git?lm=cryptopp/cryptopp#CRYPTOPP_8_2_0",
"transform": [
{
"move": {
"from": ".",
"to": "src/cryptopp",
"include": [
"*.c",
"*.cpp",
"*.h"
]
} }
]
}
}
]
} }
} }
} }

+ 2
- 5
tests/deps/use-remote/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": { "packages": {
"nlohmann-json": { "nlohmann-json": {
"3.7.1": { "3.7.1": {
"git": {
"url": "https://github.com/vector-of-bool/json.git",
"ref": "dds/3.7.1"
},
"url": "git+https://github.com/vector-of-bool/json.git#dds/3.7.1",
"depends": [] "depends": []
} }
} }

+ 2
- 6
tests/deps/use-spdlog/project/catalog.json View File

{ {
"version": 1,
"version": 2,
"packages": { "packages": {
"spdlog": { "spdlog": {
"1.4.2": { "1.4.2": {
"git": {
"url": "https://github.com/gabime/spdlog.git",
"ref": "v1.4.2",
"auto-lib": "spdlog/spdlog"
},
"url": "git+https://github.com/gabime/spdlog.git?lm=spdlog/spdlog#v1.4.2",
"depends": [] "depends": []
} }
} }

+ 1
- 1
tools/ci.py View File

paths.PREBUILT_DDS, paths.PREBUILT_DDS,
toolchain=opts.toolchain, toolchain=opts.toolchain,
cat_path=old_cat_path, cat_path=old_cat_path,
cat_json_path=Path('catalog.json'),
cat_json_path=Path('catalog.old.json'),
dds_flags=[('--repo-dir', ci_repo_dir)]) dds_flags=[('--repo-dir', ci_repo_dir)])
print('Main build PASSED!') print('Main build PASSED!')
print(f'A `dds` executable has been generated: {paths.CUR_BUILT_DDS}') print(f'A `dds` executable has been generated: {paths.CUR_BUILT_DDS}')

+ 31
- 3
tools/gen-catalog-json.py View File

d['auto-lib'] = self.auto_lib d['auto-lib'] = self.auto_lib
return d return d


def to_dict_2(self) -> str:
url = f'git+{self.url}'
if self.auto_lib:
url += f'?lm={self.auto_lib}'
url += f'#{self.ref}'
return url



RemoteInfo = Union[Git] RemoteInfo = Union[Git]


ret['git'] = self.remote.to_dict() ret['git'] = self.remote.to_dict()
return ret return ret


def to_dict_2(self) -> dict:
ret: dict = {
'description': self.description,
'depends': list(self.depends),
'transform': [f.to_dict() for f in self.remote.transforms],
}
ret['url'] = self.remote.to_dict_2()
return ret



class VersionSet(NamedTuple): class VersionSet(NamedTuple):
version: str version: str
# yapf: disable # yapf: disable
PACKAGES = [ PACKAGES = [
github_package('neo-buffer', 'vector-of-bool/neo-buffer', github_package('neo-buffer', 'vector-of-bool/neo-buffer',
['0.2.1', '0.3.0', '0.4.0', '0.4.1']),
['0.2.1', '0.3.0', '0.4.0', '0.4.1', '0.4.2']),
github_package('neo-compress', 'vector-of-bool/neo-compress', ['0.1.0']), github_package('neo-compress', 'vector-of-bool/neo-compress', ['0.1.0']),
github_package('neo-url', 'vector-of-bool/neo-url', ['0.1.0', '0.1.1', '0.1.2']),
github_package('neo-sqlite3', 'vector-of-bool/neo-sqlite3', github_package('neo-sqlite3', 'vector-of-bool/neo-sqlite3',
['0.2.3', '0.3.0']),
['0.2.3', '0.3.0', '0.4.0', '0.4.1']),
github_package('neo-fun', 'vector-of-bool/neo-fun', [ github_package('neo-fun', 'vector-of-bool/neo-fun', [
'0.1.1', '0.2.0', '0.2.1', '0.3.0', '0.3.1', '0.3.2', '0.4.0', '0.4.1'
'0.1.1', '0.2.0', '0.2.1', '0.3.0', '0.3.1', '0.3.2', '0.4.0', '0.4.1',
'0.4.2', '0.5.0', '0.5.1', '0.5.2', '0.5.3',
]), ]),
github_package('neo-concepts', 'vector-of-bool/neo-concepts', ( github_package('neo-concepts', 'vector-of-bool/neo-concepts', (
'0.2.2', '0.2.2',
args = parser.parse_args() args = parser.parse_args()


data = { data = {
'version': 2,
'packages': {
pkg.name: {ver.version: ver.to_dict_2()
for ver in pkg.versions}
for pkg in PACKAGES
}
}
old_data = {
'version': 1, 'version': 1,
'packages': { 'packages': {
pkg.name: {ver.version: ver.to_dict() pkg.name: {ver.version: ver.to_dict()
} }
json_str = json.dumps(data, indent=2, sort_keys=True) json_str = json.dumps(data, indent=2, sort_keys=True)
Path('catalog.json').write_text(json_str) Path('catalog.json').write_text(json_str)
Path('catalog.old.json').write_text(
json.dumps(old_data, indent=2, sort_keys=True))


cpp_template = textwrap.dedent(r''' cpp_template = textwrap.dedent(r'''
#include <dds/catalog/package_info.hpp> #include <dds/catalog/package_info.hpp>

Loading…
Cancel
Save