Uses: nlohmann/json | Uses: nlohmann/json | ||||
Uses: neo/buffer | Uses: neo/buffer | ||||
Uses: neo/sqlite3 | Uses: neo/sqlite3 | ||||
Uses: semver/semver | |||||
Uses: semver/semver | |||||
Uses: pubgrub/pubgrub |
#include "./catalog.hpp" | #include "./catalog.hpp" | ||||
#include <dds/solve/solve.hpp> | |||||
#include <neo/sqlite3/exec.hpp> | #include <neo/sqlite3/exec.hpp> | ||||
#include <neo/sqlite3/iter_tuples.hpp> | #include <neo/sqlite3/iter_tuples.hpp> | ||||
#include <neo/sqlite3/single.hpp> | #include <neo/sqlite3/single.hpp> | ||||
)"_sql); | )"_sql); | ||||
for (const auto& dep : pkg.deps) { | for (const auto& dep : pkg.deps) { | ||||
new_dep_st.reset(); | new_dep_st.reset(); | ||||
if (dep.versions.num_intervals() != 1) { | |||||
throw std::runtime_error( | |||||
"Package dependency may only contain a single version interval"); | |||||
} | |||||
auto iv_1 = *dep.versions.iter_intervals().begin(); | |||||
sqlite3::exec(new_dep_st, | sqlite3::exec(new_dep_st, | ||||
std::forward_as_tuple(db_pkg_id, | std::forward_as_tuple(db_pkg_id, | ||||
dep.name, | dep.name, | ||||
dep.version_range.low().to_string(), | |||||
dep.version_range.high().to_string())); | |||||
iv_1.low.to_string(), | |||||
iv_1.high.to_string())); | |||||
} | } | ||||
} | } | ||||
std::forward_as_tuple(pkg.name, pkg.version.to_string())) // | std::forward_as_tuple(pkg.name, pkg.version.to_string())) // | ||||
| ranges::views::transform([](auto&& pair) { | | ranges::views::transform([](auto&& pair) { | ||||
auto& [name, low, high] = pair; | auto& [name, low, high] = pair; | ||||
return dependency{name, | |||||
semver::range(semver::version::parse(low), | |||||
semver::version::parse(high))}; | |||||
return dependency{name, {semver::version::parse(low), semver::version::parse(high)}}; | |||||
}) // | }) // | ||||
| ranges::to_vector; | | ranges::to_vector; | ||||
} | } | ||||
pkg_name, | pkg_name, | ||||
version_, | version_, | ||||
dep_name)); | dep_name)); | ||||
auto range = semver::range::parse(std::string(dep_version)); | |||||
info.deps.push_back({ | info.deps.push_back({ | ||||
std::string(dep_name), | std::string(dep_name), | ||||
semver::range::parse(std::string(dep_version)), | |||||
{range.low(), range.high()}, | |||||
}); | }); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
std::vector<package_id> catalog::solve_requirements(const std::vector<dependency>& deps) const { | |||||
return dds::solve(deps, | |||||
[&](std::string_view pkg_name) { return this->by_name(pkg_name); }, | |||||
[&](const package_id& pkg) { return this->dependencies_of(pkg); }); | |||||
} |
#pragma once | #pragma once | ||||
#include <dds/catalog/git.hpp> | |||||
#include <dds/deps.hpp> | #include <dds/deps.hpp> | ||||
#include <dds/package_id.hpp> | #include <dds/package_id.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <dds/catalog/git.hpp> | |||||
#include <neo/sqlite3/database.hpp> | #include <neo/sqlite3/database.hpp> | ||||
#include <neo/sqlite3/statement.hpp> | #include <neo/sqlite3/statement.hpp> | ||||
static catalog open(const std::string& db_path); | static catalog open(const std::string& db_path); | ||||
static catalog open(path_ref db_path) { return open(db_path.string()); } | static catalog open(path_ref db_path) { return open(db_path.string()); } | ||||
void store(const package_info& info); | |||||
void store(const package_info& info); | |||||
std::optional<package_info> get(const package_id& id) const noexcept; | std::optional<package_info> get(const package_id& id) const noexcept; | ||||
std::vector<package_id> by_name(std::string_view sv) const noexcept; | std::vector<package_id> by_name(std::string_view sv) const noexcept; | ||||
auto content = dds::slurp_file(json_path); | auto content = dds::slurp_file(json_path); | ||||
import_json_str(content); | import_json_str(content); | ||||
} | } | ||||
std::vector<package_id> solve_requirements(const std::vector<dependency>& deps) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
db.store(dds::package_info{ | db.store(dds::package_info{ | ||||
dds::package_id{"foo", semver::version::parse("1.2.3")}, | dds::package_id{"foo", semver::version::parse("1.2.3")}, | ||||
{ | { | ||||
{"bar", semver::range::parse("=1.2.5")}, | |||||
{"baz", semver::range::parse("^5.3.2")}, | |||||
{"bar", {semver::version::parse("1.2.3"), semver::version::parse("1.4.0")}}, | |||||
{"baz", {semver::version::parse("5.3.0"), semver::version::parse("6.0.0")}}, | |||||
}, | }, | ||||
dds::git_remote_listing{"http://example.com", "master", std::nullopt}, | dds::git_remote_listing{"http://example.com", "master", std::nullopt}, | ||||
}); | }); | ||||
auto deps = db.dependencies_of(pkgs[0]); | auto deps = db.dependencies_of(pkgs[0]); | ||||
REQUIRE(deps.size() == 1); | REQUIRE(deps.size() == 1); | ||||
CHECK(deps[0].name == "bar"); | CHECK(deps[0].name == "bar"); | ||||
CHECK(deps[0].version_range == semver::range::parse("~4.2.1")); | |||||
CHECK(deps[0].versions | |||||
== dds::version_range_set{semver::version::parse("4.2.1"), | |||||
semver::version::parse("4.3.0")}); | |||||
} | |||||
TEST_CASE_METHOD(catalog_test_case, "Simple solve") { | |||||
db.import_json_str(R"({ | |||||
"version": 1, | |||||
"packages": { | |||||
"foo": { | |||||
"1.2.3": { | |||||
"depends": { | |||||
"bar": "~4.2.1" | |||||
}, | |||||
"git": { | |||||
"url": "http://example.com", | |||||
"ref": "master" | |||||
} | |||||
} | |||||
}, | |||||
"bar": { | |||||
"4.2.3": { | |||||
"depends": {}, | |||||
"git": { | |||||
"url": "http://example.com", | |||||
"ref": "master" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
})"); | |||||
auto sln = db.solve_requirements({{"foo", | |||||
dds::version_range_set{semver::version::parse("1.0.0"), | |||||
semver::version::parse("2.0.0")}}}); | |||||
REQUIRE(sln.size() == 2); | |||||
CHECK(sln[0].name == "foo"); | |||||
CHECK(sln[0].version == semver::version::parse("1.2.3")); | |||||
CHECK(sln[1].name == "bar"); | |||||
CHECK(sln[1].version == semver::version::parse("4.2.3")); | |||||
} | } |
int run() { | int run() { | ||||
const auto man = parent.load_package_manifest(); | const auto man = parent.load_package_manifest(); | ||||
for (const auto& dep : man.dependencies) { | for (const auto& dep : man.dependencies) { | ||||
std::cout << dep.name << " " << dep.version_range.to_string() << '\n'; | |||||
std::cout << dep.name << " " << dep.versions << '\n'; | |||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
repo_path_flag repo_where{cmd}; | repo_path_flag repo_where{cmd}; | ||||
catalog_path_flag catalog_path{cmd}; | catalog_path_flag catalog_path{cmd}; | ||||
int run() { | int run() { | ||||
auto man = parent.load_package_manifest(); | |||||
auto catalog = catalog_path.open(); | |||||
bool failed = false; | |||||
auto man = parent.load_package_manifest(); | |||||
auto catalog = catalog_path.open(); | |||||
bool failed = false; | |||||
auto solved_deps = catalog.solve_requirements(man.dependencies); | |||||
dds::repository::with_repository( // | dds::repository::with_repository( // | ||||
repo_where.Get(), | repo_where.Get(), | ||||
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | ||||
[&](dds::repository repo) { | [&](dds::repository repo) { | ||||
for (auto& dep : man.dependencies) { | |||||
assert(false && "Not ready yet"); | |||||
// auto exists = !!repo.find(dep.name, dep.version_range); | |||||
// if (!exists) { | |||||
// spdlog::info("Pull remote: {}@{}", dep.name, | |||||
// dep.version_range.to_string()); auto opt_pkg = | |||||
// catalog.get(dds::package_id{dep.name, dep.version_range}); if | |||||
// (opt_pkg) { | |||||
// auto tsd = dds::get_package_sdist(*opt_pkg); | |||||
// repo.add_sdist(tsd.sdist, dds::if_exists::ignore); | |||||
// } else { | |||||
// spdlog::error("No remote listing for {}@{}", | |||||
// dep.name, | |||||
// dep.version_range.to_string()); | |||||
// failed = true; | |||||
// } | |||||
// } else { | |||||
// spdlog::info("Okay: {} {}", dep.name, dep.version_range.to_string()); | |||||
// } | |||||
for (const dds::package_id& pk : solved_deps) { | |||||
auto exists = !!repo.find(pk); | |||||
if (!exists) { | |||||
spdlog::info("Pull remote: {}", pk.to_string()); | |||||
auto opt_pkg = catalog.get(pk); | |||||
if (opt_pkg) { | |||||
auto tsd = dds::get_package_sdist(*opt_pkg); | |||||
repo.add_sdist(tsd.sdist, dds::if_exists::ignore); | |||||
} else { | |||||
spdlog::error("No remote listing for {}", pk.to_string()); | |||||
failed = true; | |||||
} | |||||
} else { | |||||
spdlog::info("Okay: {}", pk.to_string()); | |||||
} | |||||
} | } | ||||
}); | }); | ||||
if (failed) { | if (failed) { | ||||
repo_where.Get(), | repo_where.Get(), | ||||
dds::repo_flags::read, | dds::repo_flags::read, | ||||
[&](dds::repository repo) { | [&](dds::repository repo) { | ||||
return find_dependencies(repo, | |||||
man.dependencies.begin(), | |||||
man.dependencies.end()); | |||||
return repo.solve(man.dependencies); | |||||
}); | }); | ||||
auto tc = tc_filepath.get_toolchain(); | auto tc = tc_filepath.get_toolchain(); |
#include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
#include <spdlog/fmt/ostr.h> | |||||
#include <cctype> | #include <cctype> | ||||
#include <map> | #include <map> | ||||
try { | try { | ||||
auto rng = semver::range::parse_restricted(version_str); | auto rng = semver::range::parse_restricted(version_str); | ||||
return dependency{std::string(name), rng}; | |||||
return dependency{std::string(name), {rng.low(), rng.high()}}; | |||||
} catch (const semver::invalid_range&) { | } catch (const semver::invalid_range&) { | ||||
throw std::runtime_error(fmt::format( | throw std::runtime_error(fmt::format( | ||||
"Invalid version range string '{}' in dependency declaration '{}' (Should be a " | "Invalid version range string '{}' in dependency declaration '{}' (Should be a " | ||||
} | } | ||||
} | } | ||||
std::vector<sdist> dds::find_dependencies(const repository& repo, const dependency& dep) { | |||||
std::vector<sdist> acc; | |||||
detail::do_find_deps(repo, dep, acc); | |||||
return acc; | |||||
} | |||||
void detail::do_find_deps(const repository& repo, const dependency& dep, std::vector<sdist>& sd) { | |||||
auto sdist_opt = repo.find(dep.name, dep.version_range.low()); | |||||
if (!sdist_opt) { | |||||
throw std::runtime_error( | |||||
fmt::format("Unable to find dependency to satisfy requirement: {} {}", | |||||
dep.name, | |||||
dep.version_range.to_string())); | |||||
} | |||||
const sdist& new_sd = *sdist_opt; | |||||
for (const auto& inner_dep : new_sd.manifest.dependencies) { | |||||
do_find_deps(repo, inner_dep, sd); | |||||
} | |||||
auto insert_point = std::partition_point(sd.begin(), sd.end(), [&](const sdist& cand) { | |||||
return cand.path < new_sd.path; | |||||
}); | |||||
if (insert_point != sd.end() | |||||
&& insert_point->manifest.pk_id.name == new_sd.manifest.pk_id.name) { | |||||
if (insert_point->manifest.pk_id.version != new_sd.manifest.pk_id.version) { | |||||
assert(false && "Version conflict resolution not implemented yet"); | |||||
std::terminate(); | |||||
} | |||||
return; | |||||
} | |||||
sd.insert(insert_point, std::move(new_sd)); | |||||
} | |||||
using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | ||||
using sdist_names = std::set<std::string>; | using sdist_names = std::set<std::string>; | ||||
#include <dds/build/plan/full.hpp> | #include <dds/build/plan/full.hpp> | ||||
#include <pubgrub/interval.hpp> | |||||
#include <semver/range.hpp> | #include <semver/range.hpp> | ||||
#include <semver/version.hpp> | #include <semver/version.hpp> | ||||
major, | major, | ||||
}; | }; | ||||
using version_range_set = pubgrub::interval_set<semver::version>; | |||||
struct dependency { | struct dependency { | ||||
std::string name; | |||||
semver::range version_range; | |||||
std::string name; | |||||
version_range_set versions; | |||||
static dependency parse_depends_string(std::string_view str); | static dependency parse_depends_string(std::string_view str); | ||||
}; | }; | ||||
namespace detail { | |||||
void do_find_deps(const repository&, const dependency& dep, std::vector<sdist>& acc); | |||||
} // namespace detail | |||||
std::vector<sdist> find_dependencies(const repository& repo, const dependency& dep); | |||||
template <typename Iter, typename Snt> | |||||
inline std::vector<sdist> find_dependencies(const repository& repo, Iter it, Snt stop) { | |||||
std::vector<sdist> acc; | |||||
while (it != stop) { | |||||
detail::do_find_deps(repo, *it++, acc); | |||||
} | |||||
return acc; | |||||
} | |||||
build_plan create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env); | build_plan create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env); | ||||
void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); |
#include "./repo.hpp" | #include "./repo.hpp" | ||||
#include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
#include <dds/solve/solve.hpp> | |||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
#include <range/v3/action/sort.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> | ||||
spdlog::info("Source distribution '{}' successfully exported", sd.manifest.pk_id.to_string()); | spdlog::info("Source distribution '{}' successfully exported", sd.manifest.pk_id.to_string()); | ||||
} | } | ||||
const sdist* repository::find(std::string_view name, semver::version ver) const noexcept { | |||||
auto found = _sdists.find(std::tie(name, ver)); | |||||
const sdist* repository::find(const package_id& pkg) const noexcept { | |||||
auto found = _sdists.find(pkg); | |||||
if (found == _sdists.end()) { | if (found == _sdists.end()) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return &*found; | return &*found; | ||||
} | } | ||||
std::vector<sdist> repository::solve(const std::vector<dependency>& deps) const { | |||||
auto ids = dds::solve(deps, | |||||
[&](std::string_view name) -> std::vector<package_id> { | |||||
auto items = ranges::views::all(_sdists) // | |||||
| ranges::views::filter([&](const sdist& sd) { | |||||
return sd.manifest.pk_id.name == name; | |||||
}) | |||||
| ranges::views::transform( | |||||
[](const sdist& sd) { return sd.manifest.pk_id; }) | |||||
| ranges::to_vector; | |||||
ranges::sort(items, std::less<>{}); | |||||
return items; | |||||
}, | |||||
[&](const package_id& pkg_id) { | |||||
auto found = find(pkg_id); | |||||
assert(found); | |||||
return found->manifest.dependencies; | |||||
}); | |||||
return ids // | |||||
| ranges::views::transform([&](const package_id& pk_id) { | |||||
auto found = find(pk_id); | |||||
assert(found); | |||||
return *found; | |||||
}) // | |||||
| ranges::to_vector; | |||||
} |
void add_sdist(const sdist&, if_exists = if_exists::throw_exc); | void add_sdist(const sdist&, if_exists = if_exists::throw_exc); | ||||
const sdist* find(std::string_view name, semver::version ver) const noexcept; | |||||
const sdist* find(const package_id& pk) const noexcept; | |||||
auto iter_sdists() const noexcept { | auto iter_sdists() const noexcept { | ||||
class ret { | class ret { | ||||
} r{_sdists}; | } r{_sdists}; | ||||
return r; | return r; | ||||
} | } | ||||
std::vector<sdist> solve(const std::vector<dependency>& deps) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
bool operator()(const sdist& lhs, const sdist& rhs) const { | bool operator()(const sdist& lhs, const sdist& rhs) const { | ||||
return lhs.manifest.pk_id < rhs.manifest.pk_id; | return lhs.manifest.pk_id < rhs.manifest.pk_id; | ||||
} | } | ||||
template <typename Name, typename Version> | |||||
bool operator()(const sdist& lhs, const std::tuple<Name, Version>& rhs) const { | |||||
auto&& [name, ver] = rhs; | |||||
return lhs.manifest.pk_id < package_id{name, ver}; | |||||
bool operator()(const sdist& lhs, const package_id& rhs) const { | |||||
return lhs.manifest.pk_id < rhs; | |||||
} | } | ||||
template <typename Name, typename Version> | |||||
bool operator()(const std::tuple<Name, Version>& lhs, const sdist& rhs) const { | |||||
auto&& [name, ver] = lhs; | |||||
return package_id{name, ver} < rhs.manifest.pk_id; | |||||
bool operator()(const package_id& lhs, const sdist& rhs) const { | |||||
return lhs < rhs.manifest.pk_id; | |||||
} | } | ||||
using is_transparent = int; | using is_transparent = int; | ||||
} sdist_compare; | } sdist_compare; |
#include "./solve.hpp" | |||||
#include <pubgrub/solve.hpp> | |||||
#include <range/v3/range/conversion.hpp> | |||||
#include <range/v3/view/transform.hpp> | |||||
using namespace dds; | |||||
namespace { | |||||
struct req_type { | |||||
dependency dep; | |||||
using req_ref = const req_type&; | |||||
bool implied_by(req_ref other) const noexcept { | |||||
return dep.versions.contains(other.dep.versions); | |||||
} | |||||
bool excludes(req_ref other) const noexcept { | |||||
return dep.versions.disjoint(other.dep.versions); | |||||
} | |||||
std::optional<req_type> intersection(req_ref other) const noexcept { | |||||
auto range = dep.versions.intersection(other.dep.versions); | |||||
if (range.empty()) { | |||||
return std::nullopt; | |||||
} | |||||
return req_type{dependency{dep.name, std::move(range)}}; | |||||
} | |||||
std::optional<req_type> union_(req_ref other) const noexcept { | |||||
auto range = dep.versions.union_(other.dep.versions); | |||||
if (range.empty()) { | |||||
return std::nullopt; | |||||
} | |||||
return req_type{dependency{dep.name, std::move(range)}}; | |||||
} | |||||
std::optional<req_type> difference(req_ref other) const noexcept { | |||||
auto range = dep.versions.difference(other.dep.versions); | |||||
if (range.empty()) { | |||||
return std::nullopt; | |||||
} | |||||
return req_type{dependency{dep.name, std::move(range)}}; | |||||
} | |||||
auto key() const noexcept { return dep.name; } | |||||
friend bool operator==(req_ref lhs, req_ref rhs) noexcept { | |||||
return lhs.dep.name == rhs.dep.name && lhs.dep.versions == rhs.dep.versions; | |||||
} | |||||
}; | |||||
auto as_pkg_id(const req_type& req) { | |||||
const version_range_set& versions = req.dep.versions; | |||||
assert(versions.num_intervals() == 1); | |||||
return package_id{req.dep.name, (*versions.iter_intervals().begin()).low}; | |||||
} | |||||
struct solver_provider { | |||||
pkg_id_provider_fn& pkgs_for_name; | |||||
deps_provider_fn& deps_for_pkg; | |||||
mutable std::map<std::string, std::vector<package_id>> pkgs_by_name = {}; | |||||
std::optional<req_type> best_candidate(const req_type& req) const { | |||||
auto found = pkgs_by_name.find(req.dep.name); | |||||
if (found == pkgs_by_name.end()) { | |||||
found = pkgs_by_name.emplace(req.dep.name, pkgs_for_name(req.dep.name)).first; | |||||
} | |||||
auto& vec = found->second; | |||||
auto cand = std::find_if(vec.cbegin(), vec.cend(), [&](const package_id& pk) { | |||||
return req.dep.versions.contains(pk.version); | |||||
}); | |||||
if (cand == vec.cend()) { | |||||
return std::nullopt; | |||||
} | |||||
return req_type{dependency{cand->name, {cand->version, cand->version.next_after()}}}; | |||||
} | |||||
std::vector<req_type> requirements_of(const req_type& req) const { | |||||
auto pk_id = as_pkg_id(req); | |||||
auto deps = deps_for_pkg(pk_id); | |||||
return deps // | |||||
| ranges::views::transform([](const dependency& dep) { return req_type{dep}; }) // | |||||
| ranges::to_vector; | |||||
} | |||||
}; | |||||
} // namespace | |||||
std::vector<package_id> dds::solve(const std::vector<dependency>& deps, | |||||
pkg_id_provider_fn pkgs_prov, | |||||
deps_provider_fn deps_prov) { | |||||
auto wrap_req | |||||
= deps | ranges::v3::views::transform([](const dependency& dep) { return req_type{dep}; }); | |||||
auto solution = pubgrub::solve(wrap_req, solver_provider{pkgs_prov, deps_prov}); | |||||
return solution | ranges::views::transform(as_pkg_id) | ranges::to_vector; | |||||
} |
#pragma once | |||||
#include <dds/deps.hpp> | |||||
#include <dds/package_id.hpp> | |||||
#include <functional> | |||||
namespace dds { | |||||
using pkg_id_provider_fn = std::function<std::vector<package_id>(std::string_view)>; | |||||
using deps_provider_fn = std::function<std::vector<dependency>(const package_id& pk)>; | |||||
std::vector<package_id> | |||||
solve(const std::vector<dependency>& deps, pkg_id_provider_fn, deps_provider_fn); | |||||
} // namespace dds |