refactored do_download() into use_existing() and try_download()
This commit is contained in:
isanae
2020-12-03 17:45:34 -05:00
parent 7d692430be
commit 51a7852aba
6 changed files with 396 additions and 114 deletions
+4 -1
View File
@@ -176,8 +176,11 @@ 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_wrap(r).set_credentials(username_, email_);
git_wrap(r).set_remote(username_, key_, nopush_, push_default_);
git_wrap(r).set_origin_and_upstream_remotes(
username_, key_, nopush_, push_default_);
}
void git_command::do_add_remote()
+57 -43
View File
@@ -60,51 +60,24 @@ void downloader::do_download()
dl_.reset(new curl_downloader(&cx()));
cx().trace(context::net, "looking for already downloaded files");
if (!file_.empty())
if (use_existing())
{
if (try_picking(file_))
return;
cx().trace(context::bypass, "using {}", file_);
return;
}
else
{
for (auto&& u : urls_)
{
const auto file = path_for_url(u);
if (try_picking(file))
{
file_ = file;
return;
}
}
}
cx().trace(context::net, "no cached downloads were found, will try:");
for (auto&& u : urls_)
cx().trace(context::net, " . {}", u);
// try them in order
for (auto&& u : urls_)
{
if (file_.empty())
file_ = path_for_url(u);
cx().trace(context::net, "trying {} into {}", u, file_);
dl_->start(u, file_);
cx().trace(context::net, "waiting for download");
dl_->join();
if (dl_->ok())
if (try_download(u))
{
cx().trace(context::net, "file {} downloaded", file_);
// done
return;
}
cx().debug(context::net, "download failed");
}
if (interrupted())
@@ -117,15 +90,37 @@ void downloader::do_download()
cx().bail_out(context::net, "all urls failed to download");
}
bool downloader::try_download(const mob::url& u)
{
// when file() wasn't called, the output file is created from the url
if (file_.empty())
file_ = path_for_url(u);
// downloading
cx().trace(context::net, "trying {} into {}", u, file_);
dl_->start(u, file_);
cx().trace(context::net, "waiting for download");
dl_->join();
if (dl_->ok())
{
// done
cx().trace(context::net, "file {} downloaded", file_);
return true;
}
cx().debug(context::net, "download failed");
return false;
}
void downloader::do_clean()
{
if (!file_.empty())
{
cx().debug(context::redownload, "deleting {}", file_);
op::delete_file(cx(), file_, op::optional);
}
else
if (file_.empty())
{
// file() wasn't called, delete all the files that would be created
// depending on the urls given
for (auto&& u : urls_)
{
const auto file = path_for_url(u);
@@ -134,6 +129,12 @@ void downloader::do_clean()
op::delete_file(cx(), file, op::optional);
}
}
else
{
// delete the given output file
cx().debug(context::redownload, "deleting {}", file_);
op::delete_file(cx(), file_, op::optional);
}
}
void downloader::do_interrupt()
@@ -142,16 +143,28 @@ void downloader::do_interrupt()
dl_->interrupt();
}
bool downloader::try_picking(const fs::path& file)
bool downloader::use_existing()
{
if (fs::exists(file))
if (file_.empty())
{
cx().trace(context::bypass, "picking {}", file_);
return true;
// check if one of the files that would be created by a url exists
for (auto&& u : urls_)
{
const auto file = path_for_url(u);
if (fs::exists(file))
{
// take it
file_ = file;
return true;
}
}
}
else
{
cx().trace(context::net, "no {}", file);
// file() was called, check if it exists
if (fs::exists(file_))
return true;
}
return false;
@@ -183,6 +196,7 @@ fs::path downloader::path_for_url(const mob::url& u) const
filename = u.filename();
}
// downloaded files go in the cache, typically build/downloads/
return conf().path().cache() / filename;
}
+66 -29
View File
@@ -31,8 +31,10 @@ void extractor::do_run()
{
interruption_file ifile(cx(), where_, "extractor");
// check interruption file from last run
if (ifile.exists())
{
// resume the extraction, will overwrite
cx().debug(context::generic,
"previous extraction was interrupted; resuming");
}
@@ -40,11 +42,14 @@ void extractor::do_run()
{
if (conf().global().reextract())
{
// output already exists, no interruption file, but the user wants
// to re-extract
cx().debug(context::reextract, "deleting {}", where_);
op::delete_directory(cx(), where_, op::optional);
}
else
{
// output already exists, no interruption file, assume it's fine
cx().debug(context::bypass, "directory {} already exists", where_);
return;
}
@@ -52,14 +57,23 @@ void extractor::do_run()
cx().debug(context::generic, "extracting {} into {}", file_, where_);
op::create_directories(cx(), where_);
// will be left on disk on crashes or interruptions
ifile.create();
op::create_directories(cx(), where_);
// deletes the directory in the destructor if in case of hard failure, but
// not interruptions so extraction is resumed later
directory_deleter delete_output(cx(), where_);
// the -spe from 7z is supposed to figure out if there's a folder in the
// archive with the same name as the target and extract its content to
// avoid duplicating the folder
// some archives have a top-level directory, others have files directly in
// it, and it sucks to have special cases that know about individual
// third parties, so this tries to figure out whether to move the files
// after extraction
//
// now, the -spe flag from 7z is supposed to figure out if there's a folder
// in the archive with the same name as the target and extract its content
// to avoid duplicating the folder
//
// however, it fails miserably if there are files along with that folder,
// which is the case for openssl:
@@ -68,52 +82,66 @@ void extractor::do_run()
// +- openssl-1.1.1d/
// +- pax_global_header
//
// that pax_global_header makes 7z fail with "unspecified error"
// that pax_global_header makes 7z fail with "unspecified error", so -spe
// just can't be used at all
//
// so the handling of a duplicate directory is done manually in
// check_duplicate_directory() below
// check_duplicate_directory() below, unfortunately
if (file_.u8string().ends_with(u8".tar.gz"))
{
// tar in gz, must be piped, 7z can't do it in one step
cx().trace(context::generic, "this is a tar.gz, piping");
// untar
auto extract_tar = process()
.binary(binary())
.arg("x")
.arg("-so", file_);
.arg("x") // extract
.arg("-so") // output to stdout
.arg(file_); // input file
// decompress
auto extract_gz = process()
.binary(binary())
.arg("x")
.arg("-aoa")
.arg("-si")
.arg("-ttar")
.arg("-o", where_, process::nospace);
.arg("x") // extract
.arg("-aoa") // overwrite all without prompt
.arg("-si") // read from stdin
.arg("-ttar") // type is tar
.arg("-o", where_, process::nospace); // output file
auto piped = process::pipe(extract_tar, extract_gz);
execute_and_join(piped);
}
else
{
// not tar, just extract directly
execute_and_join(process()
.binary(binary())
.arg("x")
.arg("-aoa")
.arg("-bd")
.arg("-bb0")
.arg("-o", where_, process::nospace)
.arg(file_));
.arg("x") // extract
.arg("-aoa") // overwrite all without prompt
.arg("-bd") // no progress indicator
.arg("-bb0") // disable log
.arg("-o", where_, process::nospace) // output file
.arg(file_)); // input file
}
check_duplicate_directory(ifile.file());
// moves files up if necessary
check_for_top_level_directory(ifile.file());
// success or interruption, don't delete the directory
delete_output.cancel();
if (!interrupted())
{
// extraction finished and not interrupted, everything worked, so remove
// the interruption file
ifile.remove();
}
}
void extractor::check_duplicate_directory(const fs::path& ifile)
void extractor::check_for_top_level_directory(const fs::path& ifile)
{
const auto dir_name = where_.filename();
@@ -149,7 +177,7 @@ void extractor::check_duplicate_directory(const fs::path& ifile)
if (!fs::is_regular_file(e.path()))
{
// don't know what to do with archives that have the
// same directory _and_ other directories
// same directory _and_ other directories, bail out for now
cx().bail_out(context::generic,
"check_duplicate_directory: {} is yet another directory",
e.path());
@@ -198,14 +226,19 @@ void archiver::create_from_glob(
auto p = process()
.binary(extractor::binary())
.arg("a")
.arg(out)
.arg("-r")
.arg("-mx=5")
.arg(glob);
.arg("a") // add to archive
.arg(out) // output file
.arg("-r") // recursive
.arg("-mx=5") // normal compression level
.arg(glob); // input file
for (auto&& i : ignore)
{
// x: exclude
// r: recurse
// !: filename or glob
p.arg("-xr!", i, process::nospace);
}
p.run();
p.join();
@@ -218,6 +251,8 @@ void archiver::create_from_files(
std::string list_file_text;
std::error_code ec;
// make each file relative to files_root, convert to utf8 and put in
// list_file_text separated by newlines
for (auto&& f : files)
{
fs::path rf = fs::relative(f, files_root, ec);
@@ -232,6 +267,8 @@ void archiver::create_from_files(
}
const auto list_file = make_temp_file();
// always delete the list file when done
guard g([&]
{
if (fs::exists(list_file))
@@ -246,8 +283,8 @@ void archiver::create_from_files(
auto p = process()
.binary(extractor::binary())
.arg("a")
.arg(out)
.arg("a") // add to archive
.arg(out) // output file
.arg("@", list_file, process::nospace)
.cwd(files_root);
+22 -22
View File
@@ -26,6 +26,16 @@ void for_each_ts(const fs::path& root, F&& f)
}
}
std::string make_url(
const std::string& org, const std::string& git_file,
const std::string& url_pattern)
{
const std::string pattern = url_pattern.empty() ?
details::default_github_url_pattern : url_pattern;
return fmt::format(pattern, org, git_file);
}
[[nodiscard]] process make_process()
{
@@ -342,7 +352,7 @@ void git_wrap::set_credentials(const std::string& username, const std::string& e
set_config("user.email", email);
}
void git_wrap::set_remote(
void git_wrap::set_origin_and_upstream_remotes(
std::string org, std::string key,
bool no_push_upstream, bool push_default_origin)
{
@@ -440,7 +450,7 @@ void git_wrap::add_remote(
if (!has_remote(remote_name))
{
run(details::add_remote(
root_, remote_name, make_url(username, gf, url_pattern)));
root_, remote_name, details::make_url(username, gf, url_pattern)));
if (push_default)
set_config("remote.pushdefault", remote_name);
@@ -558,19 +568,10 @@ bool git_wrap::has_stashed_changes()
return (run(p) == 0);
}
std::string git_wrap::make_url(
const std::string& org, const std::string& git_file,
const std::string& url_pattern)
{
const std::string pattern = url_pattern.empty() ?
details::default_github_url_pattern : url_pattern;
return fmt::format(pattern, org, git_file);
}
git::git(ops o)
: basic_process_runner("git"), op_(o)
git::git(ops o) :
basic_process_runner("git"), op_(o), ignore_ts_(false), revert_ts_(false),
shallow_(false), no_push_upstream_(false), push_default_origin_(false)
{
}
@@ -651,7 +652,9 @@ void git::do_run()
case ops::clone_or_pull:
{
do_clone_or_pull();
if (!do_clone())
do_pull();
break;
}
@@ -662,12 +665,6 @@ void git::do_run()
}
}
void git::do_clone_or_pull()
{
if (!do_clone())
do_pull();
}
bool git::do_clone()
{
const fs::path dot_git = root_ / ".git";
@@ -685,7 +682,10 @@ bool git::do_clone()
g.set_credentials(creds_username_, creds_email_);
if (!remote_org_.empty())
g.set_remote(remote_org_, remote_key_, no_push_upstream_, push_default_origin_);
{
g.set_origin_and_upstream_remotes(
remote_org_, remote_key_, no_push_upstream_, push_default_origin_);
}
if (ignore_ts_)
g.ignore_ts(true);
+237 -16
View File
@@ -3,102 +3,268 @@
namespace mob
{
// wrapper around git commands used by the git tool below or various `mob git`
// commands
//
class git_wrap
{
public:
// path to the git binary
//
static fs::path binary();
// runs git commands in the given root directory
//
// `runner` is used when the commands are issued by the git tool below,
// they ask the runner to run the various processes instead of running them
// directly; this makes logs use the name of the task running the tool, etc.
//
git_wrap(fs::path root, basic_process_runner* runner=nullptr);
// runs `git clone` with the url and branch, adds `--depth 1` when `shallow`
// is true
//
void clone(const mob::url& url, const std::string& branch, bool shallow);
// runs `git pull` with the given url and branch
//
void pull(const mob::url& url, const std::string& branch);
// runs `git config` to set "user.name" and "user.email"
//
void set_credentials(const std::string& username, const std::string& email);
void set_remote(
// 1) renames the "origin" remote to "upstream",
// 2) sets the "upstream" push url to "nopushurl" of `no_push_upstrea` is
// true
// 3) adds a new "origin" remote from github with the given org and key
// 4) sets the "origin" remote as the default push remote if
// `push_default_origin` is true
//
// this is used when cloning a repo: "origin" is usually from the
// ModOrganizer2 org, but most devs have their own fork in which they
// develop, so "origin" becomes "upstream" and a new "origin" remote is
// created for their own repo
//
// if there's already a remote named "upstream", this is a no-op
//
void set_origin_and_upstream_remotes(
std::string org, std::string key,
bool no_push_upstream, bool push_default_origin);
// finds all the .ts files in the root (recursive) and either sets or
// removes the --assume-unchanged flag on all of them
//
// .ts files are translation files that are automatically generated by Qt
// when building the various projects and they can change at any time;
// pushing them creates unnecessary merge conflicts for other devs, and it's
// a pita when it happens
//
// this basically ignores .ts completely when pushing: they won't be shown
// as modified and won't be pushed if they've changed
//
void ignore_ts(bool b);
// finds all the .ts files in the root (recursive) and reverts them (does
// a `git checkout` on all of them)
//
// this is used when pulling changes to revert all the .ts before pulling
// so there are no conflicts
//
void revert_ts();
// returns whether the given file is known to git
//
bool is_tracked(const fs::path& file);
// returns whether the given remote name exists
//
bool has_remote(const std::string& name);
// adds a remote from github
//
// remote_name: name of the new remote
//
// org: organization on github
//
// key: path to a putty key, may be empty
//
// push_default: whether this remote should be the default for push, sets
// the remote.pushdefault config
//
// url_pattern: the url pattern for the remote, should be a format string
// with two {} for org and git file respectively; if empty,
// defaults to default_github_url_pattern in git.cpp
//
// git_file: the name of the git file on github, such as
// "modorganizer.git"; if empty, defaults to the git file used
// by the "origin" remote
//
// this is necessary in some operations like in
// set_origin_and_upstream_remotes() because the "origin"
// remote might not exist at that point
//
void add_remote(
const std::string& remote_name,
const std::string& org, const std::string& key, bool push_default,
const std::string& url_pattern={}, const std::string& git_file={});
// renames remote `from` to `to`
//
void rename_remote(const std::string& from, const std::string& to);
// sets the push url of the given remote
//
void set_remote_push(const std::string& remote, const std::string& url);
// runs `git config key value`
//
void set_config(const std::string& key, const std::string& value);
// sets --assume-unchanged or --no-assume-unchanged for the given file
//
void set_assume_unchanged(const fs::path& relative_file, bool on);
// returns the .git file used by the origin remote, such as modorganizer.git
//
std::string git_file();
// runs `git init`
//
void init_repo();
// runs `git apply` and feeds the given string as stdin; used to apply a
// PR diff downloaded for github, for example
//
void apply(const std::string& diff);
// runs `git fetch remote branch`
//
void fetch(const std::string& remote, const std::string& branch);
// runs `git checkout what`
//
void checkout(const std::string& what);
// runs `git submodule add` for the given branch submodule and url
//
void add_submodule(
const std::string& branch, const std::string& submodule,
const mob::url& url);
// returns the output of `git branch --show-current`, which is the name of
// the active branch
//
std::string current_branch();
// whether the root directory given in the constructor is a valid git repo
//
bool is_git_repo();
// whether the repo has uncommitted changes (basically checks `git status`);
// see delete_directory() below
//
bool has_uncommitted_changes();
// whether the repo has stashed changes (checks `git stash show`); see
// delete_directory() below
//
bool has_stashed_changes();
// used by various tasks to delete a directory that was created by pulling
// from git
//
// if the directory has uncommitted or stashed changes, it will output an
// error and bail out; if not, the directory is deleted normally with
// op::delete_directory()
//
static void delete_directory(const context& cx, const fs::path& dir);
// runs `git ls-remote` to check if the repo at the url has the given branch
// name
//
// used mostly by `mob release official` when given a branch name to make
// sure the branch exists in all repos before starting the build so it
// doesn't fail in the middle
//
static bool remote_branch_exists(const mob::url& u, const std::string& name);
private:
// git root directory, from constructor
fs::path root_;
// optional tool that's running these git commands
basic_process_runner* runner_;
// either runs the given process directly or asks runner_ to run it if it's
// not null
//
// both versions are needed because run() can be called with a temporary
// process object sometimes
//
int run(process&& p);
int run(process& p);
// log context, either gcx() or the one from runner_ if it's not null
//
const context& cx();
static std::string make_url(
const std::string& org, const std::string& git_file,
const std::string& url_pattern={});
};
// tool to handle git operations, used by tasks
//
class git : public basic_process_runner
{
public:
// what run() should do
//
enum ops
{
// clones the repo
clone = 1,
// pulls the repo
pull,
// pulls if the repo exists, clones otherwise
clone_or_pull
};
git(ops o);
// url to clone or pull from
//
git& url(const mob::url& u);
// root directory of the git repo
//
git& root(const fs::path& dir);
// branch to clone or pull
//
git& branch(const std::string& name);
// whether all .ts files should be marked as --assume-unchanged when cloning
//
git& ignore_ts_on_clone(bool b);
// whether all .ts files should be reverted when pulling
//
git& revert_ts_on_pull(bool b);
// if this is called, sets "user.name" and "user.email" when cloning
//
git& credentials(const std::string& username, const std::string& email);
// if true, clones with `--depth 1`
//
git& shallow(bool b);
// if set, calls git_wrap::set_origin_and_upstream_remotes()
//
git& remote(
std::string org, std::string key,
bool no_push_upstream, bool push_default_origin);
@@ -107,37 +273,54 @@ protected:
void do_run() override;
private:
// operation
ops op_;
// set by the various functions above
mob::url url_;
fs::path root_;
std::string branch_;
bool ignore_ts_ = false;
bool revert_ts_ = false;
bool ignore_ts_;
bool revert_ts_;
std::string creds_username_;
std::string creds_email_;
bool shallow_ = false;
bool shallow_;
std::string remote_org_;
std::string remote_key_;
bool no_push_upstream_ = false;
bool push_default_origin_ = false;
bool no_push_upstream_;
bool push_default_origin_;
void do_clone_or_pull();
bool do_clone();
void do_pull();
};
// tool to handle git submodule operations, used by the modorganizer task to
// set up the submodules
//
// this tool is not normally run directly, instances of git_submodule are given
// to the git_submodule_adder, which runs all of them in a thread
//
class git_submodule : public basic_process_runner
{
public:
git_submodule();
// remote url
//
git_submodule& url(const mob::url& u);
git_submodule& root(const fs::path& dir);
git_submodule& branch(const std::string& name);
git_submodule& submodule(const std::string& name);
// root directory of the repo
//
git_submodule& root(const fs::path& dir);
// branch name
//
git_submodule& branch(const std::string& name);
// submodule name
//
git_submodule& submodule(const std::string& name);
const std::string& submodule() const;
protected:
@@ -151,23 +334,40 @@ private:
};
// queues submodule operations with queue(), runs them in a thread because they
// take a long time but can happen while stuff is building
//
class git_submodule_adder : public instrumentable<2>
{
public:
// instrumentable categories
//
enum class times
{
add_submodule_wait,
add_submodule
};
// calls stop() and joins
//
~git_submodule_adder();
// only one instance, runs the thread and waits for submodules to be added
// by queue()
//
static git_submodule_adder& instance();
// adds a submodule to the queue
//
void queue(git_submodule g);
// stops the thread
//
void stop();
private:
// used to sleep until queue() is called
//
struct sleeper
{
std::mutex m;
@@ -175,18 +375,39 @@ private:
bool ready = false;
};
// log context
context cx_;
// thread
std::thread thread_;
// queue
std::vector<git_submodule> queue_;
mutable std::mutex queue_mutex_;
// true in stop(), stops the thread
std::atomic<bool> quit_;
// used to sleep until queue() is called
sleeper sleeper_;
git_submodule_adder();
// starts the thread
//
void run();
// thread function, sleeps until queue() is called
//
void thread_fun();
// forces the thread function to wake up
//
void wakeup();
// processes the queue
//
void process();
};
+10 -3
View File
@@ -231,9 +231,14 @@ private:
//
fs::path path_for_url(const mob::url& u) const;
// checks if the file exists
// checks if one of the output files already exists, sets file_ to it if
// necessary and returns true
//
bool try_picking(const fs::path& file);
bool use_existing();
// tries to download the given url, returns whether it succeeded
//
bool try_download(const mob::url& u);
};
@@ -355,7 +360,7 @@ private:
// 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);
void check_for_top_level_directory(const fs::path& ifile);
};
@@ -572,6 +577,8 @@ public:
static std::string sdk();
// what run() should do
//
enum ops
{
upgrade = 1