Browse Source

Dependency downloading using `dds deps get`

default_compile_flags
vector-of-bool 5 years ago
parent
commit
69c7fd2112
10 changed files with 390 additions and 109 deletions
  1. +4
    -4
      src/dds/build.cpp
  2. +55
    -4
      src/dds/dds.main.cpp
  3. +4
    -32
      src/dds/deps.cpp
  4. +4
    -7
      src/dds/deps.hpp
  5. +141
    -0
      src/dds/repo/remote.cpp
  6. +73
    -0
      src/dds/repo/remote.hpp
  7. +51
    -44
      src/dds/repo/repo.cpp
  8. +36
    -14
      src/dds/repo/repo.hpp
  9. +18
    -0
      src/dds/sdist.hpp
  10. +4
    -4
      src/libman/parse.cpp

+ 4
- 4
src/dds/build.cpp View File

return usage_requirement_map::from_lm_index(idx); return usage_requirement_map::from_lm_index(idx);
} }


void prepare_catch2_driver(library_build_params& lib_params,
test_lib test_driver,
const build_params& params,
const package_manifest& man) {
void prepare_catch2_driver(library_build_params& lib_params,
test_lib test_driver,
const build_params& params,
const package_manifest&) {
fs::path test_include_root = params.out_root / "_test_inc"; fs::path test_include_root = params.out_root / "_test_inc";
lib_params.test_include_dirs.emplace_back(test_include_root); lib_params.test_include_dirs.emplace_back(test_include_root);



+ 55
- 4
src/dds/dds.main.cpp View File

#include <dds/build.hpp> #include <dds/build.hpp>
#include <dds/logging.hpp> #include <dds/logging.hpp>
#include <dds/repo/remote.hpp>
#include <dds/repo/repo.hpp> #include <dds/repo/repo.hpp>
#include <dds/sdist.hpp> #include <dds/sdist.hpp>
#include <dds/toolchain/from_dds.hpp> #include <dds/toolchain/from_dds.hpp>
auto same_name auto same_name
= [](auto&& a, auto&& b) { return a.manifest.name == b.manifest.name; }; = [](auto&& a, auto&& b) { return a.manifest.name == b.manifest.name; };


auto all_sdists = repo.load_sdists();
auto grp_by_name = all_sdists //
auto all = repo.iter_sdists();
auto grp_by_name = all //
| ranges::views::group_by(same_name) // | ranges::views::group_by(same_name) //
| ranges::views::transform(ranges::to_vector) // | ranges::views::transform(ranges::to_vector) //
| ranges::views::transform([](auto&& grp) { | ranges::views::transform([](auto&& grp) {
for (const auto& [name, grp] : grp_by_name) { for (const auto& [name, grp] : grp_by_name) {
spdlog::info("{}:", name); spdlog::info("{}:", name);
for (const dds::sdist& sd : grp) { for (const dds::sdist& sd : grp) {
spdlog::info(" - {}",
sd.manifest.version.to_string());
spdlog::info(" - {}", sd.manifest.version.to_string());
} }
} }


} }
} ls{*this}; } ls{*this};


struct {
cli_deps& parent;
args::Command cmd{parent.deps_group,
"get",
"Ensure we have local copies of the project dependencies"};
common_flags _common{cmd};

repo_where_flag repo_where{cmd};
path_flag remote_listing_file{
cmd,
"remote-listing",
"Path to a file containing listing of remote sdists and how to obtain them",
{'R', "remote-list"},
"remote.dds"};

int run() {
auto man = parent.load_package_manifest();
auto rd = dds::remote_directory::load_from_file(remote_listing_file.Get());
bool failed = false;
dds::repository::with_repository( //
repo_where.Get(),
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent,
[&](dds::repository repo) {
for (auto& dep : man.dependencies) {
auto exists = !!repo.find(dep.name, dep.version);
if (!exists) {
spdlog::info("Pull remote: {} {}", dep.name, dep.version.to_string());
auto opt_remote = rd.find(dep.name, dep.version);
if (opt_remote) {
auto tsd = opt_remote->pull_sdist();
repo.add_sdist(tsd.sdist, dds::if_exists::ignore);
} else {
spdlog::error("No remote listing for {} {}",
dep.name,
dep.version.to_string());
failed = true;
}
} else {
spdlog::info("Okay: {} {}", dep.name, dep.version.to_string());
}
}
});
if (failed) {
return 1;
}
return 0;
}
} get{*this};

struct { struct {
cli_deps& parent; cli_deps& parent;
args::Command cmd{parent.deps_group, "build", "Build project dependencies"}; args::Command cmd{parent.deps_group, "build", "Build project dependencies"};
return ls.run(); return ls.run();
} else if (build.cmd) { } else if (build.cmd) {
return build.run(); return build.run();
} else if (get.cmd) {
return get.run();
} }
std::terminate(); std::terminate();
} }

+ 4
- 32
src/dds/deps.cpp View File

} }


std::vector<sdist> dds::find_dependencies(const repository& repo, const dependency& dep) { std::vector<sdist> dds::find_dependencies(const repository& repo, const dependency& dep) {
auto all_dists = repo.load_sdists();
detail::sort_sdists(all_dists);
std::vector<sdist> acc; std::vector<sdist> acc;
detail::do_find_deps(all_dists, dep, acc);
detail::do_find_deps(repo, dep, acc);
return acc; return acc;
} }


auto tie_sdist(const sdist& sd) {
return std::tuple(sd.manifest.name, sd.manifest.version.to_string());
}

auto sdist_compare
= [](const sdist& lhs, const sdist& rhs) { return tie_sdist(lhs) < tie_sdist(rhs); };

void detail::sort_sdists(std::vector<sdist>& sd) { std::sort(sd.begin(), sd.end(), sdist_compare); }

namespace {

const sdist*
get_sdist(const std::vector<sdist>& sorted_sds, std::string_view name, std::string_view version) {
auto found
= std::partition_point(sorted_sds.begin(), sorted_sds.end(), [&](const auto& candidate) {
return tie_sdist(candidate) < std::tie(name, version);
});
if (found->manifest.name == name && found->manifest.version.to_string() == version) {
return &*found;
}
return nullptr;
}
} // namespace

void detail::do_find_deps(const std::vector<sdist>& sdists,
const dependency& dep,
std::vector<sdist>& sd) {
auto sdist_opt = get_sdist(sdists, dep.name, dep.version.to_string());
void detail::do_find_deps(const repository& repo, const dependency& dep, std::vector<sdist>& sd) {
auto sdist_opt = repo.find(dep.name, dep.version);
if (!sdist_opt) { if (!sdist_opt) {
throw std::runtime_error( throw std::runtime_error(
fmt::format("Unable to find dependency to satisfy requirement: {} {}", fmt::format("Unable to find dependency to satisfy requirement: {} {}",
} }
const sdist& new_sd = *sdist_opt; const sdist& new_sd = *sdist_opt;
for (const auto& inner_dep : new_sd.manifest.dependencies) { for (const auto& inner_dep : new_sd.manifest.dependencies) {
do_find_deps(sdists, inner_dep, sd);
do_find_deps(repo, inner_dep, sd);
} }
auto insert_point = std::partition_point(sd.begin(), sd.end(), [&](const sdist& cand) { auto insert_point = std::partition_point(sd.begin(), sd.end(), [&](const sdist& cand) {
return cand.path < new_sd.path; return cand.path < new_sd.path;

+ 4
- 7
src/dds/deps.hpp View File

#pragma once #pragma once


#include <dds/build/plan/full.hpp> #include <dds/build/plan/full.hpp>
#include <dds/repo/repo.hpp>


#include <semver/version.hpp> #include <semver/version.hpp>


namespace dds { namespace dds {


struct sdist; struct sdist;
class repository;


enum class version_strength { enum class version_strength {
exact, exact,


namespace detail { namespace detail {


void do_find_deps(const std::vector<sdist>&, const dependency& dep, std::vector<sdist>& acc);
void sort_sdists(std::vector<sdist>& sds);
void do_find_deps(const repository&, const dependency& dep, std::vector<sdist>& acc);


} // namespace detail } // namespace detail


template <typename Iter, typename Snt> template <typename Iter, typename Snt>
inline std::vector<sdist> find_dependencies(const repository& repo, Iter it, Snt stop) { inline std::vector<sdist> find_dependencies(const repository& repo, Iter it, Snt stop) {
std::vector<sdist> acc; std::vector<sdist> acc;
auto all_sds = repo.load_sdists();
detail::sort_sdists(all_sds);
while (it != stop) { while (it != stop) {
detail::do_find_deps(all_sds, *it++, acc);
detail::do_find_deps(repo, *it++, acc);
} }
return acc; return acc;
} }


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);


} // namespace dds
} // namespace dds

+ 141
- 0
src/dds/repo/remote.cpp View File

#include "./remote.hpp"

#include <dds/deps.hpp>
#include <dds/proc.hpp>
#include <dds/repo/repo.hpp>
#include <dds/sdist.hpp>
#include <dds/temp.hpp>
#include <dds/toolchain/toolchain.hpp>

#include <spdlog/spdlog.h>

#include <libman/parse.hpp>

#include <algorithm>

using namespace dds;

namespace {

struct read_listing_item {
std::string_view _key;
std::set<remote_listing, remote_listing_compare_t>& out;

bool operator()(std::string_view context, std::string_view key, std::string_view value) {
if (key != _key) {
return false;
}

auto nested = lm::nested_kvlist::parse(value);
auto name_ver_pair = split_shell_string(nested.primary);
if (name_ver_pair.size() != 2) {
throw std::runtime_error(
fmt::format("{}: Invalid Remote-Package identity: '{}'", context, nested.primary));
}
auto name = name_ver_pair[0];
auto version = semver::version::parse(name_ver_pair[1]);
put_listing(context, name, version, nested.pairs);
return true;
}

void put_listing(std::string_view context,
std::string name,
semver::version version,
const lm::pair_list& pairs) {
if (pairs.find("git")) {
std::string url;
std::string ref;
lm::read(fmt::format("{}: Parsing Git remote listing", context),
pairs,
lm::read_required("url", url),
lm::read_required("ref", ref),
lm::read_check_eq("git", ""),
lm::reject_unknown());
auto did_insert
= out.emplace(
remote_listing{std::move(name), version, git_remote_listing{url, ref}})
.second;
if (!did_insert) {
spdlog::warn("Duplicate remote package defintion for {} {}",
name,
version.to_string());
}
} else {
throw std::runtime_error(fmt::format("Unable to determine remote type of package {} {}",
name,
version.to_string()));
}
}
};

temporary_sdist do_pull_sdist(const git_remote_listing& git) {
auto tmpdir = dds::temporary_dir::create();
using namespace std::literals;
spdlog::info("Cloning repository: {} [{}] ...", git.url, git.ref);
auto command = {"git"s,
"clone"s,
"--depth=1"s,
"--branch"s,
git.ref,
git.url,
tmpdir.path().generic_string()};
auto git_res = run_proc(command);
if (!git_res.okay()) {
throw std::runtime_error(
fmt::format("Git clone operation failed [Git command: {}] [Exitted {}]:\n{}",
quote_command(command),
git_res.retc,
git_res.output));
}
spdlog::info("Create sdist from clone ...");
sdist_params params;
params.project_dir = tmpdir.path();
auto sd_tmp_dir = dds::temporary_dir::create();
params.dest_path = sd_tmp_dir.path();
auto sd = create_sdist(params);
return {sd_tmp_dir, sd};
}

} // namespace

temporary_sdist remote_listing::pull_sdist() const {
auto tsd = visit([](auto&& actual) { return do_pull_sdist(actual); });
if (tsd.sdist.manifest.name != name) {
throw std::runtime_error(
fmt::format("The name in the generated sdist ('{}') does not match the name listed in "
"the remote listing file (expected '{}')",
tsd.sdist.manifest.name,
name));
}
if (tsd.sdist.manifest.version != version) {
throw std::runtime_error(
fmt::format("The version of the generated sdist is '{}', which does not match the "
"expected version '{}'",
tsd.sdist.manifest.version.to_string(),
version.to_string()));
}
return tsd;
}

remote_directory remote_directory::load_from_file(path_ref filepath) {
auto kvs = lm::parse_file(filepath);
listing_set listings;
lm::read(fmt::format("Loading remote package listing from {}", filepath.string()),
kvs,
read_listing_item{"Remote-Package", listings},
lm::reject_unknown());
return {std::move(listings)};
}

const remote_listing* remote_directory::find(std::string_view name, semver::version ver) const
noexcept {
auto found = _remotes.find(std::tie(name, ver));
if (found == _remotes.end()) {
return nullptr;
}
return &*found;
}

void remote_directory::ensure_all_local(const repository&) const {
spdlog::critical("Dependency download is not fully implemented!");
}

+ 73
- 0
src/dds/repo/remote.hpp View File

#pragma once

#include <dds/util/fs.hpp>

#include <dds/sdist.hpp>
#include <dds/temp.hpp>
#include <semver/version.hpp>

#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <variant>

namespace dds {

struct temporary_sdist {
temporary_dir tmpdir;
dds::sdist sdist;
};

struct git_remote_listing {
std::string url;
std::string ref;

void clone(path_ref path) const;
};

struct remote_listing {
std::string name;
semver::version version;
std::variant<git_remote_listing> remote;

template <typename Func>
decltype(auto) visit(Func&& fn) const {
return std::visit(std::forward<Func>(fn), remote);
}

temporary_sdist pull_sdist() const;
};

inline constexpr struct remote_listing_compare_t {
using is_transparent = int;
auto tie(const remote_listing& rl) const { return std::tie(rl.name, rl.version); }
bool operator()(const remote_listing& lhs, const remote_listing& rhs) const {
return tie(lhs) < tie(rhs);
}
template <typename Name, typename Version>
bool operator()(const remote_listing& lhs, const std::tuple<Name, Version>& rhs) const {
return tie(lhs) < rhs;
}
template <typename Name, typename Version>
bool operator()(const std::tuple<Name, Version>& lhs, const remote_listing& rhs) const {
return lhs < tie(rhs);
}
} remote_listing_compare;

class remote_directory {
using listing_set = std::set<remote_listing, remote_listing_compare_t>;
listing_set _remotes;

remote_directory(listing_set s)
: _remotes(std::move(s)) {}

public:
static remote_directory load_from_file(path_ref);

void ensure_all_local(const class repository& repo) const;

const remote_listing* find(std::string_view name, semver::version ver) const noexcept;
};

} // namespace dds

+ 51
- 44
src/dds/repo/repo.cpp View File



#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>


#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; using namespace ranges;


namespace {

auto load_sdists(path_ref root) {
using namespace ranges;
using namespace ranges::views;

auto try_read_sdist = [](path_ref p) -> std::optional<sdist> {
if (starts_with(p.filename().string(), ".")) {
return std::nullopt;
}
try {
return sdist::from_directory(p);
} catch (const std::runtime_error& e) {
spdlog::error("Failed to load source distribution from directory '{}': {}",
p.string(),
e.what());
return std::nullopt;
}
};

return
// Get the top-level `name-version` dirs
fs::directory_iterator(root) //
// // Convert each dir into an `sdist` object
| transform(try_read_sdist) //
// // Drop items that failed to load
| filter([](auto&& opt) { return opt.has_value(); }) //
| transform([](auto&& opt) { return *opt; }) //
;
}

} // namespace

void repository::_log_blocking(path_ref dirpath) noexcept { void repository::_log_blocking(path_ref dirpath) noexcept {
spdlog::warn("Another process has the repository directory locked [{}]", dirpath.string()); spdlog::warn("Another process has the repository directory locked [{}]", dirpath.string());
spdlog::warn("Waiting for repository to be released..."); spdlog::warn("Waiting for repository to be released...");
} }


void repository::_init_repo_dir(path_ref dirpath) noexcept {
fs::create_directories(dirpath);
}
void repository::_init_repo_dir(path_ref dirpath) noexcept { fs::create_directories(dirpath); }


fs::path repository::default_local_path() noexcept { return dds_data_dir() / "repo"; } fs::path repository::default_local_path() noexcept { return dds_data_dir() / "repo"; }


repository repository::open_for_directory(path_ref dirpath) {
repository repository::_open_for_directory(bool writeable, path_ref dirpath) {
auto dist_dir = dirpath; auto dist_dir = dirpath;
auto entries = fs::directory_iterator(dist_dir) | to_vector;
return {dirpath};
auto entries = load_sdists(dirpath) | to<sdist_set>;
return {writeable, dirpath, std::move(entries)};
} }


void repository::add_sdist(const sdist& sd, if_exists ife_action) { void repository::add_sdist(const sdist& sd, if_exists ife_action) {
auto sd_dest
= _root / fmt::format("{}_{}", sd.manifest.name, sd.manifest.version.to_string());
if (!_write_enabled) {
spdlog::critical(
"DDS attempted to write into a repository that wasn't opened with a write-lock. This "
"is a hard bug and should be reported. For the safety and integrity of the local "
"repository, we'll hard-exit immediately.");
std::terminate();
}
auto sd_dest = _root / fmt::format("{}_{}", sd.manifest.name, sd.manifest.version.to_string());
if (fs::exists(sd_dest)) { if (fs::exists(sd_dest)) {
auto msg = fmt::format("Source distribution '{}' is already available in the local repo", auto msg = fmt::format("Source distribution '{}' is already available in the local repo",
sd.path.string()); sd.path.string());
spdlog::info("Source distribution '{}' successfully exported", sd.ident()); spdlog::info("Source distribution '{}' successfully exported", sd.ident());
} }


std::vector<sdist> repository::load_sdists() const {
using namespace ranges;
using namespace ranges::views;

auto try_read_sdist = [](path_ref p) -> std::optional<sdist> {
if (starts_with(p.filename().string(), ".")) {
return std::nullopt;
}
try {
return sdist::from_directory(p);
} catch (const std::runtime_error& e) {
spdlog::error("Failed to load source distribution from directory '{}': {}",
p.string(),
e.what());
return std::nullopt;
}
};

return
// Get the top-level `name-version` dirs
fs::directory_iterator(_root) //
// // Convert each dir into an `sdist` object
| transform(try_read_sdist) //
// // Drop items that failed to load
| filter([](auto&& opt) { return opt.has_value(); }) //
| transform([](auto&& opt) { return *opt; }) //
| to_vector //
;
}

std::optional<sdist> repository::get_sdist(std::string_view name, std::string_view version) const {
auto expect_path = _root / fmt::format("{}_{}", name, version);
if (!fs::is_directory(expect_path)) {
return std::nullopt;
const sdist* repository::find(std::string_view name, semver::version ver) const noexcept {
auto found = _sdists.find(std::tie(name, ver));
if (found == _sdists.end()) {
return nullptr;
} }

return sdist::from_directory(expect_path);
return &*found;
} }

+ 36
- 14
src/dds/repo/repo.hpp View File

#pragma once #pragma once


#include <dds/sdist.hpp>
#include <dds/util/flock.hpp> #include <dds/util/flock.hpp>
#include <dds/util/fs.hpp> #include <dds/util/fs.hpp>


#include <functional> #include <functional>
#include <optional> #include <optional>
#include <set>
#include <shared_mutex> #include <shared_mutex>
#include <vector> #include <vector>


namespace dds { namespace dds {


struct sdist;

enum repo_flags { enum repo_flags {
none = 0b00, none = 0b00,
read = none, read = none,
} }


class repository { class repository {
fs::path _root;
using sdist_set = std::set<sdist, sdist_compare_t>;

bool _write_enabled = false;
fs::path _root;
sdist_set _sdists;


repository(path_ref p)
: _root(p) {}
repository(bool writeable, path_ref p, sdist_set sds)
: _write_enabled(writeable)
, _root(p)
, _sdists(std::move(sds)) {}


static void _log_blocking(path_ref dir) noexcept;
static void _init_repo_dir(path_ref dir) noexcept;
static void _log_blocking(path_ref dir) noexcept;
static void _init_repo_dir(path_ref dir) noexcept;
static repository _open_for_directory(bool writeable, path_ref);


public: public:
template <typename Func> template <typename Func>
std::shared_lock shared_lk{mut, std::defer_lock}; std::shared_lock shared_lk{mut, std::defer_lock};
std::unique_lock excl_lk{mut, std::defer_lock}; std::unique_lock excl_lk{mut, std::defer_lock};


if (flags & repo_flags::write_lock) {
bool writeable = (flags & repo_flags::write_lock) != repo_flags::none;

if (writeable) {
if (!excl_lk.try_lock()) { if (!excl_lk.try_lock()) {
_log_blocking(dirpath); _log_blocking(dirpath);
excl_lk.lock(); excl_lk.lock();
} }
} }


return std::invoke((Func &&) fn, open_for_directory(dirpath));
auto repo = _open_for_directory(writeable, dirpath);
return std::invoke((Func &&) fn, std::move(repo));
} }


static repository open_for_directory(path_ref);

static fs::path default_local_path() noexcept; static fs::path default_local_path() noexcept;


void add_sdist(const sdist&, if_exists = if_exists::throw_exc);
std::optional<sdist> get_sdist(std::string_view name, std::string_view version) const;
std::vector<sdist> load_sdists() const;
void add_sdist(const sdist&, if_exists = if_exists::throw_exc);

const sdist* find(std::string_view name, semver::version ver) const noexcept;

auto iter_sdists() const noexcept {
class ret {
const sdist_set& s;

public:
ret(const sdist_set& s)
: s(s) {}

auto begin() const { return s.cbegin(); }
auto end() const { return s.cend(); }
} r{_sdists};
return r;
}
}; };


} // namespace dds } // namespace dds

+ 18
- 0
src/dds/sdist.hpp View File

#pragma once #pragma once


#include <tuple>

#include <dds/package_manifest.hpp> #include <dds/package_manifest.hpp>
#include <dds/util/fs.hpp> #include <dds/util/fs.hpp>


} }
}; };


inline constexpr struct sdist_compare_t {
bool operator()(const sdist& lhs, const sdist& rhs) const {
return std::tie(lhs.manifest.name, lhs.manifest.version)
< std::tie(rhs.manifest.name, rhs.manifest.version);
}
template <typename Name, typename Version>
bool operator()(const sdist& lhs, const std::tuple<Name, Version>& rhs) const {
return std::tie(lhs.manifest.name, lhs.manifest.version) < rhs;
}
template <typename Name, typename Version>
bool operator()(const std::tuple<Name, Version>& lhs, const sdist& rhs) const {
return lhs < std::tie(rhs.manifest.name, rhs.manifest.version);
}
using is_transparent = int;
} sdist_compare;

sdist create_sdist(const sdist_params&); sdist create_sdist(const sdist_params&);
sdist create_sdist_in_dir(path_ref, const sdist_params&); sdist create_sdist_in_dir(path_ref, const sdist_params&);



+ 4
- 4
src/libman/parse.cpp View File

} }


nested_kvlist nested_kvlist::parse(const std::string_view line_) { nested_kvlist nested_kvlist::parse(const std::string_view line_) {
const auto line = trim(line_);
const auto line = trim_view(line_);
const auto semi_pos = line.find(';'); const auto semi_pos = line.find(';');
const auto primary = trim(line.substr(0, semi_pos));
auto tail = semi_pos == line.npos ? ""sv : trim(line.substr(semi_pos + 1));
const auto primary = trim_view(line.substr(0, semi_pos));
auto tail = semi_pos == line.npos ? ""sv : trim_view(line.substr(semi_pos + 1));


std::vector<pair> pairs; std::vector<pair> pairs;
while (!tail.empty()) { while (!tail.empty()) {
if (space_pos == tail.npos) { if (space_pos == tail.npos) {
break; break;
} }
tail = trim(tail.substr(space_pos + 1));
tail = trim_view(tail.substr(space_pos + 1));
} }


return nested_kvlist{std::string(primary), pair_list{std::move(pairs)}}; return nested_kvlist{std::string(primary), pair_list{std::move(pairs)}};

Loading…
Cancel
Save