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
@@ -0,0 +1,14 @@ | |||
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.) |
@@ -6,14 +6,15 @@ | |||
"microsoft/wil", | |||
"range-v3/range-v3", | |||
"nlohmann/json", | |||
"neo/sqlite3", | |||
"neo/fun", | |||
"neo/sqlite3", | |||
"vob/semver", | |||
"vob/pubgrub", | |||
"vob/json5", | |||
"vob/semester", | |||
"hanickadot/ctre", | |||
// "neo/io", | |||
"neo/url", | |||
// Explicit zlib link is required due to linker input order bug. | |||
// Can be removed after alpha.5 | |||
"zlib/zlib", |
@@ -8,9 +8,10 @@ | |||
"ms-wil@2020.3.16", | |||
"range-v3@0.11.0", | |||
"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-url~0.1.2", | |||
"semver@0.2.2", | |||
"pubgrub@0.2.1", | |||
"vob-json5@0.1.5", |
@@ -7,7 +7,6 @@ | |||
#include <dds/error/errors.hpp> | |||
#include <dds/solve/solve.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/ranges.hpp> | |||
#include <json5/parse_data.hpp> | |||
#include <neo/assert.hpp> | |||
@@ -22,12 +21,12 @@ | |||
using namespace dds; | |||
namespace sqlite3 = neo::sqlite3; | |||
using namespace sqlite3::literals; | |||
namespace nsql = neo::sqlite3; | |||
using namespace neo::sqlite3::literals; | |||
namespace { | |||
void migrate_repodb_1(sqlite3::database& db) { | |||
void migrate_repodb_1(nsql::database& db) { | |||
db.exec(R"( | |||
CREATE TABLE dds_cat_pkgs ( | |||
pkg_id INTEGER PRIMARY KEY AUTOINCREMENT, | |||
@@ -71,13 +70,70 @@ void migrate_repodb_1(sqlite3::database& db) { | |||
)"); | |||
} | |||
void migrate_repodb_2(sqlite3::database& db) { | |||
void migrate_repodb_2(nsql::database& db) { | |||
db.exec(R"( | |||
ALTER TABLE dds_cat_pkgs | |||
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 acc = "["; | |||
for (auto it = trs.begin(); it != trs.end(); ++it) { | |||
@@ -104,16 +160,22 @@ void store_with_remote(neo::sqlite3::statement_cache& stmts, | |||
const package_info& pkg, | |||
const git_remote_listing& git) { | |||
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 ( | |||
name, | |||
version, | |||
git_url, | |||
git_ref, | |||
lm_name, | |||
lm_namespace, | |||
remote_url, | |||
description, | |||
repo_transform | |||
) VALUES ( | |||
@@ -121,21 +183,14 @@ void store_with_remote(neo::sqlite3::statement_cache& stmts, | |||
?2, | |||
?3, | |||
?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, | |||
@@ -162,23 +217,19 @@ void do_store_pkg(neo::sqlite3::database& db, | |||
assert(dep.versions.num_intervals() == 1); | |||
auto iv_1 = *dep.versions.iter_intervals().begin(); | |||
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"); | |||
for (auto& pkg : init_catalog_packages()) { | |||
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"( | |||
PRAGMA foreign_keys = 1; | |||
CREATE TABLE IF NOT EXISTS dds_cat_meta AS | |||
@@ -186,7 +237,7 @@ void ensure_migrated(sqlite3::database& db) { | |||
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_json] = nsql::unpack_single<std::string>(meta_st); | |||
auto meta = nlohmann::json::parse(meta_json); | |||
if (!meta.is_object()) { | |||
@@ -201,7 +252,7 @@ void ensure_migrated(sqlite3::database& db) { | |||
"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_; | |||
@@ -225,8 +276,12 @@ void ensure_migrated(sqlite3::database& db) { | |||
dds_log(debug, "Applying catalog migration 2"); | |||
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) { | |||
dds_log( | |||
@@ -253,10 +308,10 @@ catalog catalog::open(const std::string& db_path) { | |||
fs::create_directories(pardir); | |||
} | |||
dds_log(debug, "Opening package catalog [{}]", db_path); | |||
auto db = sqlite3::database::open(db_path); | |||
auto db = nsql::database::open(db_path); | |||
try { | |||
ensure_migrated(db); | |||
} catch (const sqlite3::sqlite3_error& e) { | |||
} catch (const nsql::sqlite3_error& e) { | |||
dds_log(critical, | |||
"Failed to load the repository database. It appears to be invalid/corrupted. The " | |||
"exception message is: {}", | |||
@@ -267,11 +322,11 @@ catalog catalog::open(const std::string& db_path) { | |||
return catalog(std::move(db)); | |||
} | |||
catalog::catalog(sqlite3::database db) | |||
catalog::catalog(nsql::database db) | |||
: _db(std::move(db)) {} | |||
void catalog::store(const package_info& pkg) { | |||
sqlite3::transaction_guard tr{_db}; | |||
nsql::transaction_guard tr{_db}; | |||
do_store_pkg(_db, _stmt_cache, pkg); | |||
} | |||
@@ -283,26 +338,20 @@ std::optional<package_info> catalog::get(const package_id& pk_id) const noexcept | |||
pkg_id, | |||
name, | |||
version, | |||
git_url, | |||
git_ref, | |||
lm_name, | |||
lm_namespace, | |||
remote_url, | |||
description, | |||
repo_transform | |||
FROM dds_cat_pkgs | |||
WHERE name = ? AND version = ? | |||
)"_sql); | |||
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) { | |||
dym_target::fill([&] { | |||
auto all_ids = this->all(); | |||
@@ -312,20 +361,9 @@ std::optional<package_info> catalog::get(const package_id& pk_id) const noexcept | |||
}); | |||
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.version == semver::version::parse(version)); | |||
assert(git_url); | |||
assert(git_ref); | |||
auto deps = dependencies_of(pk_id); | |||
@@ -333,12 +371,7 @@ std::optional<package_info> catalog::get(const package_id& pk_id) const noexcept | |||
pk_id, | |||
std::move(deps), | |||
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()) { | |||
@@ -373,33 +406,34 @@ auto pair_to_pkg_id = [](auto&& pair) { | |||
}; | |||
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::to_vector; | |||
} | |||
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 | |||
FROM dds_cat_pkgs | |||
WHERE name = ? | |||
)"_sql, | |||
std::tie(sv))) // | |||
)"_sql), | |||
sv) // | |||
| neo::lref // | |||
| ranges::views::transform(pair_to_pkg_id) // | |||
| ranges::to_vector; | |||
} | |||
std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const noexcept { | |||
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 ( | |||
SELECT pkg_id | |||
FROM dds_cat_pkgs | |||
@@ -409,8 +443,10 @@ std::vector<dependency> catalog::dependencies_of(const package_id& pkg) const no | |||
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()))) // | |||
)"_sql), | |||
pkg.name, | |||
pkg.version.to_string()) // | |||
| neo::lref // | |||
| ranges::views::transform([](auto&& pair) { | |||
auto& [name, low, high] = pair; | |||
auto dep | |||
@@ -425,14 +461,14 @@ void catalog::import_json_str(std::string_view content) { | |||
dds_log(trace, "Importing JSON string into catalog"); | |||
auto pkgs = parse_packages_json(content); | |||
sqlite3::transaction_guard tr{_db}; | |||
nsql::transaction_guard tr{_db}; | |||
for (const auto& pkg : pkgs) { | |||
store(pkg); | |||
do_store_pkg(_db, _stmt_cache, pkg); | |||
} | |||
} | |||
void catalog::import_initial() { | |||
sqlite3::transaction_guard tr{_db}; | |||
nsql::transaction_guard tr{_db}; | |||
dds_log(info, "Restoring built-in initial catalog contents"); | |||
store_init_packages(_db, _stmt_cache); | |||
} |
@@ -30,7 +30,7 @@ TEST_CASE_METHOD(catalog_test_case, "Store a simple package") { | |||
dds::package_id("foo", semver::version::parse("1.2.3")), | |||
{}, | |||
"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"); | |||
@@ -49,7 +49,7 @@ TEST_CASE_METHOD(catalog_test_case, "Store a simple package") { | |||
dds::package_id("foo", semver::version::parse("1.2.3")), | |||
{}, | |||
"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 | |||
info = db.get(pkgs[0]); | |||
@@ -65,7 +65,7 @@ TEST_CASE_METHOD(catalog_test_case, "Package requirements") { | |||
{"baz", {semver::version::parse("5.3.0"), semver::version::parse("6.0.0")}}, | |||
}, | |||
"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"); | |||
REQUIRE(pkgs.size() == 1); | |||
@@ -78,17 +78,14 @@ TEST_CASE_METHOD(catalog_test_case, "Package requirements") { | |||
TEST_CASE_METHOD(catalog_test_case, "Parse JSON repo") { | |||
db.import_json_str(R"({ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"foo": { | |||
"1.2.3": { | |||
"depends": [ | |||
"bar~4.2.1" | |||
], | |||
"git": { | |||
"url": "http://example.com", | |||
"ref": "master" | |||
} | |||
url: "git+http://example.com#master" | |||
} | |||
} | |||
} |
@@ -6,6 +6,7 @@ | |||
#include <fmt/core.h> | |||
#include <json5/parse_data.hpp> | |||
#include <neo/assert.hpp> | |||
#include <neo/url.hpp> | |||
#include <semester/walk.hpp> | |||
#include <optional> | |||
@@ -45,59 +46,60 @@ template <typename... 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; | |||
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 | |||
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; | |||
ret.ident = package_id{std::string{name}, version}; | |||
std::vector<fs_transformation> fs_trs; | |||
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&&) { | |||
if (!semester::holds_alternative<std::monostate>(ret.remote)) { | |||
return walk.reject("Cannot specify multiple remotes for a package"); | |||
@@ -114,10 +116,12 @@ parse_pkg_json_v1(std::string_view name, semver::version version, const json5::d | |||
for_each{require_str{"Each dependency should be a string"}, | |||
put_into{std::back_inserter(ret.deps), make_dep}}}, | |||
if_key{ | |||
"git", | |||
"url", | |||
require_str{"Remote URL should be a string"}, | |||
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)) { | |||
import_error("{}: Package listing for {} does not have any remote information", | |||
@@ -125,10 +129,19 @@ parse_pkg_json_v1(std::string_view name, semver::version version, const json5::d | |||
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; | |||
} | |||
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::string pkg_name; | |||
@@ -138,19 +151,7 @@ std::vector<package_info> parse_json_v1(const json5::data& data) { | |||
using namespace semester::walk_ops; | |||
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 | |||
= walk_seq{require_obj{"Package entries must be JSON objects"}, | |||
@@ -196,8 +197,11 @@ std::vector<package_info> dds::parse_packages_json(std::string_view content) { | |||
try { | |||
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 { | |||
throw_user_error<errc::invalid_catalog_json>("Unknown catalog JSON version '{}'", | |||
version); |
@@ -6,7 +6,7 @@ | |||
TEST_CASE("An empty import is okay") { | |||
// 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()); | |||
} | |||
@@ -19,45 +19,39 @@ TEST_CASE("Valid/invalid package JSON5") { | |||
// Missing keys | |||
"{}", | |||
// Missing "packages" | |||
"{version: 1}", | |||
"{version: 2}", | |||
// Bad version | |||
"{version: 1.7, packages: {}}", | |||
"{version: 2.7, packages: {}}", | |||
"{version: [], packages: {}}", | |||
"{version: null, packages: {}}", | |||
// '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 | |||
"{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 | |||
"{version:1, packages:{foo:{'lol':{}}}}", | |||
"{version:1, packages:{foo:{'1.2':{}}}}", | |||
"{version:2, packages:{foo:{'lol':{}}}}", | |||
"{version:2, packages:{foo:{'1.2':{}}}}", | |||
// 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 | |||
"{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 | |||
R"( | |||
{ | |||
version: 1, | |||
version: 2, | |||
packages: {foo: {'1.2.3': { | |||
git: { | |||
url: '', | |||
ref: '', | |||
'auto-lib': 'a/b', | |||
transform: 'lol hi', | |||
} | |||
url: 'git+http://example.com#master, | |||
transform: 'lol hi' | |||
}}} | |||
} | |||
)", | |||
@@ -71,49 +65,41 @@ TEST_CASE("Valid/invalid package JSON5") { | |||
std::string_view goods[] = { | |||
// Basic empty: | |||
"{version:1, packages:{}}", | |||
"{version:2, packages:{}}", | |||
// No versions for 'foo' is weird, but okay | |||
"{version:1, packages:{foo:{}}}", | |||
"{version:2, packages:{foo:{}}}", | |||
// 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: | |||
"{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: | |||
R"( | |||
{ | |||
version: 1, | |||
version: 2, | |||
packages: {foo: {'1.2.3': { | |||
git: { | |||
url: '', | |||
ref: '', | |||
'auto-lib': 'a/b', | |||
transform: [], | |||
} | |||
url: 'git+http://example.com#master', | |||
transform: [], | |||
}}} | |||
} | |||
)", | |||
// Basic transform: | |||
R"( | |||
{ | |||
version: 1, | |||
version: 2, | |||
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' | |||
] | |||
} | |||
}], | |||
}}} | |||
} | |||
)", | |||
@@ -127,15 +113,11 @@ TEST_CASE("Valid/invalid package JSON5") { | |||
TEST_CASE("Check a single object") { | |||
// An empty JSON with no packages in it | |||
auto pkgs = dds::parse_packages_json(R"({ | |||
version: 1, | |||
version: 2, | |||
packages: { | |||
foo: { | |||
'1.2.3': { | |||
git: { | |||
url: 'foo', | |||
ref: 'fasdf', | |||
'auto-lib': 'a/b', | |||
} | |||
url: 'git+http://example.com?lm=a/b#master', | |||
} | |||
} | |||
} | |||
@@ -146,8 +128,8 @@ TEST_CASE("Check a single object") { | |||
CHECK(std::holds_alternative<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); | |||
CHECK(git.auto_lib->namespace_ == "a"); | |||
CHECK(git.auto_lib->name == "b"); |
@@ -0,0 +1,20 @@ | |||
#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); | |||
} | |||
} |
@@ -14,12 +14,16 @@ | |||
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 { | |||
package_id ident; | |||
std::vector<dependency> deps; | |||
std::string description; | |||
std::variant<std::monostate, git_remote_listing> remote; | |||
remote_listing_var remote; | |||
}; | |||
} // namespace dds |
@@ -4,9 +4,13 @@ | |||
#include <dds/proc.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <neo/url.hpp> | |||
#include <neo/url/query.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); | |||
using namespace std::literals; | |||
dds_log(info, "Clone Git repository [{}] (at {}) to [{}]", url, ref, dest.string()); | |||
@@ -27,16 +31,53 @@ void dds::git_remote_listing::pull_to(const dds::package_id& pid, dds::path_ref | |||
if (auto_lib.has_value()) { | |||
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(); | |||
man_json["name"] = pid.name; | |||
man_json["version"] = pid.version.to_string(); | |||
man_json["namespace"] = auto_lib->namespace_; | |||
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(); | |||
lib_json["name"] = auto_lib->name; | |||
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 = {}}; | |||
} |
@@ -19,6 +19,8 @@ struct git_remote_listing { | |||
std::vector<fs_transformation> transforms; | |||
void pull_to(const package_id& pid, path_ref path) const; | |||
static git_remote_listing from_url(std::string_view sv); | |||
}; | |||
} // namespace dds |
@@ -14,13 +14,13 @@ | |||
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 { | |||
void migrate_1(sqlite3::database& db) { | |||
void migrate_1(nsql::database& db) { | |||
db.exec(R"( | |||
CREATE TABLE dds_files ( | |||
file_id INTEGER PRIMARY KEY, | |||
@@ -51,8 +51,8 @@ void migrate_1(sqlite3::database& db) { | |||
)"); | |||
} | |||
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"( | |||
PRAGMA foreign_keys = 1; | |||
CREATE TABLE IF NOT EXISTS dds_meta AS | |||
@@ -60,7 +60,7 @@ void ensure_migrated(sqlite3::database& db) { | |||
SELECT * FROM init; | |||
)"); | |||
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); | |||
if (!meta.is_object()) { | |||
@@ -77,26 +77,26 @@ void ensure_migrated(sqlite3::database& db) { | |||
migrate_1(db); | |||
} | |||
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 | |||
database database::open(const std::string& db_path) { | |||
auto db = sqlite3::database::open(db_path); | |||
auto db = nsql::database::open(db_path); | |||
try { | |||
ensure_migrated(db); | |||
} catch (const sqlite3::sqlite3_error& e) { | |||
} catch (const nsql::sqlite3_error& e) { | |||
dds_log( | |||
error, | |||
"Failed to load the databsae. It appears to be invalid/corrupted. We'll delete it and " | |||
"create a new one. The exception message is: {}", | |||
e.what()); | |||
fs::remove(db_path); | |||
db = sqlite3::database::open(db_path); | |||
db = nsql::database::open(db_path); | |||
try { | |||
ensure_migrated(db); | |||
} catch (const sqlite3::sqlite3_error& e) { | |||
} catch (const nsql::sqlite3_error& e) { | |||
dds_log(critical, | |||
"Failed to apply database migrations to recovery database. This is a critical " | |||
"error. The exception message is: {}", | |||
@@ -107,25 +107,25 @@ database database::open(const std::string& db_path) { | |||
return database(std::move(db)); | |||
} | |||
database::database(sqlite3::database db) | |||
database::database(nsql::database db) | |||
: _db(std::move(db)) {} | |||
std::int64_t database::_record_file(path_ref path_) { | |||
auto path = fs::weakly_canonical(path_); | |||
sqlite3::exec(_stmt_cache(R"( | |||
nsql::exec(_stmt_cache(R"( | |||
INSERT OR IGNORE INTO dds_files (path) | |||
VALUES (?) | |||
)"_sql), | |||
std::forward_as_tuple(path.generic_string())); | |||
path.generic_string()); | |||
auto& st = _stmt_cache(R"( | |||
SELECT file_id | |||
FROM dds_files | |||
WHERE path = ?1 | |||
)"_sql); | |||
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; | |||
} | |||
@@ -136,7 +136,7 @@ void database::record_dep(path_ref input, path_ref output, fs::file_time_type in | |||
INSERT OR REPLACE INTO dds_deps (input_file_id, output_file_id, input_mtime) | |||
VALUES (?, ?, ?) | |||
)"_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) { | |||
@@ -147,10 +147,7 @@ void database::store_file_command(path_ref file, const command_info& cmd) { | |||
INTO dds_file_commands(file_id, command, output) | |||
VALUES (?1, ?2, ?3) | |||
)"_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) { | |||
@@ -163,7 +160,7 @@ void database::forget_inputs_of(path_ref file) { | |||
DELETE FROM dds_deps | |||
WHERE output_file_id IN id_to_delete | |||
)"_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 { | |||
@@ -180,11 +177,11 @@ std::optional<std::vector<input_file_info>> database::inputs_of(path_ref file_) | |||
WHERE output_file_id IN file | |||
)"_sql); | |||
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; | |||
for (auto& [path, mtime] : tup_iter) { | |||
for (auto [path, mtime] : tup_iter) { | |||
ret.emplace_back( | |||
input_file_info{path, fs::file_time_type(fs::file_time_type::duration(mtime))}); | |||
} | |||
@@ -208,8 +205,8 @@ std::optional<command_info> database::command_of(path_ref file_) const { | |||
WHERE file_id IN file | |||
)"_sql); | |||
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) { | |||
return std::nullopt; | |||
} |
@@ -37,6 +37,8 @@ std::string error_url_suffix(dds::errc ec) noexcept { | |||
return "no-catalog-remote-info.html"; | |||
case errc::git_clone_failure: | |||
return "git-clone-failure.html"; | |||
case errc::invalid_remote_url: | |||
return "invalid-remote-url.html"; | |||
case errc::invalid_repo_transform: | |||
return "invalid-repo-transform.html"; | |||
case errc::sdist_ident_mismatch: | |||
@@ -172,6 +174,8 @@ dds tried to clone a repository using Git, but the clone operation failed. | |||
There are a variety of possible causes. It is best to check the output from | |||
Git in diagnosing this failure. | |||
)"; | |||
case errc::invalid_remote_url: | |||
return R"(The given package/remote URL is invalid)"; | |||
case errc::invalid_repo_transform: | |||
return R"( | |||
A 'transform' property in a catalog entry contains an invalid transformation. | |||
@@ -284,6 +288,8 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||
"packages"; | |||
case errc::git_clone_failure: | |||
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: | |||
return "A repository filesystem transformation is invalid"; | |||
case errc::sdist_ident_mismatch: |
@@ -24,6 +24,7 @@ enum class errc { | |||
no_catalog_remote_info, | |||
git_clone_failure, | |||
invalid_remote_url, | |||
invalid_repo_transform, | |||
sdist_ident_mismatch, | |||
sdist_exists, |
@@ -5,8 +5,8 @@ | |||
#include <dds/source/root.hpp> | |||
#include <dds/util/algo.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/transform.hpp> | |||
@@ -105,8 +105,9 @@ std::vector<library_root> dds::collect_libraries(path_ref root) { | |||
if (fs::is_directory(pf_libs_dir)) { | |||
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( | |||
[&](auto p) { return library_root::from_directory(fs::canonical(p)); })); | |||
} |
@@ -6,9 +6,9 @@ | |||
#include <dds/source/dist.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/paths.hpp> | |||
#include <dds/util/ranges.hpp> | |||
#include <dds/util/string.hpp> | |||
#include <neo/ref.hpp> | |||
#include <range/v3/action/sort.hpp> | |||
#include <range/v3/action/unique.hpp> | |||
#include <range/v3/range/conversion.hpp> | |||
@@ -47,10 +47,11 @@ repository repository::_open_for_directory(bool writeable, path_ref dirpath) { | |||
auto entries = | |||
// 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) // | |||
// // Drop items that failed to load | |||
// Drop items that failed to load | |||
| ranges::views::filter([](auto&& opt) { return opt.has_value(); }) // | |||
| ranges::views::transform([](auto&& opt) { return *opt; }) // | |||
| to<sdist_set>(); |
@@ -1,7 +1,6 @@ | |||
#include "./root.hpp" | |||
#include <dds/util/ranges.hpp> | |||
#include <neo/ref.hpp> | |||
#include <range/v3/range/conversion.hpp> | |||
#include <range/v3/view/filter.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
@@ -12,7 +11,8 @@ std::vector<source_file> source_root::collect_sources() const { | |||
using namespace ranges::views; | |||
// Collect all source files from the directory | |||
return // | |||
view_safe(fs::recursive_directory_iterator(path)) // | |||
fs::recursive_directory_iterator(path) // | |||
| neo::lref // | |||
| filter([](auto&& entry) { return entry.is_regular_file(); }) // | |||
| transform([&](auto&& entry) { return source_file::from_path(entry, path); }) // | |||
// source_file::from_path returns an optional. Drop nulls |
@@ -1,10 +0,0 @@ | |||
#pragma once | |||
namespace dds { | |||
template <typename T> | |||
constexpr auto& view_safe(T&& t) { | |||
return t; | |||
} | |||
} // namespace dds |
@@ -12,16 +12,12 @@ def test_get(dds: DDS): | |||
json_path = dds.build_dir / 'catalog.json' | |||
import_data = { | |||
'version': 1, | |||
'version': 2, | |||
'packages': { | |||
'neo-sqlite3': { | |||
'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', | |||
}, | |||
}, | |||
}, |
@@ -10,21 +10,15 @@ def test_import_json(dds: DDS): | |||
json_fpath = dds.build_dir / 'data.json' | |||
import_data = { | |||
'version': 1, | |||
'version': 2, | |||
'packages': { | |||
'foo': { | |||
'1.2.4': { | |||
'git': { | |||
'url': 'http://example.com', | |||
'ref': 'master', | |||
}, | |||
'url': 'git+http://example.com#master', | |||
'depends': [], | |||
}, | |||
'1.2.5': { | |||
'git': { | |||
'url': 'http://example.com', | |||
'ref': 'master', | |||
}, | |||
'url': 'git+http://example.com#master', | |||
}, | |||
}, | |||
}, |
@@ -1,24 +1,15 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"neo-sqlite3": { | |||
"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": { | |||
"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": { | |||
"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" | |||
} | |||
} | |||
} |
@@ -1,21 +1,14 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"neo-fun": { | |||
"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": { | |||
"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,4 +1,4 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": {} | |||
} |
@@ -1,26 +1,22 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"cryptopp": { | |||
"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" | |||
] | |||
} | |||
] | |||
} | |||
} | |||
] | |||
} | |||
} | |||
} |
@@ -1,12 +1,9 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"nlohmann-json": { | |||
"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": [] | |||
} | |||
} |
@@ -1,13 +1,9 @@ | |||
{ | |||
"version": 1, | |||
"version": 2, | |||
"packages": { | |||
"spdlog": { | |||
"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": [] | |||
} | |||
} |
@@ -106,7 +106,7 @@ def main(argv: Sequence[str]) -> int: | |||
paths.PREBUILT_DDS, | |||
toolchain=opts.toolchain, | |||
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)]) | |||
print('Main build PASSED!') | |||
print(f'A `dds` executable has been generated: {paths.CUR_BUILT_DDS}') |
@@ -117,6 +117,13 @@ class Git(NamedTuple): | |||
d['auto-lib'] = self.auto_lib | |||
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] | |||
@@ -136,6 +143,15 @@ class Version(NamedTuple): | |||
ret['git'] = self.remote.to_dict() | |||
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): | |||
version: str | |||
@@ -274,12 +290,14 @@ def many_versions(name: str, | |||
# yapf: disable | |||
PACKAGES = [ | |||
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-url', 'vector-of-bool/neo-url', ['0.1.0', '0.1.1', '0.1.2']), | |||
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', [ | |||
'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', ( | |||
'0.2.2', | |||
@@ -934,6 +952,14 @@ if __name__ == "__main__": | |||
args = parser.parse_args() | |||
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, | |||
'packages': { | |||
pkg.name: {ver.version: ver.to_dict() | |||
@@ -943,6 +969,8 @@ if __name__ == "__main__": | |||
} | |||
json_str = json.dumps(data, indent=2, sort_keys=True) | |||
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''' | |||
#include <dds/catalog/package_info.hpp> |