aboutsummaryrefslogtreecommitdiff
path: root/bbot/worker.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bbot/worker.cxx')
-rw-r--r--bbot/worker.cxx455
1 files changed, 453 insertions, 2 deletions
diff --git a/bbot/worker.cxx b/bbot/worker.cxx
index e0149fc..fbae8c7 100644
--- a/bbot/worker.cxx
+++ b/bbot/worker.cxx
@@ -11,12 +11,16 @@
#include <iostream>
#include <butl/pager>
+#include <butl/filesystem>
+
+#include <bbot/manifest>
#include <bbot/types>
#include <bbot/utility>
#include <bbot/diagnostics>
#include <bbot/worker-options>
+#include <bbot/bootstrap-manifest>
using namespace std;
using namespace butl;
@@ -24,7 +28,412 @@ using namespace bbot;
namespace bbot
{
+ process_path argv0;
worker_options ops;
+
+ dir_path env_dir;
+
+ const size_t tftp_timeout (10); // 10 seconds.
+}
+
+static dir_path
+change_wd (const dir_path& d, bool create = false)
+try
+{
+ if (create)
+ try_mkdir_p (d);
+
+ dir_path r (dir_path::current_directory ());
+
+ dir_path::current_directory (d);
+
+ return r;
+}
+catch (const system_error& e)
+{
+ fail << "unable to change current directory to " << d << ": " << e << endf;
+}
+
+template <typename... A>
+static result_status
+run_bpkg (tracer& t, string& log, const string& cmd, A&&... a)
+{
+ try
+ {
+ // Trace and log the command line.
+ //
+ auto cmdc = [&t, &log] (const char* c[], size_t n)
+ {
+ run_trace (t, c, n);
+
+ ostringstream os;
+ process::print (os, c, n);
+ log += os.str ();
+ log += '\n';
+ };
+
+ fdpipe pipe (fdopen_pipe ()); // Text mode seems appropriate.
+
+ process pr (
+ process_start (cmdc,
+ fdnull (), // Never reads from stdout.
+ 2, // 1>&2
+ pipe.out,
+ dir_path (),
+ "bpkg",
+ "-v",
+ cmd,
+ forward<A> (a)...));
+
+ pipe.out.close ();
+
+ // Log the diagnostics.
+ //
+ {
+ ifdstream is (move (pipe.in), fdstream_mode::skip); // Skip on exception.
+
+ for (string l; is.peek () != ifdstream::traits_type::eof (); )
+ {
+ getline (is, l);
+ log += l;
+ log += '\n';
+ }
+ }
+
+ if (pr.wait ())
+ return result_status::success;
+
+ log += "bpkg " + cmd;
+ const process_exit& e (*pr.exit);
+
+ if (e.normal ())
+ {
+ log += " exited with code " + to_string (e.code ()) + "\n";
+ return result_status::error;
+ }
+ else
+ {
+ log += " terminated abnormally: " + e.description () +
+ (e.core () ? " (core dumped)" : "") + "\n";
+ return result_status::abnormal;
+ }
+ }
+ catch (const process_error& e)
+ {
+ fail << "unable to execute bpkg " << cmd << ": " << e << endf;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read bpkg " << cmd << " diagnostics: " << e << endf;
+ }
+}
+
+static void
+build (size_t argc, const char* argv[])
+{
+ tracer trace ("build");
+
+ // Our overall plan is as follows:
+ //
+ // 1. Parse the task manifest (it should be in CWD).
+ //
+ // 2. Run bpkg to create the configuration, add the repository, and
+ // configure, build, and test the package all while saving the logs in
+ // the result manifest.
+ //
+ // 3. Upload the result manifest.
+ //
+ // Note also that we are being "watched" by the startup version of us which
+ // will upload an appropriate result in case we exit with an error. So here
+ // for abnormal situations (like a failure to parse the manifest), we just
+ // fail.
+ //
+ task_manifest tm (parse_manifest<task_manifest> (path ("manifest"), "task"));
+
+ result_manifest rm {
+ tm.name,
+ tm.version,
+ result_status::success,
+ operation_results {}
+ };
+
+ auto add_result = [&rm] (string o) -> operation_result&
+ {
+ rm.results.push_back (
+ operation_result {move (o), result_status::success, ""});
+
+ return rm.results.back ();
+ };
+
+ dir_path owd;
+
+ for (;;) // The "breakout" loop.
+ {
+ // Configure.
+ //
+ {
+ operation_result& r (add_result ("configure"));
+
+ // bpkg create <config-vars> <env-module> <env-config-vars>
+ //
+ const vector_view<const char*> env (argv + 1, argc - 1);
+
+ vector<string> cfg;
+ for (const variable& v: tm.config)
+ cfg.push_back (v.unquoted ());
+
+ dir_path dir ("build");
+
+ r.status |= run_bpkg (trace, r.log,
+ "create",
+ "-d", dir.string (),
+ "--wipe",
+ cfg,
+ env);
+
+ if (!r.status)
+ break;
+
+ owd = change_wd (dir);
+
+ // bpkg add <repository-url>
+ //
+ r.status |= run_bpkg (trace, r.log, "add", tm.repository.string ());
+
+ if (!r.status)
+ break;
+
+ // bpkg fetch
+ //
+ string t ("--trust-no");
+
+ cstrings ts;
+ for (const string& fp: tm.trust)
+ {
+ if (fp == "yes")
+ t = "--trust-yes";
+ else
+ {
+ ts.push_back ("--trust");
+ ts.push_back (fp.c_str ());
+ }
+ }
+
+ r.status |= run_bpkg (trace, r.log, "fetch", ts, t);
+
+ if (!r.status)
+ break;
+
+ // bpkg build --configure-only <package-name>/<package-version>
+ //
+ r.status |= run_bpkg (trace, r.log,
+ "build",
+ "--configure-only",
+ "--yes",
+ tm.name + '/' + tm.version.string ());
+
+ if (!r.status)
+ break;
+
+ rm.status |= r.status;
+ }
+
+ // Update.
+ //
+ {
+ operation_result& r (add_result ("update"));
+
+ // bpkg update <package-name>
+ //
+ r.status |= run_bpkg (trace, r.log, "update", tm.name);
+
+ if (!r.status)
+ break;
+
+ rm.status |= r.status;
+ }
+
+ // Test.
+ //
+ {
+ operation_result& r (add_result ("test"));
+
+ // bpkg test <package-name>
+ //
+ r.status |= run_bpkg (trace, r.log, "test", tm.name);
+
+ if (!r.status)
+ break;
+
+ rm.status |= r.status;
+ }
+
+ break;
+ }
+
+ rm.status |= rm.results.back ().status; // Merge last in case of a break.
+
+ if (!owd.empty ())
+ change_wd (owd);
+
+ // Upload the result.
+ //
+ const string url ("tftp://" + ops.tftp_host () + "/manifest");
+
+ try
+ {
+ tftp_curl c (trace,
+ path ("-"),
+ nullfd,
+ curl::put,
+ url,
+ "--max-time", tftp_timeout);
+
+ serialize_manifest (rm, c.out, url, "result");
+ c.out.close ();
+
+ if (!c.wait ())
+ throw_generic_error (EIO);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to upload result manifest to " << url << ": " << e;
+ }
+}
+
+static void
+startup ()
+{
+ tracer trace ("startup");
+
+ // Our overall plan is as follows:
+ //
+ // 1. Download the task manifest into the build directory (CWD).
+ //
+ // 2. Parse it and get the target.
+ //
+ // 3. Find the environment setup executable for this target.
+ //
+ // 4. Execute the environment setup executable.
+ //
+ // 5. If the environment setup executable fails, then upload the (failed)
+ // result ourselves.
+ //
+ const string url ("tftp://" + ops.tftp_host () + "/manifest");
+ const path mf ("manifest");
+
+ // If we fail, try to upload the result manifest (abnormal termination). The
+ // idea is that the machine gets suspended and we can investigate what's
+ // going on by logging in and examining the diagnostics (e.g., via
+ // journalctl, etc).
+ //
+ task_manifest tm;
+
+ try
+ {
+ // Download the task.
+ //
+ try
+ {
+ tftp_curl c (trace,
+ nullfd,
+ mf,
+ curl::get,
+ url,
+ "--max-time", tftp_timeout);
+
+ if (!c.wait ())
+ throw_generic_error (EIO);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to download task manifest from " << url << ": " << e;
+ }
+
+ // Parse it.
+ //
+ tm = parse_manifest<task_manifest> (mf, "task");
+
+ // Find the environment setup executable.
+ //
+ string tg;
+ process_path pp;
+
+ if (tm.target)
+ {
+ tg = tm.target->string ();
+
+ // While the executable path contains a directory (so the PATH search
+ // does not apply) we still use process::path_search() to automatically
+ // handle appending platform-specific executable extensions (.exe/.bat,
+ // etc).
+ //
+ pp = process::try_path_search (env_dir / tg, false);
+ }
+
+ if (pp.empty ())
+ pp = process::try_path_search (env_dir / "default", false);
+
+ if (pp.empty ())
+ fail << "no environment setup executable in " << env_dir << " "
+ << "for target '" << tg << "'";
+
+ // Run it.
+ //
+ // Note that we use the effective (absolute) path instead of recall since
+ // we may have changed the CWD.
+ //
+ run (trace, pp, tg, argv0.effect_string ());
+ }
+ catch (const failed&)
+ {
+ // If we failed before being able to parse the task manifest, use the
+ // "unknown" values for the package name and version.
+ //
+ result_manifest rm {
+ tm.name.empty () ? "unknown" : tm.name,
+ tm.version.empty () ? bpkg::version ("0") : tm.version,
+ result_status::abnormal,
+ operation_results {}
+ };
+
+ try
+ {
+ tftp_curl c (trace,
+ path ("-"),
+ nullfd,
+ curl::put,
+ url,
+ "--max-time", tftp_timeout);
+
+ serialize_manifest (rm, c.out, url, "result");
+ c.out.close ();
+
+ if (!c.wait ())
+ throw_generic_error (EIO);
+ }
+ catch (const system_error& e)
+ {
+ error << "unable to upload result manifest to " << url << ": " << e;
+ }
+
+ throw;
+ }
+}
+
+static void
+bootstrap ()
+{
+ bootstrap_manifest bm {
+ bootstrap_manifest::versions_type {
+ {"bbot", BBOT_VERSION},
+ {"libbbot", LIBBBOT_VERSION},
+ {"libbpkg", LIBBPKG_VERSION},
+ {"libbutl", LIBBUTL_VERSION}
+ }
+ };
+
+ serialize_manifest (bm, cout, "stdout", "bootstrap");
}
int
@@ -69,6 +478,8 @@ try
cli::argv_scanner scan (argc, argv, true);
ops.parse (scan);
+ verb = ops.verbose ();
+
// Version.
//
if (ops.version ())
@@ -94,8 +505,48 @@ try
return p.wait () ? 0 : 1;
}
- warn << "starting up" <<
- info << "lightly";
+ // Figure out our mode.
+ //
+ if (ops.bootstrap () && ops.startup ())
+ fail << "--bootstrap and --startup are mutually exclusive";
+
+ enum class mode {boot, start, build} m (mode::build);
+
+ if (ops.bootstrap ()) m = mode::boot;
+ if (ops.startup ()) m = mode::start;
+
+ // Figure out our path (used for re-exec).
+ //
+ argv0 = process::path_search (argv[0], true);
+
+ // Sort out the build directory.
+ //
+ if (ops.build_specified ())
+ change_wd (ops.build (), true); // Create if does not exist.
+
+ // Sort out the environment directory.
+ //
+ try
+ {
+ env_dir = ops.environment_specified ()
+ ? ops.environment ()
+ : dir_path::home_directory ();
+
+ if (!dir_exists (env_dir))
+ throw_generic_error (ENOENT);
+ }
+ catch (const system_error& e)
+ {
+ fail << "invalid environment directory: " << e;
+ }
+
+ switch (m)
+ {
+ case mode::boot: bootstrap (); break;
+ case mode::start: startup (); break;
+ case mode::build: build (static_cast<size_t> (argc),
+ const_cast<const char**> (argv)); break;
+ }
}
catch (const failed&)
{