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