| #include <dds/build.hpp> | #include <dds/build.hpp> | ||||
| #include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
| #include <dds/repo/repo.hpp> | |||||
| #include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <dds/util/paths.hpp> | |||||
| #include <dds/util/signal.hpp> | |||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| struct common_flags { | struct common_flags { | ||||
| args::Command& cmd; | 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 { | struct common_project_flags { | ||||
| dds::fs::current_path()}; | 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 { | struct cli_sdist { | ||||
| cli_base& base; | cli_base& base; | ||||
| args::Command cmd{base.cmd_group, "sdist", "Create a source distribution of a project"}; | args::Command cmd{base.cmd_group, "sdist", "Create a source distribution of a project"}; | ||||
| dds::fs::current_path() / "project.dsd"}; | dds::fs::current_path() / "project.dsd"}; | ||||
| args::Flag force{cmd, "force", "Forcibly replace an existing result", {"force"}}; | 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() { | int run() { | ||||
| dds::sdist_params params; | dds::sdist_params params; | ||||
| params.project_dir = project.root.Get(); | params.project_dir = project.root.Get(); | ||||
| params.dest_path = out.Get(); | params.dest_path = out.Get(); | ||||
| params.force = force.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; | return 0; | ||||
| } | } | ||||
| }; | }; | ||||
| cli_base cli{parser}; | cli_base cli{parser}; | ||||
| cli_build build{cli}; | cli_build build{cli}; | ||||
| cli_sdist sdist{cli}; | cli_sdist sdist{cli}; | ||||
| cli_repo repo{cli}; | |||||
| try { | try { | ||||
| parser.ParseCLI(argc, argv); | parser.ParseCLI(argc, argv); | ||||
| } catch (const args::Help&) { | } catch (const args::Help&) { | ||||
| return build.run(); | return build.run(); | ||||
| } else if (sdist.cmd) { | } else if (sdist.cmd) { | ||||
| return sdist.run(); | return sdist.run(); | ||||
| } else if (repo.cmd) { | |||||
| return repo.run(); | |||||
| } else { | } else { | ||||
| assert(false); | assert(false); | ||||
| std::terminate(); | std::terminate(); | ||||
| } | } | ||||
| } catch (const dds::user_cancelled&) { | |||||
| spdlog::critical("Operation cancelled by user"); | |||||
| return 2; | |||||
| } catch (const std::exception& e) { | } catch (const std::exception& e) { | ||||
| spdlog::critical(e.what()); | spdlog::critical(e.what()); | ||||
| return 2; | return 2; |
| #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()); | |||||
| } |
| #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 |
| #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 |
| #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 |