From d6db197a6ddacc54470e41434eb7cf88e488cdd0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 25 Nov 2020 10:56:18 -0500 Subject: [PATCH] split python tool, comments simplified process_runner renamed git_tool to git_wrap, it's just too confusing to have git that's a tool and a git_tool that's not a tool --- src/cmd/git.cpp | 10 +- src/cmd/pr.cpp | 4 +- src/cmd/release.cpp | 2 +- src/core/paths.cpp | 2 +- src/core/paths.h | 2 +- src/tasks/boost_di.cpp | 2 +- src/tasks/gtest.cpp | 2 +- src/tasks/installer.cpp | 2 +- src/tasks/libffi.cpp | 2 +- src/tasks/lz4.cpp | 2 +- src/tasks/modorganizer.cpp | 4 +- src/tasks/ncc.cpp | 2 +- src/tasks/nmm.cpp | 2 +- src/tasks/python.cpp | 2 +- src/tasks/spdlog.cpp | 2 +- src/tasks/usvfs.cpp | 2 +- src/tools/git.cpp | 72 ++--- src/tools/git.h | 4 +- src/tools/patcher.cpp | 4 +- src/tools/process_runner.cpp | 61 +--- src/tools/python.cpp | 192 ++++++++++++ src/tools/tools.cpp | 196 +----------- src/tools/tools.h | 578 ++++++++++++++++++++++++++++++++--- vs/mob.vcxproj | 1 + vs/mob.vcxproj.filters | 3 + 25 files changed, 806 insertions(+), 349 deletions(-) create mode 100644 src/tools/python.cpp diff --git a/src/cmd/git.cpp b/src/cmd/git.cpp index f617d56..e36fefe 100644 --- a/src/cmd/git.cpp +++ b/src/cmd/git.cpp @@ -176,8 +176,8 @@ void git_command::do_set_remotes() void git_command::do_set_remotes(const fs::path& r) { u8cout << "setting up " << path_to_utf8(r.filename()) << "\n"; - git_tool(r).set_credentials(username_, email_); - git_tool(r).set_remote(username_, key_, nopush_, push_default_); + git_wrap(r).set_credentials(username_, email_); + git_wrap(r).set_remote(username_, key_, nopush_, push_default_); } void git_command::do_add_remote() @@ -202,7 +202,7 @@ void git_command::do_add_remote() void git_command::do_add_remote(const fs::path& r) { u8cout << path_to_utf8(r.filename()) << "\n"; - git_tool(r).add_remote(remote_, username_, key_, push_default_); + git_wrap(r).add_remote(remote_, username_, key_, push_default_); } void git_command::do_ignore_ts() @@ -228,7 +228,7 @@ void git_command::do_ignore_ts() void git_command::do_ignore_ts(const fs::path& r) { u8cout << path_to_utf8(r.filename()) << "\n"; - git_tool(r).ignore_ts(tson_); + git_wrap(r).ignore_ts(tson_); } void git_command::do_branches() @@ -237,7 +237,7 @@ void git_command::do_branches() for (auto&& r : get_repos()) { - const auto b = git_tool(r).current_branch(); + const auto b = git_wrap(r).current_branch(); if (b == "master" && !all_branches_) continue; diff --git a/src/cmd/pr.cpp b/src/cmd/pr.cpp index dd8c19e..5de8259 100644 --- a/src/cmd/pr.cpp +++ b/src/cmd/pr.cpp @@ -133,7 +133,7 @@ int pr_command::pull() << "checking out pr " << pr.number << " " << "in " << task->name() << "\n"; - git_tool g(task->this_source_path()); + git_wrap g(task->this_source_path()); g.fetch( task->git_url().string(), @@ -180,7 +180,7 @@ int pr_command::revert() u8cout << "reverting " << task->name() << " to master\n"; - git_tool(task->this_source_path()).checkout("master"); + git_wrap(task->this_source_path()).checkout("master"); } return 0; diff --git a/src/cmd/release.cpp b/src/cmd/release.cpp index 651d69b..649ce51 100644 --- a/src/cmd/release.cpp +++ b/src/cmd/release.cpp @@ -330,7 +330,7 @@ void release_command::check_repos_for_branch() { const auto* o = dynamic_cast(t); - if (!git_tool::remote_branch_exists(o->git_url(), branch_)) + if (!git_wrap::remote_branch_exists(o->git_url(), branch_)) { gcx().error(context::generic, "branch {} doesn't exist in the {} repo", diff --git a/src/core/paths.cpp b/src/core/paths.cpp index 8adacc8..15d7102 100644 --- a/src/core/paths.cpp +++ b/src/core/paths.cpp @@ -247,7 +247,7 @@ fs::path find_vs() if (output.empty()) gcx().bail_out(context::conf, "vswhere failed"); - const auto lines = split(path_to_utf8(output), "\r\n"); + const auto lines = split(output, "\r\n"); if (lines.empty()) { diff --git a/src/core/paths.h b/src/core/paths.h index 68c8a81..972c6f5 100644 --- a/src/core/paths.h +++ b/src/core/paths.h @@ -36,7 +36,7 @@ fs::path find_program_files_x64(); fs::path find_vs(); // returns the absolute path to Qt's root directory, the one that contains -// the msvc_xxx directory; bails if not found +// bin, include, etc.; bails if not found // fs::path find_qt(); diff --git a/src/tasks/boost_di.cpp b/src/tasks/boost_di.cpp index a24e1e1..3c99f23 100644 --- a/src/tasks/boost_di.cpp +++ b/src/tasks/boost_di.cpp @@ -30,7 +30,7 @@ void boost_di::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } }); diff --git a/src/tasks/gtest.cpp b/src/tasks/gtest.cpp index 8a57ea5..38e642d 100644 --- a/src/tasks/gtest.cpp +++ b/src/tasks/gtest.cpp @@ -30,7 +30,7 @@ void gtest::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tasks/installer.cpp b/src/tasks/installer.cpp index aeb2829..664c19e 100644 --- a/src/tasks/installer.cpp +++ b/src/tasks/installer.cpp @@ -29,7 +29,7 @@ void installer::do_clean(clean c) instrument([&] { if (is_set(c, clean::reclone)) - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); if (is_set(c, clean::rebuild)) op::delete_directory(cx(), conf().path().install_installer()); diff --git a/src/tasks/libffi.cpp b/src/tasks/libffi.cpp index f17c083..b9dbc10 100644 --- a/src/tasks/libffi.cpp +++ b/src/tasks/libffi.cpp @@ -30,7 +30,7 @@ void libffi::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } }); diff --git a/src/tasks/lz4.cpp b/src/tasks/lz4.cpp index 7719c15..b309b75 100644 --- a/src/tasks/lz4.cpp +++ b/src/tasks/lz4.cpp @@ -44,7 +44,7 @@ void lz4::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tasks/modorganizer.cpp b/src/tasks/modorganizer.cpp index 5a937f6..3983d5c 100644 --- a/src/tasks/modorganizer.cpp +++ b/src/tasks/modorganizer.cpp @@ -101,7 +101,7 @@ void modorganizer::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), this_source_path()); + git_wrap::delete_directory(cx(), this_source_path()); return; } @@ -211,7 +211,7 @@ void modorganizer::initialize_super(const fs::path& super_root) cx().trace(context::generic, "checking super"); - git_tool g(super_root); + git_wrap g(super_root); if (g.is_git_repo()) { diff --git a/src/tasks/ncc.cpp b/src/tasks/ncc.cpp index 7163ba9..f8b6fcd 100644 --- a/src/tasks/ncc.cpp +++ b/src/tasks/ncc.cpp @@ -31,7 +31,7 @@ void ncc::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tasks/nmm.cpp b/src/tasks/nmm.cpp index 97a3b7a..35e33ca 100644 --- a/src/tasks/nmm.cpp +++ b/src/tasks/nmm.cpp @@ -30,7 +30,7 @@ void nmm::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tasks/python.cpp b/src/tasks/python.cpp index 0a9ee17..013b873 100644 --- a/src/tasks/python.cpp +++ b/src/tasks/python.cpp @@ -83,7 +83,7 @@ void python::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tasks/spdlog.cpp b/src/tasks/spdlog.cpp index b7f23ee..fd76c56 100644 --- a/src/tasks/spdlog.cpp +++ b/src/tasks/spdlog.cpp @@ -30,7 +30,7 @@ void spdlog::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } }); diff --git a/src/tasks/usvfs.cpp b/src/tasks/usvfs.cpp index 2dce17c..aaff8de 100644 --- a/src/tasks/usvfs.cpp +++ b/src/tasks/usvfs.cpp @@ -50,7 +50,7 @@ void usvfs::do_clean(clean c) { if (is_set(c, clean::reclone)) { - git_tool::delete_directory(cx(), source_path()); + git_wrap::delete_directory(cx(), source_path()); return; } diff --git a/src/tools/git.cpp b/src/tools/git.cpp index bd27212..8563246 100644 --- a/src/tools/git.cpp +++ b/src/tools/git.cpp @@ -34,7 +34,7 @@ void for_each_ts(const fs::path& root, F&& f) .set("GIT_TERMINAL_PROMPT", "0"); return std::move(process() - .binary(git_tool::binary()) + .binary(git_wrap::binary()) .env(e)); } @@ -287,17 +287,17 @@ void for_each_ts(const fs::path& root, F&& f) namespace mob { -git_tool::git_tool(fs::path root, basic_process_runner* runner) +git_wrap::git_wrap(fs::path root, basic_process_runner* runner) : root_(std::move(root)), runner_(runner) { } -fs::path git_tool::binary() +fs::path git_wrap::binary() { return conf().tool().get("git"); } -int git_tool::run(process&& p) +int git_wrap::run(process&& p) { if (runner_) return runner_->execute_and_join(p); @@ -305,7 +305,7 @@ int git_tool::run(process&& p) return p.run_and_join(); } -int git_tool::run(process& p) +int git_wrap::run(process& p) { if (runner_) return runner_->execute_and_join(p); @@ -313,7 +313,7 @@ int git_tool::run(process& p) return p.run_and_join(); } -const context& git_tool::cx() +const context& git_wrap::cx() { if (runner_) return runner_->cx(); @@ -321,17 +321,17 @@ const context& git_tool::cx() return gcx(); } -void git_tool::clone(const mob::url& url, const std::string& branch, bool shallow) +void git_wrap::clone(const mob::url& url, const std::string& branch, bool shallow) { run(details::clone(root_, url, branch, shallow)); } -void git_tool::pull(const mob::url& url, const std::string& branch) +void git_wrap::pull(const mob::url& url, const std::string& branch) { run(details::pull(root_, url, branch)); } -void git_tool::set_credentials(const std::string& username, const std::string& email) +void git_wrap::set_credentials(const std::string& username, const std::string& email) { cx().debug(context::generic, "setting up credentials"); @@ -342,7 +342,7 @@ void git_tool::set_credentials(const std::string& username, const std::string& e set_config("user.email", email); } -void git_tool::set_remote( +void git_wrap::set_remote( std::string org, std::string key, bool no_push_upstream, bool push_default_origin) { @@ -362,27 +362,27 @@ void git_tool::set_remote( add_remote("origin", org, key, push_default_origin, {}, gf); } -void git_tool::rename_remote(const std::string& from, const std::string& to) +void git_wrap::rename_remote(const std::string& from, const std::string& to) { run(details::rename_remote(root_, from, to)); } -void git_tool::set_config(const std::string& key, const std::string& value) +void git_wrap::set_config(const std::string& key, const std::string& value) { run(details::set_config(root_, key, value)); } -void git_tool::set_remote_push(const std::string& remote, const std::string& url) +void git_wrap::set_remote_push(const std::string& remote, const std::string& url) { run(details::set_remote_push(root_, remote, url)); } -void git_tool::set_assume_unchanged(const fs::path& file, bool on) +void git_wrap::set_assume_unchanged(const fs::path& file, bool on) { run(details::set_assume_unchanged(root_, file, on)); } -void git_tool::ignore_ts(bool b) +void git_wrap::ignore_ts(bool b) { details::for_each_ts(root_, [&](auto&& p) { @@ -400,7 +400,7 @@ void git_tool::ignore_ts(bool b) }); } -void git_tool::revert_ts() +void git_wrap::revert_ts() { details::for_each_ts(root_, [&](auto&& p) { @@ -418,17 +418,17 @@ void git_tool::revert_ts() }); } -bool git_tool::is_tracked(const fs::path& file) +bool git_wrap::is_tracked(const fs::path& file) { return (run(details::is_tracked(root_, file)) == 0); } -bool git_tool::has_remote(const std::string& name) +bool git_wrap::has_remote(const std::string& name) { return (run(details::has_remote(root_, name)) == 0); } -void git_tool::add_remote( +void git_wrap::add_remote( const std::string& remote_name, const std::string& username, const std::string& key, bool push_default, const std::string& url_pattern, const std::string& opt_git_file) @@ -450,41 +450,41 @@ void git_tool::add_remote( } } -void git_tool::init_repo() +void git_wrap::init_repo() { run(details::init(root_)); } -void git_tool::apply(const std::string& diff) +void git_wrap::apply(const std::string& diff) { run(details::apply(root_, diff)); } -void git_tool::fetch(const std::string& remote, const std::string& branch) +void git_wrap::fetch(const std::string& remote, const std::string& branch) { run(details::fetch(root_, remote, branch)); } -void git_tool::checkout(const std::string& what) +void git_wrap::checkout(const std::string& what) { run(details::checkout(root_, what)); } -std::string git_tool::current_branch() +std::string git_wrap::current_branch() { auto p = details::current_branch(root_); run(p); return trim_copy(p.stdout_string()); } -void git_tool::add_submodule( +void git_wrap::add_submodule( const std::string& branch, const std::string& submodule, const mob::url& url) { run(details::add_submodule(root_, branch, submodule, url)); } -std::string git_tool::git_file() +std::string git_wrap::git_file() { auto p = details::git_file(root_); run(p); @@ -508,9 +508,9 @@ std::string git_tool::git_file() return s; } -void git_tool::delete_directory(const context& cx, const fs::path& dir) +void git_wrap::delete_directory(const context& cx, const fs::path& dir) { - git_tool g(dir); + git_wrap g(dir); if (!conf().global().get("ignore_uncommitted")) { @@ -535,30 +535,30 @@ void git_tool::delete_directory(const context& cx, const fs::path& dir) op::delete_directory(cx, dir, op::optional); } -bool git_tool::is_git_repo() +bool git_wrap::is_git_repo() { return (run(details::is_repo(root_)) == 0); } -bool git_tool::remote_branch_exists(const mob::url& u, const std::string& name) +bool git_wrap::remote_branch_exists(const mob::url& u, const std::string& name) { return (details::remote_branch_exists(u, name).run_and_join() == 0); } -bool git_tool::has_uncommitted_changes() +bool git_wrap::has_uncommitted_changes() { auto p = details::has_uncommitted_changes(root_); run(p); return (p.stdout_string() != ""); } -bool git_tool::has_stashed_changes() +bool git_wrap::has_stashed_changes() { auto p = details::has_stashed_changes(root_); return (run(p) == 0); } -std::string git_tool::make_url( +std::string git_wrap::make_url( const std::string& org, const std::string& git_file, const std::string& url_pattern) { @@ -677,7 +677,7 @@ bool git::do_clone() return false; } - git_tool g(root_, this); + git_wrap g(root_, this); g.clone(url_, branch_, shallow_); @@ -695,7 +695,7 @@ bool git::do_clone() void git::do_pull() { - git_tool g(root_, this); + git_wrap g(root_, this); if (revert_ts_) g.revert_ts(); @@ -740,7 +740,7 @@ const std::string& git_submodule::submodule() const void git_submodule::do_run() { - git_tool(root_, this).add_submodule(branch_, submodule_, url_); + git_wrap(root_, this).add_submodule(branch_, submodule_, url_); } diff --git a/src/tools/git.h b/src/tools/git.h index b319b64..c99f177 100644 --- a/src/tools/git.h +++ b/src/tools/git.h @@ -3,12 +3,12 @@ namespace mob { -class git_tool +class git_wrap { public: static fs::path binary(); - git_tool(fs::path root, basic_process_runner* runner=nullptr); + git_wrap(fs::path root, basic_process_runner* runner=nullptr); void clone(const mob::url& url, const std::string& branch, bool shallow); void pull(const mob::url& url, const std::string& branch); diff --git a/src/tools/patcher.cpp b/src/tools/patcher.cpp index 785185f..6430dd1 100644 --- a/src/tools/patcher.cpp +++ b/src/tools/patcher.cpp @@ -31,7 +31,7 @@ patcher& patcher::file(const fs::path& p) patcher& patcher::root(const fs::path& dir) { - output_ = dir; + root_ = dir; return *this; } @@ -107,7 +107,7 @@ void patcher::do_patch(const fs::path& patch_file) .binary(binary()) .arg("--read-only", "ignore") .arg("--strip", "0") - .arg("--directory", output_) + .arg("--directory", root_) .arg("--quiet", process::log_quiet); const auto check = process(base) diff --git a/src/tools/process_runner.cpp b/src/tools/process_runner.cpp index ba5a85a..ad2ee1f 100644 --- a/src/tools/process_runner.cpp +++ b/src/tools/process_runner.cpp @@ -42,29 +42,19 @@ int basic_process_runner::execute_and_join() set_name(process_->name()); process_->set_context(&cx()); process_->run(); - join(); + process_->join(); return process_->exit_code(); } -void basic_process_runner::join() -{ - process_->join(); -} - int basic_process_runner::exit_code() const { return process_->exit_code(); } -process_runner::process_runner(process&& p) - : tool(p.name()), own_(new process(std::move(p))), p_(nullptr) -{ -} - process_runner::process_runner(process& p) - : tool(p.name()), p_(&p) + : tool(p.name()), p_(p) { } @@ -72,54 +62,21 @@ process_runner::~process_runner() = default; void process_runner::do_run() { - execute_and_join(); + set_name(p_.name()); + p_.set_context(&cx()); + + p_.run(); + p_.join(); } int process_runner::result() const { - return exit_code(); + return p_.exit_code(); } void process_runner::do_interrupt() { - real_process().interrupt(); -} - -int process_runner::execute_and_join() -{ - set_name(real_process().name()); - real_process().set_context(&cx()); - real_process().run(); - - join(); - - return real_process().exit_code(); -} - -void process_runner::join() -{ - real_process().join(); -} - -int process_runner::exit_code() const -{ - return real_process().exit_code(); -} - -process& process_runner::real_process() -{ - if (p_) - return *p_; - else - return *own_; -} - -const process& process_runner::real_process() const -{ - if (p_) - return *p_; - else - return *own_; + p_.interrupt(); } } // namespace diff --git a/src/tools/python.cpp b/src/tools/python.cpp new file mode 100644 index 0000000..f2ac982 --- /dev/null +++ b/src/tools/python.cpp @@ -0,0 +1,192 @@ +#include "pch.h" +#include "tools.h" +#include "../core/process.h" +#include "../tasks/tasks.h" + +namespace mob +{ + +python::python() + : basic_process_runner("python") +{ +} + +python& python::root(const fs::path& p) +{ + root_ = p; + return *this; +} + +python& python::arg(const std::string& s) +{ + args_.push_back(s); + return *this; +} + +void python::do_run() +{ + auto p = process() + .binary(tasks::python::python_exe()) + .chcp(65001) + .stdout_encoding(encodings::utf8) + .stderr_encoding(encodings::utf8) + .stderr_filter([&](process::filter& f) + { + if (f.line.find("zip_safe flag not set") != std::string::npos) + f.lv = context::level::trace; + else if (f.line.find("module references __file__") != std::string::npos) + f.lv = context::level::trace; + }) + .arg("-X", "utf8"); + + for (auto&& a : args_) + p.arg(a); + + p + .cwd(root_) + .env(this_env::get() + .set("PYTHONUTF8", "1")); + + set_process(p); + execute_and_join(); +} + + +pip::pip(ops op) + : basic_process_runner("pip"), op_(op) +{ +} + +pip& pip::package(const std::string& s) +{ + package_ = s; + return *this; +} + +pip& pip::version(const std::string& s) +{ + version_ = s; + return *this; +} + +pip& pip::file(const fs::path& p) +{ + file_ = p; + return *this; +} + +void pip::do_run() +{ + switch (op_) + { + case ensure: + do_ensure(); + break; + + case install: + do_install(); + break; + + case download: + do_download(); + break; + + default: + cx().bail_out(context::generic, "pip unknown op {}", op_); + } +} + +void pip::do_ensure() +{ + // ensure + // + // this spits out two warnings about not being on PATH and suggests to add + // --no-warn-script-location, but that's not actually a valid command + // line parameter for `ensurepip` and it fails, unlike the `install` + // commands below + // + // so just filter it out + + set_process(process() + .stderr_filter([](auto&& f) + { + if (f.line.find("which is not on PATH") != -1) + f.lv = context::level::debug; + else if (f.line.find("Consider adding this directory")) + f.lv = context::level::debug; + }) + .binary(tasks::python::python_exe()) + .arg("-m", "ensurepip")); + + execute_and_join(); + + + // upgrade + set_process(process() + .binary(tasks::python::python_exe()) + .arg("-m pip") + .arg("install") + .arg("--no-warn-script-location") + .arg("--upgrade pip")); + + execute_and_join(); + + + // ssl errors while downloading through python without certifi + set_process(process() + .binary(tasks::python::python_exe()) + .arg("-m pip") + .arg("install") + .arg("--no-warn-script-location") + .arg("certifi")); + + execute_and_join(); +} + +void pip::do_install() +{ + auto p = process() + .binary(tasks::python::python_exe()) + .chcp(65001) + .stdout_encoding(encodings::utf8) + .stderr_encoding(encodings::utf8) + .arg("-X", "utf8") + .arg("-m", "pip") + .arg("install") + .arg("--no-warn-script-location") + .arg("--disable-pip-version-check"); + + if (!package_.empty()) + p.arg(package_ + "==" + version_); + else if (!file_.empty()) + p.arg(file_); + + p + .env(this_env::get() + .set("PYTHONUTF8", "1")); + + set_process(p); + execute_and_join(); +} + +void pip::do_download() +{ + set_process(process() + .binary(tasks::python::python_exe()) + .chcp(65001) + .stdout_encoding(encodings::utf8) + .stderr_encoding(encodings::utf8) + .arg("-X", "utf8") + .arg("-m", "pip") + .arg("download") + .arg("--no-binary=:all:") + .arg("--no-deps") + .arg("-d", conf().path().cache()) + .arg(package_ + "==" + version_) + .env(this_env::get() + .set("PYTHONUTF8", "1"))); + + execute_and_join(); +} + +} // namespace diff --git a/src/tools/tools.cpp b/src/tools/tools.cpp index 5a2d8b8..67fd418 100644 --- a/src/tools/tools.cpp +++ b/src/tools/tools.cpp @@ -1,10 +1,6 @@ #include "pch.h" #include "tools.h" -#include "../utility.h" -#include "../core/op.h" -#include "../core/conf.h" #include "../core/process.h" -#include "../tasks/tasks.h" namespace mob { @@ -57,6 +53,11 @@ void tool::interrupt() } } +void tool::do_interrupt() +{ + // no-op +} + bool tool::interrupted() const { return interrupted_; @@ -114,7 +115,7 @@ fs::path vs::devenv_binary() fs::path vs::installation_path() { - return conf().path().get("vs"); + return conf().path().vs(); } fs::path vs::vswhere() @@ -188,7 +189,7 @@ void vs::do_upgrade() } -fs::path vswhere::find_vs() +std::string vswhere::find_vs() { auto p = process() .binary(vs::vswhere()) @@ -229,189 +230,6 @@ void nuget::do_run() execute_and_join(); } -python::python() - : basic_process_runner("python") -{ -} - -python& python::root(const fs::path& p) -{ - root_ = p; - return *this; -} - -python& python::arg(const std::string& s) -{ - args_.push_back(s); - return *this; -} - -void python::do_run() -{ - auto p = process() - .binary(tasks::python::python_exe()) - .chcp(65001) - .stdout_encoding(encodings::utf8) - .stderr_encoding(encodings::utf8) - .stderr_filter([&](process::filter& f) - { - if (f.line.find("zip_safe flag not set") != std::string::npos) - f.lv = context::level::trace; - else if (f.line.find("module references __file__") != std::string::npos) - f.lv = context::level::trace; - }) - .arg("-X", "utf8"); - - for (auto&& a : args_) - p.arg(a); - - p - .cwd(root_) - .env(this_env::get() - .set("PYTHONUTF8", "1")); - - set_process(p); - execute_and_join(); -} - - -pip::pip(ops op) - : basic_process_runner("pip"), op_(op) -{ -} - -pip& pip::package(const std::string& s) -{ - package_ = s; - return *this; -} - -pip& pip::version(const std::string& s) -{ - version_ = s; - return *this; -} - -pip& pip::file(const fs::path& p) -{ - file_ = p; - return *this; -} - -void pip::do_run() -{ - switch (op_) - { - case ensure: - do_ensure(); - break; - - case install: - do_install(); - break; - - case download: - do_download(); - break; - - default: - cx().bail_out(context::generic, "pip unknown op {}", op_); - } -} - -void pip::do_ensure() -{ - // ensure - // - // this spits out two warnings about not being on PATH and suggests to add - // --no-warn-script-location, but that's not actually a valid command - // line parameter for `ensurepip` and it fails, unlike the `install` - // commands below - // - // so just filter it out - - set_process(process() - .stderr_filter([](auto&& f) - { - if (f.line.find("which is not on PATH") != -1) - f.lv = context::level::debug; - else if (f.line.find("Consider adding this directory")) - f.lv = context::level::debug; - }) - .binary(tasks::python::python_exe()) - .arg("-m", "ensurepip")); - - execute_and_join(); - - - // upgrade - set_process(process() - .binary(tasks::python::python_exe()) - .arg("-m pip") - .arg("install") - .arg("--no-warn-script-location") - .arg("--upgrade pip")); - - execute_and_join(); - - - // ssl errors while downloading through python without certifi - set_process(process() - .binary(tasks::python::python_exe()) - .arg("-m pip") - .arg("install") - .arg("--no-warn-script-location") - .arg("certifi")); - - execute_and_join(); -} - -void pip::do_install() -{ - auto p = process() - .binary(tasks::python::python_exe()) - .chcp(65001) - .stdout_encoding(encodings::utf8) - .stderr_encoding(encodings::utf8) - .arg("-X", "utf8") - .arg("-m", "pip") - .arg("install") - .arg("--no-warn-script-location") - .arg("--disable-pip-version-check"); - - if (!package_.empty()) - p.arg(package_ + "==" + version_); - else if (!file_.empty()) - p.arg(file_); - - p - .env(this_env::get() - .set("PYTHONUTF8", "1")); - - set_process(p); - execute_and_join(); -} - -void pip::do_download() -{ - set_process(process() - .binary(tasks::python::python_exe()) - .chcp(65001) - .stdout_encoding(encodings::utf8) - .stderr_encoding(encodings::utf8) - .arg("-X", "utf8") - .arg("-m", "pip") - .arg("download") - .arg("--no-binary=:all:") - .arg("--no-deps") - .arg("-d", conf().path().cache()) - .arg(package_ + "==" + version_) - .env(this_env::get() - .set("PYTHONUTF8", "1"))); - - execute_and_join(); -} - transifex::transifex(ops o) : basic_process_runner("transifex"), op_(o), diff --git a/src/tools/tools.h b/src/tools/tools.h index 9c67b4d..f8bcdd6 100644 --- a/src/tools/tools.h +++ b/src/tools/tools.h @@ -9,96 +9,236 @@ namespace mob { class process; -class git; +// all the various tools used by mob itself or the tasks, most of them inherit +// from basic_process_runner, which is a small wrapper around a `process`, +// although `downloader` doesn't because it uses a curl_downloader instead +// +// a tool is meant to be used from within a task with task::run_tool(), but can +// also be run standalone with run(); some tools provide a result() function for +// the tool's output +// +// some tools are also used extensively from the command line, like git, which +// has a wrapper `git_wrap` around the all git operations needed and is also +// used by the `git` tool itself +// +// the `context` class knows about tools and will log the tool name when given +// one + + +// base class for all tools +// class tool { public: + // can't copy tools, they have atomics, processes, etc. + // tool(tool&& t); tool& operator=(tool&& t); virtual ~tool() = default; + // tool name, set in constructor + // const std::string& name() const; + // tells the given context that this tool is running and calls do_run() + // void run(context& cx); + + // sets the interrupt flag and calls do_interrupt(); derived classes + // can call interrupted() regularly to see if they should stop or do + // something specific in do_interrupt() + // void interrupt(); + + // context given in run(), gcx() if none + // const context& cx() const; + // some tools have a result() member function that returns something + // specific, like an int of a path; task::run_tool() is a template and + // always returns result(), so a base implementation is required here + // void result() {} protected: tool(std::string name); - + // whether this tool should stop executing as soon as possible + // bool interrupted() const; + // called from run(), does the actual work + // virtual void do_run() = 0; - virtual void do_interrupt() = 0; + // called from interrupt(), only called once + // + virtual void do_interrupt(); + + // changes the name given in the constructor, some tool can have more + // specific or useful names later once they get more parameters + // void set_name(const std::string& s); private: + // set in run(), can be null context* cx_; + + // tool name from constructor or set_name() + // std::string name_; + + // true when interrupt() is called + // std::atomic interrupted_; }; +// these are not strictly tools, but they're a centralized place for perl, +// nasm, qt and vswhere stuff + +// only used when building openssl from source +// struct perl { + // path to perl binary + // static fs::path binary(); }; +// only used when building openssl from source, bundled with mob in +// third-party/bin +// struct nasm { + // path to nasm binary + // static fs::path binary(); }; struct qt { + // path to qt's root directory, the one that contains bin, include, etc. + // static fs::path installation_path(); + + // path to qt's bin directory, basically installation_path()/bin + // static fs::path bin_path(); + + // qt version from the ini + // static std::string version(); + + // vs version for qt as a year, used to build the msvcYYYY_xx directory name + // static std::string vs_version(); }; +// bundled with mob in third-party/bin +// +struct vswhere +{ + // runs the vswhere binary and returns the output, empty on error + // + // note that vswhere may return more than one line if there are multiple + // installations of vs found; this is handled in find_vs() in paths.cpp + // + static std::string find_vs(); +}; + +// a tool that downloads a file, can be given multiple urls in case one fails; +// if none of the given urls can be downloaded, bails out +// +// if file() is not called, the downloader will use the filename from the url +// and put the file in the cache directory (the downloads/ directory by default) +// +// in any case, if the output file already exists, the file is not downloaded +// and run() returns immediately; result() can be used to figure out the path +// of the file +// class downloader : public tool { public: + // what run() should do + // enum ops { + // deletes the file at the path where it would be downloaded clean = 1, + + // downloads the file to the given path download }; + // an empty downloader, url() must be called + // downloader(ops o=download); + + // a downloader for the given url + // downloader(mob::url u, ops o=download); + // adds a url to download + // downloader& url(const mob::url& u); + + // output file + // downloader& file(const fs::path& p); + // path to the output file; this is file() if it was called, or the + // generated name if it wasn't, which can vary if multiple urls were given + // fs::path result() const; protected: + // cleans or downloads + // void do_run() override; + + // tells the curl_downloader to stop + // void do_interrupt() override; private: + // given in the constructor ops op_; + + // the curl downloader std::unique_ptr dl_; + + // output path, may be empty when run() is called, will contain the + // generated filename later fs::path file_; + + // every url added with url() std::vector urls_; + + // deletes an already downloaded file, no-op if not found + // void do_clean(); + + // downloads a file to the output path + // void do_download(); + // generates an output path for the given url + // fs::path path_for_url(const mob::url& u) const; + + // checks if the file exists + // bool try_picking(const fs::path& file); }; +// base class for tools that run processes +// class basic_process_runner : public tool { public: @@ -106,18 +246,43 @@ public: basic_process_runner(basic_process_runner&&); ~basic_process_runner(); + // changes the name of this tool to the process' name, gives the tool's + // context to the process, and runs and joins it, returning the exit code + // + // does not copy the process + // + // tools normally use the protected set_process() and execute_and_join(), + // but this is used by git_wrap, which can be run standalone from the + // command line, or reused by the `git` class in tasks + // + // when git_wrap is used for tasks, it's given the `git` tool used by the + // task and calls this function to run processes in the context of the tool; + // when standalone, it just executes process objects directly + // int execute_and_join(process& p); - void join(); + // exit code of the process + // int exit_code() const; protected: basic_process_runner(std::string name); + // makes a copy of the given process and stores it + // void set_process(const process& p); + + // returns the internal process copied from set_process(), used by derived + // classes to get information about the process, like the output + // process& get_process(); + // interrupts the internal process + // void do_interrupt() override; + + // executes the internal process and waits until it terminates + // int execute_and_join(); private: @@ -125,130 +290,295 @@ private: }; +// runs an arbitrary process, used by some tasks for one-time programs that +// don't have a dedicated tool class, like b2 for boost, +// +// does not copy the given process, which must live at least until run() returns +// class process_runner : public tool { public: - process_runner(process&& p); + // creates a process_runner for the given process, keeps a reference to it + // process_runner(process& p); // anchor ~process_runner(); - void join(); - int exit_code() const; - + // returns the exit code of process, returned by task::run_tool() + // int result() const; protected: + // changes the name of this tool to the process' name, gives the tool's + // context to the process, and runs and joins it + // void do_run() override; -private: - std::unique_ptr own_; - process* p_; - + // interrupts the process + // void do_interrupt() override; - int execute_and_join(); - process& real_process(); - const process& real_process() const; +private: + // process given in constructor + process& p_; }; +// tool to handle extracting archives +// +// if extraction fails, an interruption file is left in the directory so +// extraction is restarted next time mob runs +// +// 7z is bundled with mob in third-party/bin +// class extractor : public basic_process_runner { public: - extractor(); - + // path to the program used for extraction, typically 7z + // static fs::path binary(); + + extractor(); + + // file to extract + // extractor& file(const fs::path& file); + + // output directory + // extractor& output(const fs::path& dir); protected: + // extracts the file + // void do_run() override; private: fs::path file_; fs::path where_; + // some archives have a top level directory, this moves all the files up one + // directory and deletes the now empty top level directory + // void check_duplicate_directory(const fs::path& ifile); }; +// tool to handle creating archives, 7z is bundled with mob in third-party/bin +// +// this isn't used by any task, but it's used in a few places in op.cpp, mostly +// for creating archives with the `release` command +// class archiver : public basic_process_runner { public: + // archives all the files matching `glob` into a file `out`, ignoring + // anything that matches a string in `ignore` + // static void create_from_glob( const context& cx, const fs::path& out, const fs::path& glob, const std::vector& ignore); + // archives all the given files rooted in `files_root`, into a file `out` + // static void create_from_files( const context& cx, const fs::path& out, const std::vector& files, const fs::path& files_root); }; +// tool to apply patches available for a third-party task; patches live in +// mob/patches, in one directory per task +// +// patch.exe is bundled with mob in third-party/bin +// +// there are two ways of patching a task: +// +// 1) auto patching: each task runs the patcher tool as a step in +// task::fetch(), just after calling do_fetch(), which checks for a +// directory `patches/task-name/sources` or `patches/task-name/prebuilt` +// depending on whether this is a prebuilt or not +// +// any file ending with `.patch` will be applied, rooted in the path +// given to root() (defaults to the task's source directory) +// +// 2) manual patching: a task can run the patcher with a specific patch file +// given in file() at any time, which is useful for tasks that need to +// patch files generated later in the building process, such as pyqt +// +// manual patch files can be anywhere, but they're in patches/task-name with +// a `.manual_patch` by convention +// class patcher : public basic_process_runner { public: - patcher(); - + // path to the patch binary + // static fs::path binary(); + + patcher(); + + // sets the task name and whether it's a prebuilt, used in task::fetch() for + // auto patching + // patcher& task(const std::string& name, bool prebuilt=false); + + // sets the patch's filename; if this is not set, uses the task name and + // prebuilt flag from task() + // patcher& file(const fs::path& p); + + // directory in which the patches are applied; the filenames in the patch + // files must be relative to this directory + // patcher& root(const fs::path& dir); protected: + // applies the patch file from the task directory or the patch file given + // in file() + // void do_run() override; private: + // task name and prebuilt flag, used to generate the auto patching directory std::string task_; bool prebuilt_; - fs::path output_; + + // root directory, used to resolve the relative file names in the patch + // files + fs::path root_; + + // patch file, empty for auto patching fs::path file_; + + // applies one patch file + // void do_patch(const fs::path& patch_file); }; +// tool that runs `jom`, an alternative to `nmake` that supports parallel +// builds, bundled with mob in third-party/bin +// +// note that although jom reduces build times considerably, some third parties +// don't handle it very well, giving a bunch of errors about files in use, etc. +// +// so most tools that use jom use a loop that runs jom several times, allowing +// failure, and then run jom one last time in single_job mode to finish the +// build, which should be guaranteed to work +// class jom : public basic_process_runner { public: + // path to the job binary + // + static fs::path binary(); + + enum flags_t { noflags = 0x00, + + // disables multi-process build single_job = 0x01, + + // don't bail out on failure allow_failure = 0x02 }; jom(); - static fs::path binary(); - + // path in which to invoke jom + // jom& path(const fs::path& p); + + // makefile target + // jom& target(const std::string& s); + + // adds a macro definition as "NAME=value" + // jom& def(const std::string& s); + + // sets flags + // jom& flag(flags_t f); + + // sets the architecture used to run jom, used to get the appropriate + // VS environment variables, defaults to arch::def + // jom& architecture(arch a); + // jom's exit code + // int result() const; protected: + // runs jom + // void do_run() override; private: + // set in path() fs::path cwd_; + + // set in def(), just plain arguments std::vector def_; + + // set in target(), another plain argument std::string target_; + + // set in flag() flags_t flags_; + + // set in architecture() arch arch_; }; +// tool that runs devenv.exe, only invoked to upgrade projects for now +// class vs : public basic_process_runner { public: + // path to devenv.exe + // + static fs::path devenv_binary(); + + // path to visual studio's root directory, the one that contains Common7, + // VC, etc. + // + static fs::path installation_path(); + + // path to vswhere.exe, typically from mob's third-party/bin + // + static fs::path vswhere(); + + // path to vcvars batch file + // + static fs::path vcvars(); + + // vs version from ini + // + static std::string version(); + + // vs year from ini + // + static std::string year(); + + // vs toolset from ini + // + static std::string toolset(); + + // vs sdk version from ini + // + static std::string sdk(); + + enum ops { upgrade = 1 @@ -256,181 +586,337 @@ public: vs(ops o); - static fs::path devenv_binary(); - static fs::path installation_path(); - static fs::path vswhere(); - static fs::path vcvars(); - static std::string version(); - static std::string year(); - static std::string toolset(); - static std::string sdk(); - + // path to the solution file to be upgraded + // vs& solution(const fs::path& sln); protected: + // calls do_upgrade() + // void do_run() override; private: ops op_; fs::path sln_; + // upgrades the solution file + // void do_upgrade(); }; -class vswhere : public basic_process_runner -{ -public: - static fs::path find_vs(); -}; - - +// tool that runs `nuget restore` for the given solution, bundled with mob in +// third-party/bin +// class nuget : public basic_process_runner { public: + // nuget tool for the given solution + // nuget(fs::path sln); + // path to the nuget binary + // static fs::path binary(); protected: + // runs nuget void do_run() override; private: + // solution file set in constructor fs::path sln_; }; +// tool that runs python +// class python : public basic_process_runner { public: python(); + // working directory + // python& root(const fs::path& p); + + // arbitrary arguments given to python + // python& arg(const std::string& s); protected: + // runs python + // void do_run() override; private: + // working directory fs::path root_; + + // arguments std::vector args_; }; +// tool that runs pip for one of the operations in the enum +// class pip : public basic_process_runner { public: enum ops { + // installs pip if needed and updates it ensure = 1, + + // runs `-m pip install` for the given package/version or file + // install, + + // runs `-m pip download` for the given package and version, does not + // use file() download }; + + // a pip tool for the given operation + // pip(ops o); + // `pip install` can work with either a "package==version" string, which + // downloads the file if needed, or a local file + // + // so for the `install` operation, either package/version or file can be + // given, but the `download` operation requires the package/version only + // and puts the downloaded file in the "cache" path (defaults to the + // downloads/ directory) + // pip& package(const std::string& s); pip& version(const std::string& s); pip& file(const fs::path& p); protected: + // runs pip + // void do_run() override; private: + // what pip command to run ops op_; + + // set in package(), version() and file() std::string package_; std::string version_; fs::path file_; + // runs `-m ensurepip`, then upgrades pip + // void do_ensure(); + + // runs `-m pip install` with the given package/version or file + // void do_install(); + + // runs `-m pip download` with the given package/version, saves the file + // in the cache directory + // void do_download(); }; +// tool that runs transifex, used for pulling translations before release, +// bundled with mob in third-party/bin +// class transifex : public basic_process_runner { public: - enum ops - { - init = 1, - config, - pull - }; - + // path to the tx binary + // static fs::path binary(); + enum ops + { + // runs `ini`, initializes an empty directory to use transifex + init = 1, + + // runs `config`, sets the url + config, + + // pulls translations + pull + }; + transifex(ops op); + // directory that contains the .tx directory + // transifex& root(const fs::path& p); + + // api key used for requests + // transifex& api_key(const std::string& key); + + // url of the translations on transifex + // transifex& url(const mob::url& u); + + // minimum completion percentage of translations to pull, must be an int + // between 0 and 100 + // transifex& minimum(int percent); + + // sets the log level for tx's stdout; it's trace by default while building, + // but this tool is also used on the command line, in which case stdout is + // set to info to see the logs on the console + // transifex& stdout_level(context::level lv); + + // passes --force to `pull`, bypasses timestamp checks and forces redownload + // of all translation files + // transifex& force(bool b); protected: + // runs the selected operation + // void do_run() override; private: + // operation ops op_; + + // log level of stdout, set in stdout_level() context::level stdout_; + + // root directory fs::path root_; + + // api key std::string key_; + + // url of translations on transifex mob::url url_; + + // minimum completion percentage, [0,100] int min_; + + // whether to give `--force` to `pull` bool force_; + + // runs `init` + // void do_init(); + + // runs `config` + // void do_config(); + + // runs `pull` + // void do_pull(); }; +// tool that runs `lrelease`, which compiles translation files into a .qm file +// and drops them in the directory given in out(), typically +// install/bin/translations +// +// lrelease.exe from the qt installation +// class lrelease : public basic_process_runner { public: + // path to the lrelease binary + // static fs::path binary(); lrelease(); + // name of the project, used to generate the output filename, which is + // project_lang.qm + // lrelease& project(const std::string& name); + + // adds or sets the source .ts files that are compiled to create the .qm + // file + // + // a .qm file is normally compiled from just one .ts, but some projects use + // an additional .ts file, like gamebryo, where each game plugin has + // specific strings in their own .ts file, but also use a common .ts file + // from gamebryo + // + // this prevents copying common strings to all the plugin .ts files, giving + // more work to translators, but it also means that lrelease must know about + // both the plugin's .ts and gamebryo's ts + // + // it's also assumed that all the given .ts files are for the same language, + // so the filename of the first source that's added is used as the language + // string when generating the filename, along with the project name + // + // since .ts files from transifex are simply named `lang.ts`, such as + // `fr.ts` or `de.ts`, the generated filename would be something like + // `uibase_fr.qm` + // lrelease& add_source(const fs::path& ts_file); lrelease& sources(const std::vector& v); + + // output directory where the .qm file is generated + // lrelease& out(const fs::path& dir); + // path to the .qm file that was generated + // fs::path qm_file() const; protected: + // runs lrelease on the given sources + // void do_run() override; private: + // project name std::string project_; + + // .ts files std::vector sources_; + + // output directory fs::path out_; }; +// tool that runs Inno Setup's iscc.exe to create the installer +// class iscc : public basic_process_runner { public: + // path to the iscc.exe binary + // static fs::path binary(); - iscc(fs::path iss = {}); + // iscc tool with an optional path to the .iss file + // + iscc(fs::path iss={}); + // .iss file + // iscc& iss(const fs::path& p); protected: + // runs iscc + // void do_run() override; private: + // path to the .iss file fs::path iss_; }; } // namespace +// more tools #include "git.h" #include "cmake.h" #include "msbuild.h" diff --git a/vs/mob.vcxproj b/vs/mob.vcxproj index ee8309e..ac13289 100644 --- a/vs/mob.vcxproj +++ b/vs/mob.vcxproj @@ -114,6 +114,7 @@ + diff --git a/vs/mob.vcxproj.filters b/vs/mob.vcxproj.filters index 8f1418b..5537c7c 100644 --- a/vs/mob.vcxproj.filters +++ b/vs/mob.vcxproj.filters @@ -201,6 +201,9 @@ src\core + + src\tools +