#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 |