| @@ -1,7 +1,10 @@ | |||
| #include <dds/build.hpp> | |||
| #include <dds/logging.hpp> | |||
| #include <dds/repo/repo.hpp> | |||
| #include <dds/sdist.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| #include <dds/util/paths.hpp> | |||
| #include <dds/util/signal.hpp> | |||
| #include <libman/parse.hpp> | |||
| @@ -25,7 +28,7 @@ struct cli_base { | |||
| struct common_flags { | |||
| args::Command& cmd; | |||
| args::HelpFlag _help{cmd, "help", "Print this hellp message and exit", {'h', "help"}}; | |||
| args::HelpFlag _help{cmd, "help", "Print this help message and exit", {'h', "help"}}; | |||
| }; | |||
| struct common_project_flags { | |||
| @@ -38,6 +41,55 @@ struct common_project_flags { | |||
| dds::fs::current_path()}; | |||
| }; | |||
| struct cli_repo { | |||
| cli_base& base; | |||
| args::Command cmd{base.cmd_group, "repo", "Manage the package repository"}; | |||
| common_flags _common{cmd}; | |||
| path_flag where{cmd, "dir", "Directory in which to initialize the repository", {'d', "dir"}}; | |||
| args::Group repo_group{cmd, "Repo subcommands"}; | |||
| struct { | |||
| cli_repo& parent; | |||
| args::Command cmd{parent.repo_group, "ls", "List repository contents"}; | |||
| common_flags _common{cmd}; | |||
| int run() { | |||
| return dds::repository::with_repository(dds::repository::default_local_path(), | |||
| dds::repo_flags::none, | |||
| [&](auto) { return 0; }); | |||
| } | |||
| } ls{*this}; | |||
| struct { | |||
| cli_repo& parent; | |||
| args::Command cmd{parent.repo_group, "init", "Initialize a directory as a repository"}; | |||
| common_flags _common{cmd}; | |||
| int run() { | |||
| if (parent.where.Get().empty()) { | |||
| throw args::ParseError("The --dir flag is required"); | |||
| } | |||
| auto repo_dir = dds::fs::absolute(parent.where.Get()); | |||
| dds::repository::with_repository(repo_dir, dds::repo_flags::create_if_absent, [](auto) { | |||
| }); | |||
| return 0; | |||
| } | |||
| } init{*this}; | |||
| int run() { | |||
| if (ls.cmd) { | |||
| return ls.run(); | |||
| } else if (init.cmd) { | |||
| return init.run(); | |||
| } else { | |||
| assert(false); | |||
| std::terminate(); | |||
| } | |||
| } | |||
| }; | |||
| struct cli_sdist { | |||
| cli_base& base; | |||
| args::Command cmd{base.cmd_group, "sdist", "Create a source distribution of a project"}; | |||
| @@ -53,13 +105,25 @@ struct cli_sdist { | |||
| dds::fs::current_path() / "project.dsd"}; | |||
| args::Flag force{cmd, "force", "Forcibly replace an existing result", {"force"}}; | |||
| args::Flag export_{cmd, | |||
| "export", | |||
| "Export the result into the local repository", | |||
| {'E', "export"}}; | |||
| int run() { | |||
| dds::sdist_params params; | |||
| params.project_dir = project.root.Get(); | |||
| params.dest_path = out.Get(); | |||
| params.force = force.Get(); | |||
| dds::create_sdist(params); | |||
| auto sdist = dds::create_sdist(params); | |||
| if (export_.Get()) { | |||
| dds::repository::with_repository( // | |||
| dds::repository::default_local_path(), | |||
| dds::repo_flags::create_if_absent | dds::repo_flags::write_lock, | |||
| [&](dds::repository repo) { // | |||
| repo.add_sdist(sdist); | |||
| }); | |||
| } | |||
| return 0; | |||
| } | |||
| }; | |||
| @@ -161,6 +225,7 @@ int main(int argc, char** argv) { | |||
| cli_base cli{parser}; | |||
| cli_build build{cli}; | |||
| cli_sdist sdist{cli}; | |||
| cli_repo repo{cli}; | |||
| try { | |||
| parser.ParseCLI(argc, argv); | |||
| } catch (const args::Help&) { | |||
| @@ -179,10 +244,15 @@ int main(int argc, char** argv) { | |||
| return build.run(); | |||
| } else if (sdist.cmd) { | |||
| return sdist.run(); | |||
| } else if (repo.cmd) { | |||
| return repo.run(); | |||
| } else { | |||
| assert(false); | |||
| std::terminate(); | |||
| } | |||
| } catch (const dds::user_cancelled&) { | |||
| spdlog::critical("Operation cancelled by user"); | |||
| return 2; | |||
| } catch (const std::exception& e) { | |||
| spdlog::critical(e.what()); | |||
| return 2; | |||
| @@ -0,0 +1,47 @@ | |||
| #include "./repo.hpp" | |||
| #include <dds/sdist.hpp> | |||
| #include <dds/util/paths.hpp> | |||
| #include <spdlog/spdlog.h> | |||
| #include <range/v3/range/conversion.hpp> | |||
| using namespace dds; | |||
| using namespace ranges; | |||
| void repository::_log_blocking(path_ref dirpath) noexcept { | |||
| spdlog::warn("Another process has the repository directory locked [{}]", dirpath.string()); | |||
| spdlog::warn("Waiting for repository to be released..."); | |||
| } | |||
| void repository::_init_repo_dir(path_ref dirpath) noexcept { | |||
| fs::create_directories(dirpath / "dist"); | |||
| } | |||
| fs::path repository::default_local_path() noexcept { return dds_data_dir() / "repo"; } | |||
| repository repository::open_for_directory(path_ref dirpath) { | |||
| auto dist_dir = dirpath / "dist"; | |||
| auto entries = fs::directory_iterator(dist_dir) | to_vector; | |||
| return {dirpath}; | |||
| } | |||
| void repository::add_sdist(const sdist& sd) { | |||
| auto sd_dest = _root / "dist" / sd.name() / sd.version() / sd.hash(); | |||
| if (fs::exists(sd_dest)) { | |||
| spdlog::info("Source distribution '{}' is already available in the local repo", | |||
| sd.path().string()); | |||
| return; | |||
| } | |||
| auto tmp_copy = sd_dest; | |||
| tmp_copy.replace_filename(".tmp-import"); | |||
| if (fs::exists(tmp_copy)) { | |||
| fs::remove_all(tmp_copy); | |||
| } | |||
| fs::create_directories(tmp_copy.parent_path()); | |||
| fs::copy(sd.path(), tmp_copy, fs::copy_options::recursive); | |||
| fs::rename(tmp_copy, sd_dest); | |||
| spdlog::info("Source distribution '{}' successfully exported", sd.ident()); | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| #pragma once | |||
| #include <dds/util/flock.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| #include <cassert> | |||
| #include <functional> | |||
| #include <shared_mutex> | |||
| namespace dds { | |||
| class sdist; | |||
| enum repo_flags { | |||
| none = 0b00, | |||
| create_if_absent = 0b01, | |||
| write_lock = 0b10, | |||
| }; | |||
| inline repo_flags operator|(repo_flags a, repo_flags b) { | |||
| return static_cast<repo_flags>(int(a) | int(b)); | |||
| } | |||
| class repository { | |||
| fs::path _root; | |||
| repository(path_ref p) | |||
| : _root(p) {} | |||
| static void _log_blocking(path_ref dir) noexcept; | |||
| static void _init_repo_dir(path_ref dir) noexcept; | |||
| public: | |||
| template <typename Func> | |||
| static decltype(auto) with_repository(path_ref dirpath, repo_flags flags, Func&& fn) { | |||
| if (!fs::exists(dirpath)) { | |||
| if (flags & repo_flags::create_if_absent) { | |||
| _init_repo_dir(dirpath); | |||
| } | |||
| } | |||
| shared_file_mutex mut{dirpath / ".lock"}; | |||
| std::shared_lock shared_lk{mut, std::defer_lock}; | |||
| std::unique_lock excl_lk{mut, std::defer_lock}; | |||
| if (flags & repo_flags::write_lock) { | |||
| if (!excl_lk.try_lock()) { | |||
| _log_blocking(dirpath); | |||
| excl_lk.lock(); | |||
| } | |||
| } else { | |||
| if (!shared_lk.try_lock()) { | |||
| _log_blocking(dirpath); | |||
| shared_lk.lock(); | |||
| } | |||
| } | |||
| return std::invoke((Func &&) fn, open_for_directory(dirpath)); | |||
| } | |||
| static repository open_for_directory(path_ref); | |||
| static fs::path default_local_path() noexcept; | |||
| void add_sdist(const sdist&); | |||
| }; | |||
| } // namespace dds | |||
| @@ -0,0 +1,28 @@ | |||
| #pragma once | |||
| #include <dds/util/fs.hpp> | |||
| namespace dds { | |||
| class shared_file_mutex { | |||
| fs::path _path; | |||
| void* _lock_data = nullptr; | |||
| public: | |||
| shared_file_mutex(path_ref p); | |||
| shared_file_mutex(const shared_file_mutex&) = delete; | |||
| ~shared_file_mutex(); | |||
| path_ref path() const noexcept { return _path; } | |||
| bool try_lock() noexcept; | |||
| bool try_lock_shared() noexcept; | |||
| void lock(); | |||
| void lock_shared(); | |||
| void unlock(); | |||
| void unlock_shared(); | |||
| }; | |||
| } // namespace dds | |||
| @@ -0,0 +1,90 @@ | |||
| #ifndef _WIN32 | |||
| #include "./flock.hpp" | |||
| #include <dds/util/signal.hpp> | |||
| #include <spdlog/fmt/fmt.h> | |||
| #include <fcntl.h> | |||
| #include <sys/file.h> | |||
| #include <unistd.h> | |||
| using namespace dds; | |||
| namespace { | |||
| struct lock_data { | |||
| int fd; | |||
| bool do_lock(int fcntl_kind, short int lock_kind, path_ref p) { | |||
| struct ::flock lk = {}; | |||
| lk.l_type = lock_kind; | |||
| lk.l_len = 0; | |||
| lk.l_whence = SEEK_SET; | |||
| lk.l_start = 0; | |||
| auto rc = ::fcntl(fd, fcntl_kind, &lk); | |||
| if (rc == -1) { | |||
| cancellation_point(); | |||
| if (errno == EAGAIN || errno == EACCES) { | |||
| return false; | |||
| } | |||
| throw std::system_error(std::error_code(errno, std::system_category()), | |||
| fmt::format("Failed to modify file lock [{}]", p.string())); | |||
| } | |||
| return true; | |||
| } | |||
| }; | |||
| } // namespace | |||
| #define THIS_DATA static_cast<lock_data*>(_lock_data) | |||
| shared_file_mutex::shared_file_mutex(path_ref filepath) | |||
| : _path{filepath} { | |||
| int fd = ::open(_path.string().c_str(), O_CREAT | O_CLOEXEC | O_RDWR, 0b110'100'100); | |||
| if (fd < 0) { | |||
| throw std::system_error(std::error_code(errno, std::system_category()), | |||
| fmt::format("Failed to open file for locking [{}]", | |||
| _path.string())); | |||
| } | |||
| _lock_data = new lock_data(lock_data{fd}); | |||
| } | |||
| #define MY_LOCK_DATA (*static_cast<lock_data*>(_lock_data)) | |||
| shared_file_mutex::~shared_file_mutex() { | |||
| assert(_lock_data); | |||
| ::close(MY_LOCK_DATA.fd); | |||
| delete &MY_LOCK_DATA; | |||
| _lock_data = nullptr; | |||
| } | |||
| bool shared_file_mutex::try_lock() noexcept { | |||
| // Attempt to take an exclusive lock | |||
| return MY_LOCK_DATA.do_lock(F_SETLK, F_WRLCK, _path); | |||
| } | |||
| bool shared_file_mutex::try_lock_shared() noexcept { | |||
| // Take a non-exclusive lock | |||
| return MY_LOCK_DATA.do_lock(F_SETLK, F_RDLCK, _path); | |||
| } | |||
| void shared_file_mutex::lock() { | |||
| // Blocking exclusive lock | |||
| MY_LOCK_DATA.do_lock(F_SETLKW, F_WRLCK, _path); | |||
| } | |||
| void shared_file_mutex::lock_shared() { | |||
| // Blocking shared lock | |||
| MY_LOCK_DATA.do_lock(F_SETLKW, F_RDLCK, _path); | |||
| } | |||
| void shared_file_mutex::unlock() { | |||
| // Unlock | |||
| MY_LOCK_DATA.do_lock(F_SETLK, F_UNLCK, _path); | |||
| } | |||
| void shared_file_mutex::unlock_shared() { unlock(); } | |||
| #endif | |||