aboutsummaryrefslogtreecommitdiff
path: root/bbot/worker/worker.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bbot/worker/worker.cxx')
-rw-r--r--bbot/worker/worker.cxx6047
1 files changed, 5025 insertions, 1022 deletions
diff --git a/bbot/worker/worker.cxx b/bbot/worker/worker.cxx
index eb7f50b..8fb7796 100644
--- a/bbot/worker/worker.cxx
+++ b/bbot/worker/worker.cxx
@@ -1,26 +1,28 @@
// file : bbot/worker.cxx -*- C++ -*-
-// license : TBC; see accompanying LICENSE file
+// license : MIT; see accompanying LICENSE file
#ifndef _WIN32
# include <signal.h> // signal()
#else
-# include <libbutl/win32-utility.hxx>
+# include <libbutl/win32-utility.hxx> // SetErrorMode(), Sleep()
#endif
#include <map>
#include <regex>
-#include <cstring> // strchr()
+#include <cstring> // strchr(), strncmp()
#include <sstream>
#include <iostream>
-#include <algorithm> // find(), find_if(), remove_if()
-#include <libbutl/b.mxx>
-#include <libbutl/pager.mxx>
-#include <libbutl/prompt.mxx>
-#include <libbutl/utility.mxx> // to_utf8()
-#include <libbutl/timestamp.mxx>
-#include <libbutl/filesystem.mxx>
-#include <libbutl/string-parser.mxx>
+#include <libbutl/b.hxx>
+#include <libbutl/pager.hxx>
+#include <libbutl/prompt.hxx>
+#include <libbutl/utility.hxx> // to_utf8(), eof()
+#include <libbutl/timestamp.hxx>
+#include <libbutl/filesystem.hxx>
+#include <libbutl/string-parser.hxx>
+#include <libbutl/manifest-serializer.hxx>
+
+#include <libbutl/json/parser.hxx>
#include <libbbot/manifest.hxx>
@@ -60,7 +62,7 @@ namespace bbot
const size_t tftp_get_retries (3); // Task request retries (see startup()).
}
-bool
+static bool
exists (const dir_path& d)
try
{
@@ -71,6 +73,17 @@ catch (const system_error& e)
fail << "unable to stat path " << d << ": " << e << endf;
}
+static bool
+exists (const path& f)
+try
+{
+ return file_exists (f, true /* follow_symlinks */);
+}
+catch (const system_error& e)
+{
+ fail << "unable to stat path " << f << ": " << e << endf;
+}
+
static dir_path
current_directory ()
try
@@ -82,11 +95,23 @@ catch (const system_error& e)
fail << "unable to obtain current directory: " << e << endf;
}
-static dir_path
-change_wd (tracer& t, string* log, const dir_path& d, bool create = false)
-try
+static void
+#ifndef _WIN32
+mk_p (tracer& t, string* log, const dir_path& d, bool sudo = false)
{
- if (create)
+ if (sudo)
+ {
+ if (log != nullptr)
+ *log += "sudo mkdir -p " + d.representation () + '\n';
+
+ run_io (t, 0, 1, 2, "sudo", "mkdir", "-p", d);
+ }
+ else
+#else
+mk_p (tracer& t, string* log, const dir_path& d, bool = false)
+{
+#endif
+ try
{
if (verb >= 3)
t << "mkdir -p " << d;
@@ -96,21 +121,55 @@ try
try_mkdir_p (d);
}
+ catch (const system_error& e)
+ {
+ fail << "unable to create directory " << d << ": " << e << endf;
+ }
+}
- dir_path r (current_directory ());
+static void
+mk (tracer& t, string* log, const dir_path& d)
+try
+{
+ if (verb >= 3)
+ t << "mkdir " << d;
+ if (log != nullptr)
+ *log += "mkdir " + d.representation () + '\n';
+
+ try_mkdir (d);
+}
+catch (const system_error& e)
+{
+ fail << "unable to create directory " << d << ": " << e << endf;
+}
+
+static bool
+empty (const dir_path& d)
+try
+{
+ return dir_empty (d);
+}
+catch (const system_error& e)
+{
+ fail << "unable to scan directory " << d << ": " << e << endf;
+}
+
+static void
+cp_into (tracer& t, string* log, const path& p, const dir_path& d)
+try
+{
if (verb >= 3)
- t << "cd " << d;
+ t << "cp " << p << ' ' << d;
if (log != nullptr)
- *log += "cd " + d.representation () + '\n';
+ *log += "cp " + p.string () + ' ' + d.representation () + '\n';
- dir_path::current_directory (d);
- return r;
+ cpfile_into (p, d);
}
catch (const system_error& e)
{
- fail << "unable to change current directory to " << d << ": " << e << endf;
+ fail << "unable to copy file " << p << " into " << d << ": " << e << endf;
}
static void
@@ -133,6 +192,25 @@ catch (const system_error& e)
}
static void
+mv_into (tracer& t, string* log, const path& from, const dir_path& into)
+try
+{
+ if (verb >= 3)
+ t << "mv " << from << ' ' << into;
+
+ if (log != nullptr)
+ *log += "mv " + from.representation () + ' ' + into.representation () +
+ "\n";
+
+ mventry_into (from, into);
+}
+catch (const system_error& e)
+{
+ fail << "unable to move entry '" << from << "' into '" << into << "': " << e
+ << endf;
+}
+
+static void
rm_r (tracer& t, string* log, const dir_path& d)
try
{
@@ -149,77 +227,330 @@ catch (const system_error& e)
fail << "unable to remove directory " << d << ": " << e << endf;
}
+static dir_path
+change_wd (tracer& t, string* log, const dir_path& d, bool create = false)
+try
+{
+ if (create)
+ mk_p (t, log, d);
+
+ dir_path r (current_directory ());
+
+ if (verb >= 3)
+ t << "cd " << d;
+
+ if (log != nullptr)
+ *log += "cd " + d.representation () + '\n';
+
+ dir_path::current_directory (d);
+ return r;
+}
+catch (const system_error& e)
+{
+ fail << "unable to change current directory to " << d << ": " << e << endf;
+}
+
// Step IDs.
//
+// NOTE: keep ids ordered according to the sequence of steps and remember to
+// update unreachable breakpoint checks if changing anything here.
+//
enum class step_id
{
- bpkg_module_create,
- bpkg_module_configure_add,
- bpkg_module_configure_fetch,
- bpkg_module_configure_build,
- bpkg_module_update,
- bpkg_module_test,
- bpkg_create,
+ // Common fallbacks for bpkg_*_create/b_test_installed_create and
+ // bpkg_*_configure_build/b_test_installed_configure, respectively. Note:
+ // not breakpoints.
+ //
+ b_create,
+ b_configure,
+
+ // Note that bpkg_module_* options are only used if the main package is a
+ // build system module (using just ~build2 otherwise). They also have no
+ // fallback (build system modules are just too different to try to handle
+ // them together with target and host; e.g., install root). However,
+ // bpkg_module_create is complemented with arguments from un-prefixed step
+ // ids, the same way as other *.create[_for_*] steps (note that un-prefixed
+ // steps are not fallbacks, they are always added first).
+ //
+ bpkg_create, // Breakpoint and base.
+ bpkg_target_create, //: b_create, bpkg_create
+ bpkg_host_create, //: b_create, bpkg_create
+ bpkg_module_create, //: no fallback
+
+ bpkg_link,
+
bpkg_configure_add,
bpkg_configure_fetch,
- bpkg_configure_build,
+
+ // Global (as opposed to package-specific) bpkg-pkg-build options (applies
+ // to all *_configure_build* steps). Note: not a breakpoint.
+ //
+ bpkg_global_configure_build,
+
+ // Note that bpkg_configure_build serves as a breakpoint for the
+ // bpkg-pkg-build call that configures (at once) the main package and all
+ // its external tests.
+ //
+ bpkg_configure_build, // Breakpoint and base.
+ bpkg_target_configure_build, //: b_configure, bpkg_configure_build
+ bpkg_host_configure_build, //: b_configure, bpkg_configure_build
+ bpkg_module_configure_build, //: b_configure, bpkg_configure_build
+
bpkg_update,
bpkg_test,
- bpkg_test_separate_configure_build,
- bpkg_test_separate_update,
- bpkg_test_separate_test,
+
+ // Note that separate test packages are configured as part of the
+ // bpkg_configure_build step above with options taken from
+ // bpkg_{target,host}_configure_build, depending on tests package type.
+ //
+ bpkg_test_separate_update, //: bpkg_update
+ bpkg_test_separate_test, //: bpkg_test
+
+ // Note that we only perform the installation tests if this is a target
+ // package or a self-hosted configuration. Also note that this step is
+ // considered disabled if any of the bpkg_bindist_* steps is explicitly
+ // enabled.
+ //
bpkg_install,
- b_test_installed_create,
- b_test_installed_configure,
+
+ bbot_install_ldconfig, // Note: disabled by default.
+
+ // Note that the bpkg_bindist_* steps are mutually exclusive and the latest
+ // status change for them (via the leading +/- characters in the prefix)
+ // overrides all the previous ones. Disabled by default.
+ //
+ bpkg_bindist_debian,
+ bpkg_bindist_fedora,
+ bpkg_bindist_archive,
+
+ // Note that this step is considered disabled unless one of the
+ // bpkg_bindist_* steps is explicitly enabled. Note: not a breakpoint.
+ //
+ bbot_sys_install,
+
+ bbot_sys_install_apt_get_update,
+ bbot_sys_install_apt_get_install,
+ bbot_sys_install_dnf_install,
+ bbot_sys_install_tar_extract,
+
+ bbot_sys_install_ldconfig, // Note: disabled by default.
+
+ // Note: skipped for modules.
+ //
+ b_test_installed_create, //: b_create
+ b_test_installed_configure, //: b_configure
b_test_installed_test,
- bpkg_test_installed_create,
- bpkg_test_installed_configure_add,
- bpkg_test_installed_configure_fetch,
- bpkg_test_separate_installed_configure_build,
- bpkg_test_separate_installed_update,
- bpkg_test_separate_installed_test,
+
+ // Note that for a host package this can involve both run-time and build-
+ // time tests (which means we may also need a shared configuration for
+ // modules).
+ //
+ // The *_for_{target,host,module} denote main package type, not
+ // configuration being created, which will always be target (more precisely,
+ // target or host, but host only in a self-hosted case, which means it's
+ // the same as target).
+ //
+ // Note that if this is a non-self-hosted configuration, we can only end up
+ // here if building target package and so can just use *_create and *_build
+ // values in buildtabs.
+ //
+ bpkg_test_separate_installed_create, // Breakpoint and base.
+ bpkg_test_separate_installed_create_for_target, //: bpkg_test_separate_installed_create
+ bpkg_test_separate_installed_create_for_host, //: bpkg_test_separate_installed_create
+ bpkg_test_separate_installed_create_for_module, //: no fallback
+
+ bpkg_test_separate_installed_link, // breakpoint only
+ bpkg_test_separate_installed_configure_add, //: bpkg_configure_add
+ bpkg_test_separate_installed_configure_fetch, //: bpkg_configure_fetch
+
+ bpkg_test_separate_installed_configure_build, // Breakpoint and base.
+ bpkg_test_separate_installed_configure_build_for_target, //: bpkg_test_separate_installed_configure_build
+ bpkg_test_separate_installed_configure_build_for_host, //: bpkg_test_separate_installed_configure_build
+ bpkg_test_separate_installed_configure_build_for_module, //: bpkg_test_separate_installed_configure_build
+
+ bpkg_test_separate_installed_update, //: bpkg_update
+ bpkg_test_separate_installed_test, //: bpkg_test
+
+ bbot_sys_uninstall_apt_get_remove,
+ bbot_sys_uninstall_dnf_remove,
+
bpkg_uninstall,
+
+ bbot_bindist_upload, // Note: disabled by default, not a breakpoint.
+
+ // Note that this step is considered disabled unless the upload/ directory
+ // is not empty. Note: not a breakpoint.
+ //
+ bbot_upload,
+
+ bbot_upload_tar_create,
+ bbot_upload_tar_list,
+
end
};
static const strings step_id_str {
- "bpkg.module.create",
- "bpkg.module.configure.add",
- "bpkg.module.configure.fetch",
- "bpkg.module.configure.build",
- "bpkg.module.update",
- "bpkg.module.test",
+ "b.create",
+ "b.configure",
+
"bpkg.create",
+ "bpkg.target.create",
+ "bpkg.host.create",
+ "bpkg.module.create",
+
+ "bpkg.link",
+
"bpkg.configure.add",
"bpkg.configure.fetch",
+
+ "bpkg.global.configure.build",
+
"bpkg.configure.build",
+ "bpkg.target.configure.build",
+ "bpkg.host.configure.build",
+ "bpkg.module.configure.build",
+
"bpkg.update",
"bpkg.test",
- "bpkg.test-separate.configure.build",
+
"bpkg.test-separate.update",
"bpkg.test-separate.test",
+
"bpkg.install",
+ "bbot.install.ldconfig",
+
+ "bpkg.bindist.debian",
+ "bpkg.bindist.fedora",
+ "bpkg.bindist.archive",
+
+ "bbot.sys-install",
+ "bbot.sys-install.apt-get.update",
+ "bbot.sys-install.apt-get.install",
+ "bbot.sys-install.dnf.install",
+ "bbot.sys-install.tar.extract",
+ "bbot.sys-install.ldconfig",
+
"b.test-installed.create",
"b.test-installed.configure",
"b.test-installed.test",
- "bpkg.test-installed.create",
- "bpkg.test-installed.configure.add",
- "bpkg.test-installed.configure.fetch",
+
+ "bpkg.test-separate-installed.create",
+ "bpkg.test-separate-installed.create_for_target",
+ "bpkg.test-separate-installed.create_for_host",
+ "bpkg.test-separate-installed.create_for_module",
+
+ "bpkg.test-separate-installed.link",
+ "bpkg.test-separate-installed.configure.add",
+ "bpkg.test-separate-installed.configure.fetch",
+
"bpkg.test-separate-installed.configure.build",
+ "bpkg.test-separate-installed.configure.build_for_target",
+ "bpkg.test-separate-installed.configure.build_for_host",
+ "bpkg.test-separate-installed.configure.build_for_module",
+
"bpkg.test-separate-installed.update",
"bpkg.test-separate-installed.test",
+
+ "bbot.sys-uninstall.apt-get.remove",
+ "bbot.sys-uninstall.dnf.remove",
+
"bpkg.uninstall",
+
+ "bbot.bindist.upload",
+
+ "bbot.upload",
+ "bbot.upload.tar.create",
+ "bbot.upload.tar.list",
+
"end"};
+static inline const string&
+to_string (step_id s)
+{
+ return step_id_str[static_cast<size_t> (s)];
+}
+
using std::regex;
namespace regex_constants = std::regex_constants;
using regexes = vector<regex>;
+// UTF-8-sanitize and log the line. Also print it to a tracer, if specified,
+// or to stderr otherwise at verbosity level 3 or higher.
+//
+static void
+log_line (string&& l, string& log, tracer* trace = nullptr)
+{
+ if (verb >= 3)
+ {
+ if (trace != nullptr)
+ *trace << l;
+ else
+ text << l;
+ }
+
+ to_utf8 (l, '?', codepoint_types::graphic, U"\n\r\t");
+
+ log += l;
+ log += '\n';
+}
+
+#ifndef _WIN32
+const char* comment_begin ("#");
+#else
+const char* comment_begin ("rem");
+#endif
+
+static void
+log_step_id (tracer& t, string& log, step_id id)
+{
+ string ts (to_string (system_clock::now (),
+ "%Y-%m-%d %H:%M:%S %Z",
+ true /* special */,
+ true /* local */));
+
+ const string& sid (to_string (id));
+
+ l3 ([&]{t << "step id: " << sid << ' ' << ts;});
+
+ log += comment_begin;
+ log += " step id: ";
+ log += sid;
+ log += ' ';
+ log += ts;
+ log += '\n';
+}
+
+// Add the specified string to the log as a comment. Unless the string is
+// empty (e.g., a blank line to separate comments), also trace it.
+//
+static void
+log_comment (tracer& t, string& log, const string& s)
+{
+ if (!s.empty ())
+ l3 ([&]{t << s;});
+
+ log += comment_begin;
+
+ if (!s.empty ())
+ {
+ log += ' ';
+ log += s;
+ }
+
+ log += '\n';
+}
+
// Run the worker script command. Name is used for logging and diagnostics
// only. Match lines read from the command's stderr against the regular
// expressions and return the warning result status (instead of success) in
// case of a match. Save the executed command into last_cmd.
//
+// Redirect stdout to stderr if the out_* arguments are not specified (out_str
+// is NULL and out_file is empty; must never be specified both). Otherwise,
+// save the process output into the variable referenced by out_str, if
+// specified, and to the file referenced by out_file otherwise. Note: in the
+// former case assumes that the output will always fit into the pipe buffer.
+//
// If bkp_step is present and is equal to the command step, then prior to
// running this command ask the user if to continue or abort the task
// execution. If bkp_status is present, then ask for that if the command
@@ -228,36 +559,28 @@ using regexes = vector<regex>;
// For the special end step no command is executed. In this case only the user
// is potentially prompted and the step is traced/logged.
//
+// If specified, the pre-run callback is called after the step id is logged
+// but before the command is logged/executed.
+//
+using pre_run_function = void ();
+
template <typename... A>
static result_status
run_cmd (step_id step,
tracer& t,
- string& log, const regexes& warn_detect,
+ string& log,
+ const function<pre_run_function>& pre_run,
+ optional<string>* out_str, const path& out_file,
+ const regexes& warn_detect,
const string& name,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const process_env& pe,
A&&... a)
{
- // UTF-8-sanitize and log the diagnostics. Also print the raw diagnostics
- // to stderr at verbosity level 3 or higher.
- //
- auto add = [&log, &t] (string&& s, bool trace = true)
- {
- if (verb >= 3)
- {
- if (trace)
- t << s;
- else
- text << s;
- }
-
- to_utf8 (s, '?', codepoint_types::graphic, U"\n\r\t");
-
- log += s;
- log += '\n';
- };
+ assert (out_str == nullptr || out_file.empty ());
string next_cmd;
@@ -266,60 +589,54 @@ run_cmd (step_id step,
//
struct abort {};
- auto prompt = [&last_cmd, &next_cmd, &add] (const string& what)
+ auto prompt = [&aux_env, &last_cmd, &next_cmd, &t, &log] (const string& what)
{
diag_record dr (text);
dr << '\n'
<< what << '\n'
- << " current dir: " << current_directory () << '\n'
- << " environment: " << ops.env_script () << ' ' << ops.env_target ();
+ << " current dir: " << current_directory () << '\n'
+ << " environment: " << ops.env_script () << ' ' << ops.env_target ();
+
+ if (!aux_env.empty ())
+ {
+ dr << '\n'
+ << " auxiliary environment:";
+
+ for (const string& e: aux_env)
+ dr << '\n'
+ << " " << e;
+ }
if (!last_cmd.empty ())
dr << '\n'
- << " last command: " << last_cmd;
+ << " last command: " << last_cmd;
if (!next_cmd.empty ())
dr << '\n'
- << " next command: " << next_cmd;
+ << " next command: " << next_cmd;
dr.flush ();
if (!yn_prompt (
"continue execution (or you may shutdown the machine)? [y/n]"))
{
- add ("execution aborted by interactive user");
+ log_line ("execution aborted by interactive user", log, &t);
throw abort ();
}
};
- auto prompt_step = [step, &t, &log, &bkp_step, &prompt] ()
+ auto prompt_step = [step, &t, &log, &bkp_step, &prompt, &pre_run] ()
{
- const string& sid (step_id_str[static_cast<size_t> (step)]);
-
// Prompt the user if the breakpoint is reached.
//
if (bkp_step && *bkp_step == step)
- prompt (sid + " step reached");
+ prompt (to_string (step) + " step reached");
- string ts (to_string (system_clock::now (),
- "%Y-%m-%d %H:%M:%S %Z",
- true /* special */,
- true /* local */));
+ log_step_id (t, log, step);
- // Log the step id and the command to be executed.
- //
- l3 ([&]{t << "step id: " << sid << ' ' << ts;});
-
-#ifndef _WIN32
- log += "# step id: ";
-#else
- log += "rem step id: ";
-#endif
- log += sid;
- log += ' ';
- log += ts;
- log += '\n';
+ if (pre_run)
+ pre_run ();
};
try
@@ -335,6 +652,8 @@ run_cmd (step_id step,
prompt_step ();
+ // Log the command to be executed.
+ //
t (c, n);
log += next_cmd;
@@ -347,27 +666,47 @@ run_cmd (step_id step,
{
try
{
- fdpipe pipe (fdopen_pipe ()); // Text mode seems appropriate.
+ // Redirect stdout to stderr, if the caller is not interested in it.
+ //
+ // Text mode seems appropriate.
+ //
+ fdpipe out_pipe (out_str != nullptr ? fdopen_pipe () : fdpipe ());
+ fdpipe err_pipe (fdopen_pipe ());
+
+ // If the output file is specified, then open "half-pipe".
+ //
+ if (!out_file.empty ())
+ try
+ {
+ out_pipe.out = fdopen (out_file,
+ fdopen_mode::out | fdopen_mode::create);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to open " << out_file << ": " << e;
+ }
process pr (
- process_start_callback (cmdc,
- fdopen_null (), // Never reads from stdin.
- 2, // 1>&2
- pipe,
- pe,
- forward<A> (a)...));
+ process_start_callback (
+ cmdc,
+ fdopen_null (), // Never reads from stdin.
+ out_pipe.out != nullfd ? out_pipe.out.get () : 2,
+ err_pipe,
+ pe,
+ forward<A> (a)...));
- pipe.out.close ();
+ out_pipe.out.close ();
+ err_pipe.out.close ();
{
// Skip on exception.
//
- ifdstream is (move (pipe.in), fdstream_mode::skip);
+ ifdstream is (move (err_pipe.in),
+ fdstream_mode::skip,
+ ifdstream::badbit);
- for (string l; is.peek () != ifdstream::traits_type::eof (); )
+ for (string l; !eof (getline (is, l)); )
{
- getline (is, l);
-
// Match the log line with the warning-detecting regular
// expressions until the first match.
//
@@ -392,17 +731,27 @@ run_cmd (step_id step,
}
}
- add (move (l), false /* trace */);
+ log_line (move (l), log);
}
}
if (!pr.wait ())
{
const process_exit& e (*pr.exit);
- add (name + " " + to_string (e));
+ log_line (name + ' ' + to_string (e), log, &t);
r = e.normal () ? result_status::error : result_status::abnormal;
}
+ // Only read the buffered output if the process terminated normally.
+ //
+ if (out_str != nullptr && pr.exit->normal ())
+ {
+ // Note: shouldn't throw since the output is buffered.
+ //
+ ifdstream is (move (out_pipe.in));
+ *out_str = is.read_text ();
+ }
+
last_cmd = move (next_cmd);
if (bkp_status && r >= *bkp_status)
@@ -434,6 +783,61 @@ run_cmd (step_id step,
}
}
+template <typename... A>
+static result_status
+run_cmd (step_id step,
+ tracer& t,
+ string& log,
+ optional<string>* out_str, const path& out_file,
+ const regexes& warn_detect,
+ const string& name,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const process_env& pe,
+ A&&... a)
+{
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* pre_run */,
+ out_str, out_file,
+ warn_detect,
+ name,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ pe,
+ forward<A> (a)...);
+}
+
+template <typename V, typename... A>
+static result_status
+run_bpkg (step_id step,
+ const V& envvars,
+ tracer& t,
+ string& log,
+ const function<pre_run_function>& pre_run,
+ optional<string>& out,
+ const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const char* verbosity,
+ const string& cmd, A&&... a)
+{
+ return run_cmd (step,
+ t,
+ log,
+ pre_run,
+ &out, path () /* out_file */,
+ warn_detect,
+ "bpkg " + cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("bpkg", envvars),
+ verbosity, cmd, forward<A> (a)...);
+}
+
template <typename V, typename... A>
static result_status
run_bpkg (step_id step,
@@ -442,15 +846,68 @@ run_bpkg (step_id step,
string& log, const regexes& warn_detect,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const char* verbosity,
+ const string& cmd, A&&... a)
+{
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file */,
+ warn_detect,
+ "bpkg " + cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("bpkg", envvars),
+ verbosity, cmd, forward<A> (a)...);
+}
+
+template <typename... A>
+static result_status
+run_bpkg (step_id step,
+ tracer& t,
+ string& log,
+ const function<pre_run_function>& pre_run,
+ optional<string>& out,
+ const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const char* verbosity,
+ const string& cmd, A&&... a)
+{
+ const char* const* envvars (nullptr);
+
+ return run_bpkg (step,
+ envvars,
+ t,
+ log,
+ pre_run,
+ out,
+ warn_detect,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ verbosity, cmd, forward<A> (a)...);
+}
+
+template <typename V, typename... A>
+static result_status
+run_bpkg (step_id step,
+ const V& envvars,
+ tracer& t,
+ string& log, const path& out, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const char* verbosity,
const string& cmd, A&&... a)
{
return run_cmd (step,
t,
- log, warn_detect,
+ log, nullptr /* out_str */, out, warn_detect,
"bpkg " + cmd,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
process_env ("bpkg", envvars),
verbosity, cmd, forward<A> (a)...);
}
@@ -459,9 +916,32 @@ template <typename... A>
static result_status
run_bpkg (step_id step,
tracer& t,
+ string& log, const path& out, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const char* verbosity,
+ const string& cmd, A&&... a)
+{
+ const char* const* envvars (nullptr);
+
+ return run_bpkg (step,
+ envvars,
+ t,
+ log, out, warn_detect,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ verbosity, cmd, forward<A> (a)...);
+}
+
+template <typename... A>
+static result_status
+run_bpkg (step_id step,
+ tracer& t,
string& log, const regexes& warn_detect,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const char* verbosity,
const string& cmd, A&&... a)
@@ -472,7 +952,7 @@ run_bpkg (step_id step,
envvars,
t,
log, warn_detect,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
verbosity, cmd, forward<A> (a)...);
}
@@ -484,6 +964,7 @@ run_b (step_id step,
string& log, const regexes& warn_detect,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const char* verbosity,
const strings& buildspecs, A&&... a)
@@ -498,10 +979,11 @@ run_b (step_id step,
}
return run_cmd (step,
- t,
- log, warn_detect,
+ t, log,
+ nullptr /* out_str */, path () /* out_file */,
+ warn_detect,
name,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
process_env ("b", envvars),
verbosity, buildspecs, forward<A> (a)...);
}
@@ -514,15 +996,17 @@ run_b (step_id step,
string& log, const regexes& warn_detect,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const char* verbosity,
const string& buildspec, A&&... a)
{
return run_cmd (step,
- t,
- log, warn_detect,
+ t, log,
+ nullptr /* out_str */, path () /* out_file */,
+ warn_detect,
"b " + buildspec,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
process_env ("b", envvars),
verbosity, buildspec, forward<A> (a)...);
}
@@ -534,6 +1018,7 @@ run_b (step_id step,
string& log, const regexes& warn_detect,
const optional<step_id>& bkp_step,
const optional<result_status>& bkp_status,
+ const strings& aux_env,
string& last_cmd,
const char* verbosity,
const string& buildspec, A&&... a)
@@ -543,10 +1028,131 @@ run_b (step_id step,
envvars,
t,
log, warn_detect,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
verbosity, buildspec, forward<A> (a)...);
}
+template <typename... A>
+static result_status
+run_ldconfig (step_id step,
+ tracer& t,
+ string& log, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ A&&... a)
+{
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file*/,
+ warn_detect,
+ "sudo ldconfig",
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("sudo"),
+ "ldconfig", forward<A> (a)...);
+}
+
+template <typename... A>
+static result_status
+run_apt_get (step_id step,
+ tracer& t,
+ string& log, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const string& cmd, A&&... a)
+{
+ // Note: dumps some of its diagnostics to stdout.
+ //
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file*/,
+ warn_detect,
+ "sudo apt-get " + cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("sudo"),
+ "apt-get", cmd, forward<A> (a)...);
+}
+
+template <typename... A>
+static result_status
+run_dnf (step_id step,
+ tracer& t,
+ string& log, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ const string& cmd, A&&... a)
+{
+ // Note: dumps some of its diagnostics to stdout.
+ //
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file*/,
+ warn_detect,
+ "sudo dnf " + cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("sudo"),
+ "dnf", cmd, forward<A> (a)...);
+}
+
+#ifndef _WIN32
+template <typename... A>
+static result_status
+run_tar (step_id step,
+ tracer& t,
+ string& log, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ bool sudo,
+ A&&... a)
+{
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file*/,
+ warn_detect,
+ sudo ? "sudo tar" : "tar",
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env (sudo ? "sudo" : "tar"),
+ sudo ? "tar" : nullptr, forward<A> (a)...);
+}
+#else
+template <typename... A>
+static result_status
+run_tar (step_id step,
+ tracer& t,
+ string& log, const regexes& warn_detect,
+ const optional<step_id>& bkp_step,
+ const optional<result_status>& bkp_status,
+ const strings& aux_env,
+ string& last_cmd,
+ bool /* sudo */,
+ A&&... a)
+{
+ // Note: using bsdtar which can unpack .zip archives (and also not an MSYS
+ // executable).
+ //
+ return run_cmd (step,
+ t,
+ log,
+ nullptr /* out_str */, path () /* out_file*/,
+ warn_detect,
+ "bsdtar",
+ bkp_step, bkp_status, aux_env, last_cmd,
+ process_env ("bsdtar"),
+ forward<A> (a)...);
+}
+#endif
+
// Upload compressed manifest to the specified TFTP URL with curl. Issue
// diagnostics and throw failed on invalid manifest or process management
// errors and throw io_error for input/output errors or non-zero curl exit.
@@ -567,6 +1173,11 @@ upload_manifest (tracer& trace,
// other hand, uploading from a file appears to work reliably (we still
// get an odd error on Windows from time to time with larger uploads).
//
+ // Let's not break lines in the manifest values not to further increase
+ // the size of the manifest encoded representation. Also here we don't
+ // care much about readability of the manifest since it will only be read
+ // by the bbot agent anyway.
+ //
#if 0
// Note: need to add compression support if re-enable this.
tftp_curl c (trace,
@@ -577,7 +1188,7 @@ upload_manifest (tracer& trace,
"--tftp-blksize", tftp_blksize,
"--max-time", tftp_put_timeout);
- manifest_serializer s (c.out, url);
+ manifest_serializer s (c.out, url, true /* long_lines */);
m.serialize (s);
c.out.close ();
#else
@@ -585,9 +1196,9 @@ upload_manifest (tracer& trace,
try
{
tmp = auto_rmfile (path::temp_path (what + "-manifest.lz4"));
- ofdstream ofs (tmp.path);
+ ofdstream ofs (tmp.path, fdopen_mode::binary);
olz4stream ozs (ofs, 9, 5 /* 256KB */, nullopt /* content_size */);
- manifest_serializer s (ozs, tmp.path.string ());
+ manifest_serializer s (ozs, tmp.path.string (), true /* long_lines */);
m.serialize (s);
ozs.close ();
ofs.close ();
@@ -632,24 +1243,39 @@ upload_manifest (tracer& trace,
}
}
+static strings
+parse_auxiliary_environment (const string&, const char*); // See below.
+
+static const string worker_checksum ("5"); // Logic version.
+
static int bbot::
build (size_t argc, const char* argv[])
{
- using namespace bpkg;
+ using std::map;
+ using std::multimap;
using string_parser::unquote;
+ using serializer = manifest_serializer;
+ using serialization = manifest_serialization;
+
+ using namespace bpkg;
+
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, test, optionally install, test installed and
- // uninstall the package all while saving the logs in the result manifest.
+ // 2. Run bpkg to create the package/tests configurations, add the
+ // repository to them, and configure, build, test, optionally install or
+ // alternatively bindist and sys-install, test installed, and
+ // (sys-)uninstall the package all while saving the logs in the result
+ // manifest.
//
- // 3. Upload the result manifest.
+ // 3. Upload the result manifest and, optionally, the build artifacts.
+ //
+ // NOTE: consider updating worker_checksum if making any logic changes.
//
// 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
@@ -659,42 +1285,35 @@ build (size_t argc, const char* argv[])
task_manifest tm (
parse_manifest<task_manifest> (path ("task.manifest"), "task"));
+ // Reset the dependency checksum if the task's worker checksum doesn't match
+ // the current one.
+ //
+ if (!tm.worker_checksum || *tm.worker_checksum != worker_checksum)
+ tm.dependency_checksum = nullopt;
+
result_manifest rm {
tm.name,
tm.version,
result_status::success,
- operation_results {}
+ operation_results {},
+ worker_checksum,
+ nullopt /* dependency_checksum */
};
- // Reserve storage large enough to hold all the potential operation results
- // without reallocations. Note that this is not an optimization but is
- // required to make sure that element references are not invalidated when
- // new results are added.
- //
- size_t max_results (6);
- rm.results.reserve (max_results);
-
- auto add_result = [&rm, max_results] (string o) -> operation_result&
+ auto add_result = [&rm] (string o) -> operation_result&
{
- assert (rm.results.size () < max_results);
-
rm.results.push_back (
operation_result {move (o), result_status::success, ""});
return rm.results.back ();
};
- // Note that we don't consider the build system module configuring and
- // testing during the "pre-step" as separate operations and share the
- // operation logs with the "main" configure and test steps (see below).
- // Thus, we save pointers to the added result objects for the subsequent
- // use.
- //
- operation_result* configure_result (nullptr);
- operation_result* test_result (nullptr);
-
dir_path rwd; // Root working directory.
+ // Archive of the build artifacts for upload.
+ //
+ path upload_archive ("upload.tar");
+
// Resolve the breakpoint specified by the interactive manifest value into
// the step id or the result status breakpoint. If the breakpoint is
// invalid, then log the error and abort the build. Note that we reuse the
@@ -704,8 +1323,49 @@ build (size_t argc, const char* argv[])
optional<result_status> bkp_status;
string last_cmd; // Used in the user prompt.
+ // Parse the auxiliary environment, if present, to dump it into the
+ // configure operation log and to use it in the interactive build user
+ // prompt. Note that this environment is already set by the parent process.
+ //
+ strings aux_env;
+
for (;;) // The "breakout" loop.
{
+ auto fail_operation = [&trace] (operation_result& r,
+ const string& e,
+ result_status s,
+ const string& name = "",
+ uint64_t line = 0,
+ uint64_t column = 0)
+ {
+ string prefix;
+
+ if (!name.empty ())
+ {
+ prefix += name;
+ prefix += ':';
+
+ if (line != 0)
+ {
+ prefix += to_string (line);
+ prefix += ':';
+
+ if (column != 0)
+ {
+ prefix += to_string (column);
+ prefix += ':';
+ }
+ }
+
+ prefix += ' ';
+ }
+
+ l3 ([&]{trace << prefix << e;});
+
+ r.log += prefix + "error: " + e + '\n';
+ r.status = s;
+ };
+
// Regular expressions that detect different forms of build2 toolchain
// warnings. Accidently (or not), they also cover GCC and Clang warnings
// (for the English locale).
@@ -723,7 +1383,7 @@ build (size_t argc, const char* argv[])
for (const string& re: tm.unquoted_warning_regex ())
wre.emplace_back (re, f);
- if (tm.interactive)
+ if (tm.interactive && *tm.interactive != "none")
{
const string& b (*tm.interactive);
@@ -745,171 +1405,571 @@ build (size_t argc, const char* argv[])
if (!bkp_step && !bkp_status)
{
- string e ("invalid interactive build breakpoint '" + b + "'");
-
- l3 ([&]{trace << e;});
-
- operation_result& r (add_result ("configure"));
-
- r.log = "error: " + e + '\n';
- r.status = result_status::abort;
+ fail_operation (add_result ("configure"),
+ "invalid interactive build breakpoint '" + b + '\'',
+ result_status::abort);
break;
}
}
+ // Parse the auxiliary environment, if present.
+ //
+ if (tm.auxiliary_environment)
+ {
+ // Note: cannot throw since has already been called successfully by the
+ // parent process.
+ //
+ aux_env = parse_auxiliary_environment (*tm.auxiliary_environment,
+ comment_begin);
+ }
+
// Split the argument into prefix (empty if not present) and unquoted
- // value. Return nullopt if the prefix is invalid.
+ // value (absent if not present) and determine the step status. If the
+ // prefix is present and is prepended with the '+'/'-' character, then the
+ // respective step needs to be enabled/disabled. Return nullopt if the
+ // prefix is invalid.
+ //
+ // Note that arguments with absent values are normally used to
+ // enable/disable steps and are omitted from the command lines.
//
- auto parse_arg = [] (const string& a) -> optional<pair<string, string>>
+ struct argument
+ {
+ string prefix;
+
+ // Absent if the argument value is an empty unquoted string.
+ //
+ optional<string> value;
+
+ // True - enable, false - disable, nullopt - neutral.
+ //
+ optional<bool> step_status;
+ };
+
+ auto parse_arg = [] (const string& a) -> optional<argument>
{
size_t p (a.find_first_of (":=\"'"));
+ auto value = [] (const string& v)
+ {
+ return !v.empty () ? unquote (v) : optional<string> ();
+ };
+
if (p == string::npos || a[p] != ':') // No prefix.
- return make_pair (string (), unquote (a));
+ return argument {string (), value (a), nullopt};
+
+ string prefix (a, 0, p);
+
+ optional<bool> enable;
+ if (prefix[0] == '+' || prefix[0] == '-')
+ {
+ enable = (prefix[0] == '+');
+
+ prefix.erase (0, 1);
+
+ if (prefix != "bpkg.update" &&
+ prefix != "bpkg.test" &&
+ prefix != "bpkg.test-separate.update" &&
+ prefix != "bpkg.test-separate.test" &&
+ prefix != "bpkg.install" &&
+ prefix != "bbot.install.ldconfig" &&
+ prefix != "bpkg.bindist.debian" &&
+ prefix != "bpkg.bindist.fedora" &&
+ prefix != "bpkg.bindist.archive" &&
+ prefix != "bbot.sys-install" &&
+ prefix != "bbot.sys-install.ldconfig" &&
+ prefix != "b.test-installed.test" &&
+ prefix != "bpkg.test-separate-installed.update" &&
+ prefix != "bpkg.test-separate-installed.test" &&
+ prefix != "bbot.bindist.upload" &&
+ prefix != "bbot.upload")
+ {
+ return nullopt; // Prefix is invalid.
+ }
+ }
for (const string& id: step_id_str)
{
- if (a.compare (0, p, id, 0, p) == 0 &&
- (id.size () == p || (id.size () > p && id[p] == '.')))
- return make_pair (a.substr (0, p), unquote (a.substr (p + 1)));
+ size_t n (prefix.size ());
+ if (id.compare (0, n, prefix) == 0 &&
+ (id.size () == n || (id.size () > n && id[n] == '.')))
+ return argument {move (prefix), value (a.substr (p + 1)), enable};
}
return nullopt; // Prefix is invalid.
};
- // Enter split arguments into a map. Those without a prefix are
- // entered for the *.create steps.
+ // Keep track of explicitly enabled/disabled steps.
+ //
+ map<string, bool> step_statuses;
+
+ // Return true if the step is explicitly enabled via a +<prefix>:[<value>]
+ // environment/configuration argument.
//
- auto add_arg = [] (std::multimap<string, string>& args,
- pair<string, string>&& a)
+ auto step_enabled = [&step_statuses] (step_id step) -> bool
{
- if (!a.first.empty ())
- args.emplace (move (a));
- else
+ auto i (step_statuses.find (to_string (step)));
+ return i != step_statuses.end () && i->second;
+ };
+
+ // Return true if the step is explicitly disabled via a -<prefix>:[<value>]
+ // environment/configuration argument.
+ //
+ auto step_disabled = [&step_statuses] (step_id step) -> bool
+ {
+ auto i (step_statuses.find (to_string (step)));
+ return i != step_statuses.end () && !i->second;
+ };
+
+ // Save a step status.
+ //
+ // Note that since the bpkg.bindist.* steps are mutually exclusive we only
+ // keep the latest status change (see above for details).
+ //
+ auto step_status = [&step_statuses] (const string& step, bool status)
+ {
+ if (step.compare (0, 13, "bpkg.bindist.") == 0)
{
- args.emplace ("bpkg.create", a.second);
- args.emplace ("b.test-installed.create", a.second);
- args.emplace ("bpkg.test-installed.create", move (a.second));
+ step_statuses.erase ("bpkg.bindist.debian");
+ step_statuses.erase ("bpkg.bindist.fedora");
+ step_statuses.erase ("bpkg.bindist.archive");
}
+
+ step_statuses[step] = status;
};
- // Parse configuration arguments. Report failures to the bbot controller.
+ // Parse the environment, target configuration, and build package
+ // configuration arguments.
+ //
+ // NOTE: keep this parsing order intact so that, for example, a build
+ // package configuration argument can override step status specified
+ // by a target configuration argument.
+ //
+
+ // Parse environment arguments.
+ //
+ multimap<string, string> modules;
+ multimap<string, string> env_args;
+
+ for (size_t i (1); i != argc; ++i)
+ {
+ const char* a (argv[i]);
+ optional<argument> v (parse_arg (a));
+
+ if (!v)
+ fail << "invalid environment argument prefix in '" << a << "'";
+
+ bool mod (v->value &&
+ (*v->value)[0] != '-' &&
+ v->value->find ('=') == string::npos);
+
+ if (mod &&
+ !v->prefix.empty () &&
+ v->prefix != "b.create" &&
+ v->prefix != "bpkg.create" &&
+ v->prefix != "bpkg.target.create" &&
+ v->prefix != "bpkg.host.create" &&
+ v->prefix != "bpkg.module.create" &&
+ v->prefix != "b.test-installed.create" &&
+ v->prefix != "bpkg.test-separate-installed.create" &&
+ v->prefix != "bpkg.test-separate-installed.create_for_target" &&
+ v->prefix != "bpkg.test-separate-installed.create_for_host" &&
+ v->prefix != "bpkg.test-separate-installed.create_for_module")
+ fail << "invalid module prefix in '" << a << "'";
+
+ if (v->step_status)
+ step_status (v->prefix, *v->step_status);
+
+ if (v->value)
+ (mod ? modules : env_args).emplace (make_pair (move (v->prefix),
+ move (*v->value)));
+ }
+
+ // Parse target configuration arguments. Report failures to the bbot
+ // controller.
//
- std::multimap<string, string> config_args;
+ multimap<string, string> tgt_args;
- for (const string& c: tm.config)
+ for (const string& c: tm.target_config)
{
- optional<pair<string, string>> v (parse_arg (c));
+ optional<argument> v (parse_arg (c));
if (!v)
{
rm.status |= result_status::abort;
- l3 ([&]{trace << "invalid configuration argument prefix in "
+ l3 ([&]{trace << "invalid target configuration argument prefix in "
<< "'" << c << "'";});
break;
}
- if (v->second[0] != '-' && v->second.find ('=') == string::npos)
+ if (v->value &&
+ (*v->value)[0] != '-' &&
+ v->value->find ('=') == string::npos)
{
rm.status |= result_status::abort;
- l3 ([&]{trace << "invalid configuration argument '" << c << "'";});
+ l3 ([&]{trace << "invalid target configuration argument '" << c
+ << "'";});
break;
}
- add_arg (config_args, move (*v));
+ if (v->step_status)
+ step_status (v->prefix, *v->step_status);
+
+ if (v->value)
+ tgt_args.emplace (make_pair (move (v->prefix), move (*v->value)));
}
if (!rm.status)
break;
- // Parse environment arguments.
+ // Parse the build package configuration represented as a whitespace
+ // separated list of the following potentially quoted bpkg-pkg-build
+ // command arguments:
//
- std::multimap<string, string> modules;
- std::multimap<string, string> env_args;
-
- for (size_t i (1); i != argc; ++i)
+ // <option>...
+ // <config-var>...
+ // ([{ <config-var>... }+] (?[sys:]|sys:)<pkg-name>[<version-spec>])...
+ // ( { <config-var>... }+ <pkg-name>)...
+ //
+ // If the package configuration is specified, then parse it into the
+ // following lists/maps:
+ //
+ // - The prefixed global options and configuration variables map
+ // (pkg_args). Added to the command lines at the corresponding steps
+ // after potential environment and target configuration arguments.
+ //
+ // - The unprefixed global options list (pkg_config_opts). Specified after
+ // all the prefixed global options on the bpkg-pkg-build command line at
+ // the bpkg.configure.build step.
+ //
+ // - The main package-specific configuration variables list
+ // (pkg_config_vars). Specified for the main package only on the
+ // bpkg-pkg-build command line at the bpkg.configure.build step,
+ // wherever it is configured. Also specified on the b-configure command
+ // line at the b.test-installed.configure step.
+ //
+ // - The main package-specific dependency packages list
+ // (pkg_config_main_deps), potentially with their own configuration
+ // variables (but not options). Only configured where the main package
+ // is configured with the bpkg-pkg-build command line at the
+ // bpkg.configure.build step.
+ //
+ // - The global system dependency packages list (pkg_config_glob_deps).
+ // Configured in all configurations with the bpkg-pkg-build command line
+ // at the bpkg.configure.build step.
+ //
+ // - The main and external test package-specific configuration variables
+ // map (pkg_config_pkgs). Specified on the bpkg-pkg-build command lines
+ // at the bpkg.configure.build and
+ // bpkg.test-separate-installed.configure.build steps. Package names
+ // other than the main and external test package names are silently
+ // ignored.
+ //
+ multimap<string, string> pkg_args;
+ strings pkg_config_opts;
+ strings pkg_config_vars;
+ vector<pair<string, strings>> pkg_config_main_deps; // ?<pkg>, sys:<pkg>
+ vector<pair<string, strings>> pkg_config_glob_deps; // ?sys:<pkg>
+ map<string, strings> pkg_config_pkgs; // <pkg>
+
+ if (!tm.package_config.empty ())
{
- const char* a (argv[i]);
- optional<pair<string, string>> v (parse_arg (a));
+ struct abort {};
- if (!v)
- fail << "invalid environment argument prefix in '" << a << "'";
+ auto fail = [&tm, &add_result, &fail_operation] (const string& d,
+ bool throw_abort = true)
+ {
+ fail_operation (add_result ("configure"),
+ "invalid package configuration: " + d +
+ "\n info: package configuration: '" +
+ tm.package_config + '\'',
+ result_status::abort);
- bool mod (v->second[0] != '-' && v->second.find ('=') == string::npos);
+ if (throw_abort)
+ throw abort ();
+ };
- if (mod && !v->first.empty () &&
- v->first != "bpkg.create" &&
- v->first != "b.test-installed.create" &&
- v->first != "bpkg.test-installed.create")
- fail << "invalid module prefix in '" << a << "'";
+ try
+ {
+ strings argsv (string_parser::parse_quoted (tm.package_config,
+ false /* unquote */));
+
+ cli::vector_scanner scanv (argsv);
+ cli::group_scanner args (scanv);
+
+ while (args.more ())
+ {
+ string a (args.next ());
+
+ // Unless the argument is an unquoted dependency (starts with `?` or
+ // `sys:`), first try to interpret it as a prefixed option/variable
+ // and/or step id status (enabled/disabled).
+ //
+ if (!(a[0] == '?' || a.compare (0, 4, "sys:") == 0))
+ {
+ optional<argument> v (parse_arg (a));
+
+ // Note that we only assume an argument as prefixed if the prefix
+ // is a known step id. Otherwise, we interpret the argument as
+ // unprefixed global option, variable, or a package spec.
+ //
+ if (v && !v->prefix.empty ())
+ {
+ if (v->value &&
+ (*v->value)[0] != '-' &&
+ v->value->find ('=') == string::npos)
+ fail ("invalid prefixed argument '" + a + '\'');
- add_arg (mod ? modules : env_args, move (*v));
+ if (args.group ().more ())
+ fail ("unexpected options group for prefixed argument '" + a +
+ '\'');
+
+ if (v->step_status)
+ step_status (v->prefix, *v->step_status);
+
+ if (v->value)
+ pkg_args.emplace (make_pair (move (v->prefix),
+ move (*v->value)));
+
+ continue;
+ }
+ }
+
+ a = unquote (a);
+
+ // Return true if the argument is an option.
+ //
+ // Note that options with values can only be specified using
+ // the single argument notation.
+ //
+ auto opt = [] (const string& a)
+ {
+ // Make sure that -- or - is always followed by some characters.
+ //
+ return a.compare (0, 2, "--") == 0 ? a.size () > 2 :
+ a[0] == '-' ? a.size () > 1 :
+ false ;
+ };
+
+ // Return true if the argument is a configuration variable.
+ //
+ auto var = [] (const string& a)
+ {
+ // Note: we need to be careful not to misinterpret
+ // '?libfoo == 1.0.0' as a variable.
+ //
+ return a.compare (0, 7, "config.") == 0 &&
+ a.find ('=') != string::npos;
+ };
+
+ bool o (opt (a));
+ bool v (var (a));
+
+ cli::scanner& ag (args.group ());
+
+ if (o) // Option.
+ {
+ if (ag.more ())
+ fail ("unexpected options group for option '" + a + '\'');
+
+ pkg_config_opts.push_back (move (a));
+ }
+ else if (v) // Configuration variable.
+ {
+ if (ag.more ())
+ fail ("unexpected options group for configuration variable '" +
+ a + '\'');
+
+ pkg_config_vars.push_back (move (a));
+ }
+ else // Dependency or build-to-hold package.
+ {
+ // Note that we consider a system package as a dependency
+ // regardless whether it is prefixed with '?' or not.
+ //
+ strings vars;
+ while (ag.more ())
+ {
+ string da (unquote (ag.next ()));
+ if (!var (da))
+ fail ("argument is not a configuration variable for "
+ "dependency " + a + ": '" + da + '\'');
+
+ vars.push_back (move (da));
+ }
+
+ // Add the system dependency packages (prefixed with `?sys:`) to
+ // a separate list, to specify them globally on the
+ // bpkg-pkg-build command line for configuring them in all the
+ // (being) created configurations.
+ //
+ // Note, though, that we will handle the build-to-hold system
+ // packages (prefixed with `sys:`) in the same way as non system
+ // dependencies, since such an auto-configuration is only
+ // supported by bpkg-pkg-build for system dependencies. In the
+ // future, we may support that on the bbot worker level by, for
+ // example, specifying all the configurations manually for the
+ // build-to-hold system packages and also specifying them as a
+ // system dependencies globally. We need to be careful to make
+ // sure that these dependencies are also auto-configured for the
+ // private configurations potentially created by bpkg-pkg-build.
+ //
+ // Also note that in the future we may allow package-specific
+ // --config-uuid options to only configure such packages in the
+ // specified configurations. We may also invent the special
+ // 00000000-0000-0000-0000-000000000005 configuration id to, for
+ // example, only configure them at the
+ // bpkg.test-separate-installed.configure.build step.
+ //
+ if (a.compare (0, 5, "?sys:") == 0) // Global system dependency.
+ {
+ pkg_config_glob_deps.push_back (make_pair (move (a),
+ move (vars)));
+ }
+ else if (a[0] == '?' || // Main package dependency.
+ a.compare (0, 4, "sys:") == 0)
+ {
+ pkg_config_main_deps.push_back (make_pair (move (a),
+ move (vars)));
+ }
+ else // Build-to-hold package.
+ {
+ if (vars.empty ())
+ fail ("no configuration variables specified for package '" +
+ a + '\'');
+
+ auto i (pkg_config_pkgs.find (a));
+
+ if (i == pkg_config_pkgs.end ())
+ {
+ pkg_config_pkgs.emplace (move (a), move (vars));
+ }
+ else
+ {
+ strings& vs (i->second);
+ vs.insert (vs.end (),
+ make_move_iterator (vars.begin ()),
+ make_move_iterator (vars.end ()));
+ }
+ }
+ }
+ }
+ }
+ catch (const cli::exception& e)
+ {
+ fail (e.what (), false /* throw_abort */);
+ break;
+ }
+ catch (const string_parser::invalid_string& e)
+ {
+ fail (e.what (), false /* throw_abort */);
+ break;
+ }
+ catch (const abort&)
+ {
+ break;
+ }
}
- // Return command arguments for the specified step id. Arguments with more
- // specific prefixes come last.
+ // Return command arguments for the specified step id, complementing
+ // *.create[_for_*] steps with un-prefixed arguments. If no arguments are
+ // specified for the step then use the specified fallbacks, potentially
+ // both. Arguments with more specific prefixes come last. Optionally,
+ // search for arguments starting from the specified step id rather than
+ // from the least specific one (tool id).
//
- auto step_args = [] (const std::multimap<string, string>& args,
+ auto step_args = [] (const multimap<string, string>& args,
step_id step,
- optional<step_id> fallback = nullopt) -> strings
+ optional<step_id> fallback1 = nullopt,
+ optional<step_id> fallback2 = nullopt,
+ optional<step_id> start_step = nullopt) -> cstrings
{
- strings r;
- const string& sid (step_id_str[static_cast<size_t> (step)]);
+ cstrings r;
- // If no arguments found for the step id, then use the fallback step id,
- // if specified.
+ // Add arguments for a specified, potentially empty, prefix.
//
- const string& s (args.find (sid) == args.end () && fallback
- ? step_id_str[static_cast<size_t> (*fallback)]
- : sid);
-
- for (size_t n (0);; ++n)
+ auto add_args = [&args, &r] (const string& prefix)
{
- n = s.find ('.', n);
-
- auto range (
- args.equal_range (n == string::npos ? s : string (s, 0, n)));
+ auto range (args.equal_range (prefix));
for (auto i (range.first); i != range.second; ++i)
- r.emplace_back (i->second);
+ r.emplace_back (i->second.c_str ());
+ };
- if (n == string::npos)
+ // Add un-prefixed arguments if this is one of the *.create[_for_*]
+ // steps.
+ //
+ switch (step)
+ {
+ case step_id::b_create:
+ case step_id::bpkg_create:
+ case step_id::bpkg_target_create:
+ case step_id::bpkg_host_create:
+ case step_id::bpkg_module_create:
+ case step_id::b_test_installed_create:
+ case step_id::bpkg_test_separate_installed_create:
+ case step_id::bpkg_test_separate_installed_create_for_target:
+ case step_id::bpkg_test_separate_installed_create_for_host:
+ case step_id::bpkg_test_separate_installed_create_for_module:
+ {
+ add_args ("");
break;
+ }
+ default: break;
}
- return r;
- };
+ auto add_step_args = [&add_args] (step_id step,
+ optional<step_id> start_step = nullopt)
+ {
+ const string& s (to_string (step));
- // Search for config.install.root variable. If it is present and has a
- // non-empty value, then test the package installation and uninstall. Note
- // that passing [null] value would be meaningless, so we don't recognize
- // it as a special one. While at it, cache the bpkg.create args for later
- // use.
- //
- dir_path install_root;
- strings cargs (step_args (config_args, step_id::bpkg_create));
- {
- size_t n (19);
- auto space = [] (char c) {return c == ' ' || c == '\t';};
+ size_t n;
- for (const string& s: reverse_iterate (cargs))
- {
- if (s.compare (0, n, "config.install.root") == 0 &&
- (s[n] == '=' || space (s[n])))
+ if (start_step)
{
- while (space (s[n])) ++n; // Skip spaces.
- if (s[n] == '=') ++n; // Skip the equal sign.
- while (space (s[n])) ++n; // Skip spaces.
+ const string& ss (to_string (*start_step));
- // Note that the config.install.root variable value may
- // potentially be quoted.
- //
- install_root = dir_path (unquote (string (s, n, s.size () - n)));
- break;
+ assert (s.size () >= ss.size () &&
+ s.compare (0, ss.size (), ss) == 0 &&
+ (s.size () == ss.size () || s[ss.size ()] == '.'));
+
+ n = ss.size ();
}
+ else
+ n = 0;
+
+ for (;; ++n)
+ {
+ n = s.find ('.', n);
+
+ add_args (n == string::npos ? s : string (s, 0, n));
+
+ if (n == string::npos)
+ break;
+ }
+ };
+
+ // If no arguments found for the step id, then use the fallback step
+ // ids, if specified.
+ //
+ if (args.find (to_string (step)) != args.end ())
+ {
+ add_step_args (step, start_step);
}
- }
+ else
+ {
+ // Note that if we ever need to specify fallback pairs with common
+ // ancestors, we may want to suppress duplicate ancestor step ids.
+ //
+ if (fallback1)
+ add_step_args (*fallback1);
+
+ if (fallback2)
+ add_step_args (*fallback2);
+ }
+
+ return r;
+ };
// bpkg-rep-fetch trust options.
//
@@ -935,6 +1995,7 @@ build (size_t argc, const char* argv[])
const version& ver (tm.version);
const string repo (tm.repository.string ());
const dir_path pkg_dir (pkg + '-' + ver.string ());
+ const string pkg_var (tm.name.variable ());
// Specify the revision explicitly for the bpkg-build command not to end
// up with a race condition building the latest revision rather than the
@@ -951,7 +2012,7 @@ build (size_t argc, const char* argv[])
// Query the project's build system information with `b info`.
//
auto prj_info = [&trace] (const dir_path& d,
- bool ext_mods,
+ b_info_flags fl,
const char* what)
{
// Note that the `b info` diagnostics won't be copied into any of the
@@ -962,7 +2023,7 @@ build (size_t argc, const char* argv[])
//
try
{
- return b_info (d, ext_mods, verb, trace);
+ return b_info (d, fl, verb, trace);
}
catch (const b_error& e)
{
@@ -974,10 +2035,14 @@ build (size_t argc, const char* argv[])
}
};
- b_project_info prj; // Package project information.
-
rwd = current_directory ();
+ // Create directory for the build artifacts to archive and upload.
+ //
+ dir_path upload_dir ("upload");
+
+ mk (trace, nullptr /* log */, upload_dir);
+
// If the package comes from a version control-based repository, then we
// will also test its dist meta-operation. Specifically, we will checkout
// the package outside the configuration directory passing --checkout-root
@@ -988,6 +2053,11 @@ build (size_t argc, const char* argv[])
dir_path dist_root (rwd / dir_path ("dist"));
dir_path dist_src (dist_root / pkg_dir);
+ dir_path dist_install_root (rwd / dir_path ("dist-install"));
+ dir_path dist_install_src (dist_install_root / pkg_dir);
+
+ dir_path dist_installed_root (rwd / dir_path ("dist-installed"));
+
// Redistribute the package source directory (pkg_dir) checked out into
// the directory other than the configuration directory (dist_root) and
// replace it with the newly created distribution. Assume that the current
@@ -996,12 +2066,12 @@ build (size_t argc, const char* argv[])
// for the build2 process. Return true if the dist meta-operation
// succeeds.
//
- auto redist = [&trace, &wre, &bkp_step, &bkp_status, &last_cmd]
+ auto redist = [&trace, &wre, &bkp_step, &bkp_status, &aux_env, &last_cmd]
(step_id step,
operation_result& r,
const dir_path& dist_root,
const dir_path& pkg_dir, // <name>-<version>
- const char* import = nullptr,
+ const optional<string>& import = nullopt,
const small_vector<string, 1>& envvars = {})
{
// Temporarily change the current directory to the distribution root
@@ -1026,7 +2096,7 @@ build (size_t argc, const char* argv[])
step,
envvars,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
"config.dist.root=" + redist_root.string (),
import,
@@ -1045,998 +2115,3763 @@ build (size_t argc, const char* argv[])
return true;
};
- // The module phase.
+ // Note that if this is not a self-hosted configuration, then we do not
+ // build external runtime tests nor run internal for host or module
+ // packages because the assumption is that they have been built/run (and
+ // with buildtab settings such as warnings, etc) when testing the
+ // self-hosted configuration this non-self-hosted one is based on. Also,
+ // by the same reason, we don't install tools or modules nor generate the
+ // binary distribution packages for them for non-self-hosted
+ // configurations.
+ //
+ // Actually, it could make sense to build and install tools and module
+ // from a target configuration in this case. But that means for a
+ // non-self-hosted configuration a tool/module may want to test two
+ // things: its output build and its own build, which means we would need a
+ // way to control which of the two things (or both) are to be tested
+ // (think of two cross-compiler configurations, Emscripten and MinGW: for
+ // the former a source code generator would normally only want to test the
+ // output while for the latter -- both; maybe we could have a `cross-host`
+ // class, meaning that the configuration is not host itself but its target
+ // is). In any case, seeing that there is no way to verify such own build
+ // works, we ignore this for now.
+ //
+ // Also note that build system modules can only have external build-time
+ // tests (which is verified by bpkg-rep-fetch) and target packages cannot
+ // have external build-time tests (which we verify ourselves).
+ //
+ bool selfhost (tm.host && *tm.host);
+
+ // Detect if the package is of the target, host, or module type.
+ //
+ auto requirement = [&tm] (const char* id)
+ {
+ return find_if (tm.requirements.begin (),
+ tm.requirements.end (),
+ [id] (const requirement_alternatives& r)
+ {
+ if (r.size () == 1)
+ {
+ const requirement_alternative& a (r[0]);
+ return find (a.begin (), a.end (), id) != a.end ();
+ }
+
+ return false;
+ }) != tm.requirements.end ();
+ };
+
+ bool module_pkg (pkg.compare (0, 10, "libbuild2-") == 0);
+ bool bootstrap (module_pkg && requirement ("bootstrap"));
+ bool host_pkg (!module_pkg && requirement ("host"));
+ bool target_pkg (!module_pkg && !host_pkg);
+
+ // Don't generate binary packages for tools or modules for non-self-hosted
+ // configurations (see above for details).
+ //
+ optional<step_id> bindist;
+
+ if (target_pkg || selfhost)
+ {
+ if (step_enabled (step_id::bpkg_bindist_debian))
+ bindist = step_id::bpkg_bindist_debian;
+ else if (step_enabled (step_id::bpkg_bindist_fedora))
+ bindist = step_id::bpkg_bindist_fedora;
+ else if (step_enabled (step_id::bpkg_bindist_archive))
+ bindist = step_id::bpkg_bindist_archive;
+ }
+
+ bool sys_install (bindist && !step_disabled (step_id::bbot_sys_install));
+ bool bindist_upload (bindist && step_enabled (step_id::bbot_bindist_upload));
+
+ // Unless a bpkg.bindist.* step is enabled or bpkg.install step is
+ // disabled, search for config.install.root variable. If it is present and
+ // has a non-empty value, then test the package installation and
+ // uninstall. Note that passing [null] value would be meaningless, so we
+ // don't recognize it as a special one.
+ //
+ // Note that the host package can only be installed for a self-hosted
+ // configuration, using bpkg configuration of the target type.
+ //
+ // Also note that the module package is always installed for a self-hosted
+ // configuration (and never otherwise), using config.install.root
+ // specified for ~build2 configuration.
+ //
+ // If present, indicates that the install, test installed, and uninstall
+ // operations need to be tested.
+ //
+ // Note that the main package may not support the install operation. We,
+ // however, can only detect that after the package is configured. If
+ // that's the case, we will disable the steps which may not be performed
+ // for such a package (bpkg.install, bpkg.bindist.*, etc) later, after the
+ // package is configured.
+ //
+ optional<dir_path> install_root;
+
+ // While building and running tests against the installation created
+ // either from source or from the archive distribution package we will
+ // make the bin/ subdirectory of config.install.root, if specified, the
+ // first entry in the PATH environment variable, except for build system
+ // modules which supposedly don't install any executables.
+ //
+ // Note that normally the config.install.root is expected to be prefixed
+ // with the bpkg.target.create or, as a fallback, b.create or bpkg.create
+ // step ids. However, for testing of the relocatable installations it can
+ // be desirable to extract the archive distribution package content at the
+ // bbot.sys-install.tar.extract step into a different installation
+ // directory. If that's the case, then this directory needs to also be
+ // specified as bbot.sys-install:config.install.root. If specified, this
+ // directory will be preferred as a base for forming the bin/ directory
+ // path.
+ //
+ optional<dir_path> install_bin;
+
+ auto config_install_root = [&step_args, &tgt_args]
+ (step_id s,
+ optional<step_id> f1 = nullopt,
+ optional<step_id> f2 = nullopt)
+ -> optional<dir_path>
+ {
+ size_t n (19);
+ auto space = [] (char c) {return c == ' ' || c == '\t';};
+
+ for (const char* a: reverse_iterate (step_args (tgt_args, s, f1, f2)))
+ {
+ if (strncmp (a, "config.install.root", n) == 0 &&
+ (a[n] == '=' || space (a[n])))
+ {
+ while (space (a[n])) ++n; // Skip spaces.
+ if (a[n] == '=') ++n; // Skip the equal sign.
+ while (space (a[n])) ++n; // Skip spaces.
+
+ // Note that the config.install.root variable value may potentially
+ // be quoted.
+ //
+ return dir_path (unquote (a + n));
+ }
+ }
+
+ return nullopt;
+ };
+
+ if ((target_pkg || selfhost) &&
+ !bindist &&
+ !step_disabled (step_id::bpkg_install))
+ {
+ if (!module_pkg)
+ {
+ install_root = config_install_root (step_id::bpkg_target_create,
+ step_id::b_create,
+ step_id::bpkg_create);
+
+ if (install_root)
+ install_bin = *install_root / dir_path ("bin");
+ }
+ else
+ install_root = dir_path ();
+ }
+
+ // Split external test packages into the runtime and build-time lists.
+ //
+ // Note that runtime and build-time test packages are always configured in
+ // different bpkg configurations, since they can depend on different
+ // versions of the same package.
+ //
+ small_vector<test_dependency, 1> runtime_tests;
+ small_vector<test_dependency, 1> buildtime_tests;
+
+ for (test_dependency& t: tm.tests)
+ {
+ if (t.buildtime)
+ buildtime_tests.push_back (move (t));
+ else if (target_pkg || selfhost)
+ runtime_tests.push_back (move (t));
+ }
+
+ bool has_buildtime_tests (!buildtime_tests.empty ());
+ bool has_runtime_tests (!runtime_tests.empty ());
+
+ // Abort if a target package has external build-time tests.
+ //
+ if (target_pkg && has_buildtime_tests)
+ {
+ fail_operation (
+ add_result ("configure"),
+ "build-time tests in package not marked with `requires: host`",
+ result_status::abort);
+
+ break;
+ }
+
+ // Create the required build configurations.
+ //
+ // Note that if this is a target package, then we intentionally do not
+ // create host or module configuration letting the automatic private
+ // configuration creation to take its course (since that would probably be
+ // the most typical usage scenario).
+ //
+ // Also note that we may need a separate target configuration to build the
+ // host package for installation. This is required to avoid a potential
+ // conflict between the main package and a tool it may try to run during
+ // the build. We also do the same for module packages which, while cannot
+ // have build-time dependencies, could have private code generators. This
+ // configuration needs to have the target type (so that it uses any
+ // build-time dependencies from build-host/module configurations). Note
+ // also that we currently only do this for self-hosted configuration
+ // (since we don't install otherwise, see above).
+ //
+ dir_path target_conf ("build");
+ dir_path host_conf ("build-host");
+ dir_path module_conf ("build-module");
+ dir_path install_conf ("build-install");
+
+ // Main package config.
+ //
+ const dir_path& main_pkg_conf (target_pkg ? target_conf :
+ host_pkg ? host_conf :
+ module_conf);
+
+ // Create the target configuration if this is a target package or if the
+ // host/module package has external build-time tests.
//
+ bool create_target (target_pkg || has_buildtime_tests);
- // If this is a build system module, perform a "pre-step" by building it
- // in a separate configuration reproducing the one used to build build2
- // itself. Note that the configuration and the environment options and
- // variables are not passed to commands that may affect this
- // configuration.
+ // Create the host configuration if this is a host package.
+ //
+ // Also create it for the module package with external build-time tests.
+ // The idea is to be able to test a tool which might only be tested via
+ // the module. To be precise, we need to check that the tests package has
+ // a build-time dependency (on the tool) but that's not easy to do and so
+ // we will create a host configuration if a module has any build-time
+ // tests.
+ //
+ bool create_host (host_pkg || (module_pkg && has_buildtime_tests));
+
+ // Create the module configuration if the package is a build system
+ // module.
+ //
+ // Also create it for the host package with the external build-time tests,
+ // so that a single build2 configuration is used for both target and host
+ // packages (this is important in case they happen to use the same
+ // module).
//
- bool module (pkg.compare (0, 10, "libbuild2-") == 0);
- dir_path module_dir ("build-module");
+ bool create_module (module_pkg || (host_pkg && has_buildtime_tests));
- // If this is a build system module that requires bootstrap, then its
- // importation into the dependent (test) projects cannot be configured and
- // the corresponding config.import.* variable needs to be specified on the
- // bpkg/build2 command line as a global override, whenever required.
+ // Create the configuration for installing the main package (potentially
+ // as a part of generating binary distribution package) of the host or
+ // module type, unless it's not supposed to be installed.
//
- // Note that such a module must be explicitly marked with `requires:
- // bootstrap` in its manifest. This can only be detected after the module
- // is configured and its manifest available.
+ bool create_install (!target_pkg && (install_root || bindist));
+
+ // Configuration where the package will be installed from.
//
- bool bootstrap (false);
+ dir_path effective_install_conf (
+ rwd / (create_install ? install_conf : main_pkg_conf));
- // Note that we will parse the package manifest right after the package is
- // configured.
+ // Root configuration through which we will be configuring the cluster
+ // (note: does not necessarily match the main package type).
+ //
+ // In other words, this is configuration that will be specified for
+ // bpkg-pkg-build as the current configuration (via -d). It must be the
+ // configuration that links to all the other configurations, except
+ // install.
+ //
+ // Note that the install configuration, if present, is either the
+ // cluster's "second root" (for a host package) or is an independent
+ // cluster (for a module package). In either case it needs to additionally
+ // be specified as a current configuration on the command line.
+ //
+ const dir_path& root_conf (create_target ? target_conf :
+ create_host ? host_conf :
+ module_conf);
+
+ // Note that bpkg doesn't support configuring bootstrap module
+ // dependents well, not distinguishing such modules from regular ones
+ // (see pkg_configure() for details). Thus, we need to pass the
+ // !config.import.* global override wherever required ourselves.
//
- package_manifest pm;
- path mf ("manifest");
+ // Also note that since this override is global, it may only be specified
+ // globally on the bpkg command line (as opposed to package-specific
+ // overrides).
+ //
+ optional<string> bootstrap_import;
+
+ if (bootstrap)
+ bootstrap_import = "!config.import." + pkg_var + '=' +
+ (rwd / main_pkg_conf).string ();
- if (module)
+ // Configure.
+ //
{
- // Configure.
+ operation_result* pr (&add_result ("configure"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ // If we have auxiliary environment, show it in the logs.
//
+ if (!aux_env.empty ())
{
- operation_result& r (add_result ("configure"));
- configure_result = &r;
+ for (const string& e: aux_env)
+ {
+ r.log += e;
+ r.log += '\n';
+ }
- // Noop, just for the log record.
+ // Add a trailing blank line to separate this from the rest.
//
- change_wd (trace, &r.log, rwd);
+ r.log += '\n';
+ }
- // b create(<dir>) config.config.load=~build2
- //
- // [bpkg.module.create]
- //
- // Note also that we suppress warnings about unused config.* values,
- // such CLI configuration.
- //
- // What if a module wants to use CLI? The current thinking is that we
- // will be "whitelisting" base (i.e., those that can plausibly be used
- // by multiple modules) libraries and tools for use by build system
- // modules. So if and when we whitelist CLI, we will add it here, next
- // to cc.
- //
- r.status |= run_b (
- step_id::bpkg_module_create,
+ // Noop, just for the log record.
+ //
+ change_wd (trace, &r.log, rwd);
+
+ // If we end up with multiple current configurations (root and install)
+ // then when running the bpkg-pkg-build command we need to specify the
+ // configuration for each package explicitly via --config-uuid.
+ //
+ // While it's tempting to use the --config-name option instead of
+ // --config-uuid, that wouldn't work well for multiple current
+ // configurations. For --config-name the configuration search is carried
+ // out among configurations explicitly linked to the main configuration
+ // only. That's in contrast to --config-uuid, when the whole
+ // configuration cluster is searched (see bpkg-pkg-build implementation
+ // for details).
+ //
+ // Let's not generate random UUIDs but use some predefined values which
+ // we can easily recognize in the build logs.
+ //
+ const char* target_uuid ("00000000-0000-0000-0000-000000000001");
+ const char* host_uuid ("00000000-0000-0000-0000-000000000002");
+ const char* module_uuid ("00000000-0000-0000-0000-000000000003");
+ const char* install_uuid ("00000000-0000-0000-0000-000000000004");
+
+ // Let's however distinguish the target package as a simple common case
+ // and simplify the configuration creation and packages configuration
+ // commands making them more readable in the build log. For this simple
+ // case only one configuration needs to be created explicitly and so it
+ // doesn't need the UUID. Also there is no need in any package-specific
+ // options for the bpkg-pkg-build command in this case.
+ //
+ // Create the target configuration.
+ //
+ // bpkg create <env-modules> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ if (create_target)
+ {
+ step_id b (step_id::bpkg_create); // Breakpoint.
+ step_id s (step_id::bpkg_target_create); // Step.
+ step_id f1 (step_id::b_create); // First fallback.
+ step_id f2 (step_id::bpkg_create); // Second fallback.
+
+ r.status |= run_bpkg (
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-V",
- "create(" + module_dir.representation () + ",cc)",
- "config.config.load=~build2",
- "config.config.persist+='config.*'@unused=drop");
+ "create",
+ "-d", target_conf,
+ !target_pkg ? cstrings ({"--uuid", target_uuid}) : cstrings (),
+ step_args (modules, s, f1, f2),
+ step_args (env_args, s, f1, f2),
+ step_args (tgt_args, s, f1, f2),
+ step_args (pkg_args, s, f1, f2));
if (!r.status)
break;
+ }
- change_wd (trace, &r.log, module_dir);
+ // Create the host configurations.
+ //
+ if (create_host)
+ {
+ step_id b (step_id::bpkg_create);
+
+ if (host_pkg && selfhost)
+ {
+ // Create the host configuration.
+ //
+ {
+ step_id s (step_id::bpkg_host_create);
+ step_id f1 (step_id::b_create);
+ step_id f2 (step_id::bpkg_create);
- // bpkg create --existing
+ // bpkg create --type host <env-modules> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create",
+ "-d", host_conf,
+ "--type", "host",
+ "--uuid", host_uuid,
+ step_args (modules, s, f1, f2),
+ step_args (env_args, s, f1, f2),
+ step_args (tgt_args, s, f1, f2),
+ step_args (pkg_args, s, f1, f2));
+
+ if (!r.status)
+ break;
+ }
+
+ // Create the install configuration.
+ //
+ // bpkg create <env-modules> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ if (create_install)
+ {
+ step_id s (step_id::bpkg_target_create);
+ step_id f1 (step_id::b_create);
+ step_id f2 (step_id::bpkg_create);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create",
+ "-d", install_conf,
+ "--uuid", install_uuid,
+ step_args (modules, s, f1, f2),
+ step_args (env_args, s, f1, f2),
+ step_args (tgt_args, s, f1, f2),
+ step_args (pkg_args, s, f1, f2));
+
+ if (!r.status)
+ break;
+ }
+ }
+ else
+ {
+ // b create(<dir>) config.config.load=~host
+ //
+ // Note also that we suppress warnings about unused config.* values.
+ //
+ r.status |= run_b (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create(" + host_conf.representation () + ",cc)",
+ "config.config.load=~host",
+ "config.config.persist+='config.*'@unused=drop");
+
+ if (!r.status)
+ break;
+
+ // bpkg create --existing --type host
+ //
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "create",
+ "--existing",
+ "-d", host_conf,
+ "--type", "host",
+ "--uuid", host_uuid);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ // Create the module configurations.
+ //
+ if (create_module)
+ {
+ step_id b (step_id::bpkg_create);
+
+ // Create the module configuration.
//
- r.status |= run_bpkg (
- step_id::bpkg_module_create,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "create",
- "--existing");
+ {
+ // b create(<dir>) config.config.load=~build2 [<env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>]
+ //
+ // Note also that we suppress warnings about unused config.* values.
+ //
+ // What if a module wants to use CLI? The current thinking is that
+ // we will be "whitelisting" base (i.e., those that can plausibly be
+ // used by multiple modules) libraries and tools for use by build
+ // system modules. So if and when we whitelist CLI, we will add it
+ // here, next to cc.
+ //
+ string mods;
+ cstrings eas;
+ cstrings cas;
+ cstrings pas;
- if (!r.status)
- break;
+ if (module_pkg && selfhost)
+ {
+ step_id s (step_id::bpkg_module_create);
+
+ for (const char* m: step_args (modules, s))
+ {
+ if (!mods.empty ())
+ mods += ' ';
+
+ mods += m;
+ }
+
+ eas = step_args (env_args, s);
+ cas = step_args (tgt_args, s);
+ pas = step_args (pkg_args, s);
+ }
+ else
+ mods = "cc";
+
+ r.status |= run_b (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create(" + module_conf.representation () + ',' + mods + ')',
+ "config.config.load=~build2",
+ "config.config.persist+='config.*'@unused=drop",
+ eas,
+ cas,
+ pas);
- // bpkg add <env-config-args> <config-args> <repository-url>
+ if (!r.status)
+ break;
+
+ // bpkg create --existing --type build2
+ //
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "create",
+ "--existing",
+ "-d", module_conf,
+ "--type", "build2",
+ "--uuid", module_uuid);
+
+ if (!r.status)
+ break;
+ }
+
+ // Create the install configuration.
//
- // bpkg.module.configure.add (bpkg.configure.add)
+ if (create_install && module_pkg)
+ {
+ step_id s (step_id::bpkg_module_create);
+
+ string mods;
+ for (const char* m: step_args (modules, s))
+ {
+ if (!mods.empty ())
+ mods += ' ';
+
+ mods += m;
+ }
+
+ // b create(<dir>) config.config.load=~build2 [<env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>]
+ //
+ r.status |= run_b (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create(" + install_conf.representation () + ',' + mods + ')',
+ "config.config.load=~build2",
+ "config.config.persist+='config.*'@unused=drop",
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s));
+
+ if (!r.status)
+ break;
+
+ // bpkg create --existing
+ //
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "create",
+ "--existing",
+ "-d", install_conf,
+ "--uuid", install_uuid);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ // Link the configurations.
+ //
+ // bpkg link -d <dir> <dir>
+ //
+ {
+ step_id b (step_id::bpkg_link);
+
+ if (create_target)
+ {
+ if (create_host)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", target_conf,
+ host_conf);
+
+ if (!r.status)
+ break;
+ }
+
+ if (create_module)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", target_conf,
+ module_conf);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ if (create_host)
+ {
+ if (create_module)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", host_conf,
+ module_conf);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ // Link the install configuration only for the host package. Note that
+ // the module package may not have build-time dependencies and so
+ // doesn't need configurations for them.
//
+ if (create_install && host_pkg)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", install_conf,
+ host_conf);
+
+ if (!r.status)
+ break;
+
+ if (create_module)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", install_conf,
+ module_conf);
+
+ if (!r.status)
+ break;
+ }
+ }
+ }
+
+ // Fetch repositories into the main package configuration, the target
+ // configuration for external build-time tests, if any, and the install
+ // configuration, if present.
+ //
+ // bpkg add <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <repository-url>
+ //
+ {
+ step_id b (step_id::bpkg_configure_add);
+ step_id s (step_id::bpkg_configure_add);
+
r.status |= run_bpkg (
- step_id::bpkg_module_configure_add,
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
"add",
-
- step_args (env_args,
- step_id::bpkg_module_configure_add,
- step_id::bpkg_configure_add),
-
- step_args (config_args,
- step_id::bpkg_module_configure_add,
- step_id::bpkg_configure_add),
-
+ "-d", main_pkg_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
repo);
if (!r.status)
break;
+ }
+
+ // bpkg fetch <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <trust-options>
+ //
+ {
+ step_id b (step_id::bpkg_configure_fetch);
+ step_id s (step_id::bpkg_configure_fetch);
- // bpkg fetch <env-config-args> <config-args> <trust-options>
- //
- // bpkg.module.configure.fetch (bpkg.configure.fetch)
- //
r.status |= run_bpkg (
- step_id::bpkg_module_configure_fetch,
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
"fetch",
-
- step_args (env_args,
- step_id::bpkg_module_configure_fetch,
- step_id::bpkg_configure_fetch),
-
- step_args (config_args,
- step_id::bpkg_module_configure_fetch,
- step_id::bpkg_configure_fetch),
-
+ "-d", main_pkg_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
trust_ops);
if (!r.status)
break;
+ }
- // bpkg build --configure-only <package-name>/<package-version>
- //
- // [bpkg.module.configure.build]
+ if (create_install)
+ {
+ // bpkg add <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <repository-url>
//
- r.status |= run_bpkg (
- step_id::bpkg_module_configure_build,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "build",
- "--configure-only",
- "--checkout-root", dist_root,
- "--yes",
- pkg_rev);
-
- if (!r.status)
- break;
+ {
+ step_id b (step_id::bpkg_configure_add);
+ step_id s (step_id::bpkg_configure_add);
- rm.status |= r.status;
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "add",
+ "-d", install_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ repo);
- bool dist (exists (dist_src));
- const dir_path& src_dir (dist ? dist_src : pkg_dir);
+ if (!r.status)
+ break;
+ }
- // Note that being unable to parse the package manifest is likely to
- // be an infrastructure problem, given that the package has been
- // successfully configured.
+ // bpkg fetch <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <trust-options>
//
- pm = parse_manifest<package_manifest> (src_dir / mf, "package");
+ {
+ step_id b (step_id::bpkg_configure_fetch);
+ step_id s (step_id::bpkg_configure_fetch);
- bootstrap = find_if (pm.requirements.begin (),
- pm.requirements.end (),
- [] (const requirement_alternatives& r)
- {
- return r.size () == 1 && r[0] == "bootstrap";
- }) != pm.requirements.end ();
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "fetch",
+ "-d", install_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ trust_ops);
+
+ if (!r.status)
+ break;
+ }
+ }
- if (dist)
+ if (has_buildtime_tests)
+ {
+ // bpkg add <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <repository-url>
+ //
{
- // Note that we reuse the configure operation log for the dist
- // meta-operation.
- //
- if (!redist (step_id::bpkg_module_configure_build,
- r,
- dist_root,
- pkg_dir))
+ step_id b (step_id::bpkg_configure_add);
+ step_id s (step_id::bpkg_configure_add);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "add",
+ "-d", target_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ repo);
+
+ if (!r.status)
break;
+ }
- rm.status |= r.status;
+ // bpkg fetch <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <trust-options>
+ //
+ {
+ step_id b (step_id::bpkg_configure_fetch);
+ step_id s (step_id::bpkg_configure_fetch);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "fetch",
+ "-d", target_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ trust_ops);
+
+ if (!r.status)
+ break;
}
}
- // Update.
+ // Configure all the packages using a single bpkg-pkg-build command.
+ //
+ // First, prepare the common and package arguments.
+ //
+ // If no variables are specified in the package configuration, then add
+ // the config.<pkg>.develop=false variable for the main package instead
+ // to trigger its package skeleton creation and loading. Also add this
+ // variable for the external test packages for the same purpose. This
+ // way we make sure that these packages can be used as dependencies of
+ // dependents with configuration clauses.
//
+ // Also add the dependency packages specified in the package
+ // configuration, if any, to configurations where the main package is
+ // being configured.
+ //
+ // Should we also add the dependency packages to configurations where
+ // the test packages are being configured? It feels like we shouldn't.
+ // Moreover, in the future we may decide to support specifying tests
+ // package configuration in the tests manifest value or some such. In
+ // this case a test package may have its own dependencies to be
+ // configured. What we could probably do now, is to never share a bpkg
+ // configuration between the main package and the tests packages if we
+ // configure any dependencies in it. Note that such dependencies may
+ // potentially be unsatisfactory for the test packages (unsatisfactory
+ // version, etc). This, however, seems rather far fetched so let's keep
+ // it simple for now.
+ //
+ strings common_args;
+ strings pkgs;
+
+ if (target_pkg) // The simple common case (see above)?
{
- operation_result& r (add_result ("update"));
+ // The overall command looks like this (but some parts may be omitted):
+ //
+ // bpkg build --configure-only <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <pkg-config-opts>
+ // --
+ // { <pkg-config-vars>|config.<pkg-name>.develop=false }+ <pkg>
+ // { <rtt-config-vars>|config.<runtime-test-name>.develop=false }+ <runtime-test>...
+ // { <dep-config-vars> }+ <main-dep>...
+ // <main-dep>...
+ // <glob-dep>...
+ //
+ step_id s (step_id::bpkg_target_configure_build);
+ step_id f1 (step_id::b_configure);
+ step_id f2 (step_id::bpkg_configure_build);
+
+ cstrings eas (step_args (env_args, s, f1, f2));
+ cstrings cas (step_args (tgt_args, s, f1, f2));
+ cstrings pas (step_args (pkg_args, s, f1, f2));
+
+ common_args.push_back ("--checkout-root");
+ common_args.push_back (dist_root.string ());
+
+ common_args.insert (common_args.end (), eas.begin (), eas.end ());
+ common_args.insert (common_args.end (), cas.begin (), cas.end ());
+ common_args.insert (common_args.end (), pas.begin (), pas.end ());
- // Noop, just for the log record to reduce the potential confusion for
- // the combined log reader due to the configure operation log sharing
- // (see above for details).
+ // Add the main package.
//
- change_wd (trace, &r.log, current_directory ());
+ pkgs.push_back ("{");
- // bpkg update <package-name>
+ // @@ config.<pkg>.develop=false
//
- // [bpkg.module.update]
+ // Only add the config.<pkg>.develop variable if there are no package
+ // configuration variables specified.
//
- r.status |= run_bpkg (
- step_id::bpkg_module_update,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "update",
- pkg);
+ auto i (pkg_config_pkgs.find (tm.name.string ()));
- if (!r.status)
- break;
+ if (!pkg_config_vars.empty () || i != pkg_config_pkgs.end ())
+ {
+ if (!pkg_config_vars.empty ())
+ pkgs.insert (pkgs.end (),
+ pkg_config_vars.begin (), pkg_config_vars.end ());
- rm.status |= r.status;
- }
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+ }
+#if 1
+ else
+ pkgs.push_back ("config." + pkg_var + ".develop=false");
+#endif
- // Run the package internal tests if the test operation is supported by
- // the project.
- //
- prj = prj_info (pkg_dir, true /* ext_mods */, "project");
+ pkgs.push_back ("}+");
+
+ pkgs.push_back (pkg_rev);
+
+ // Add the runtime test packages.
+ //
+ for (const auto& t: runtime_tests)
+ {
+ pkgs.push_back ("{");
+
+ // @@ config.<pkg>.develop=false
+ //
+ // Only add the config.<pkg>.develop variable if there are no
+ // package configuration variables specified.
+ //
+ auto i (pkg_config_pkgs.find (t.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+#if 1
+ else
+ pkgs.push_back ("config." + t.name.variable () + ".develop=false");
+#endif
- if (find (prj.operations.begin (), prj.operations.end (), "test") !=
- prj.operations.end ())
+ pkgs.push_back ("}+");
+
+ // Add test dependency package constraints (strip the potential
+ // reflection variable assignment; for example 'bar > 1.0.0').
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+
+ // Add the main package dependencies.
+ //
+ for (const pair<string, strings>& d: pkg_config_main_deps)
+ {
+ if (!d.second.empty ())
+ {
+ pkgs.push_back ("{");
+ pkgs.insert (pkgs.end (), d.second.begin (), d.second.end ());
+ pkgs.push_back ("}+");
+ }
+
+ pkgs.push_back (d.first);
+ }
+ }
+ else
{
- operation_result& r (add_result ("test"));
- test_result = &r;
+ // The overall command looks like this (but some parts may be omitted):
+ //
+ // bpkg build --configure-only <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <pkg-config-opts>
+ // --
+ // { <build-config> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <pkg-config-vars>|config.<pkg-name>.develop=false }+ <pkg>
+ //
+ // { <build-config> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <rtt-config-vars>|config.<runtime-test-name>.develop=false }+ <runtime-test>...
+ //
+ // { <install-config> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <pkg-config-vars> }+ <pkg>
+ //
+ // { <target-config> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <btt-config-vars>|config.<buildtime-test-name>.develop=false }+ <buildtime-test>...
+ //
+ // { <build-config> <install-config> <dep-config-vars> }+ <main-dep>...
+ // { <build-config> <install-config> }+ { <main-dep>... }
+ // <glob-dep>...
+ //
- // Use --package-cwd to help ported to build2 third-party packages a
- // bit (see bpkg-pkg-test(1) for details).
+ // Main package configuration name.
//
- // Note that internal tests that load the module itself don't make
- // much sense, thus we don't pass the config.import.* variable on
- // the command line for modules that require bootstrap.
+ const char* conf_uuid (host_pkg ? host_uuid : module_uuid);
+
+ // Add the main package args.
//
- // bpkg test <package-name>
+ // Also add the external runtime test packages here since they share
+ // the configuration directory with the main package.
//
- // [bpkg.module.test]
+ {
+ step_id s (target_pkg ? step_id::bpkg_target_configure_build :
+ host_pkg ? step_id::bpkg_host_configure_build :
+ step_id::bpkg_module_configure_build);
+
+ step_id f1 (step_id::b_configure);
+ step_id f2 (step_id::bpkg_configure_build);
+
+ cstrings eas (step_args (env_args, s, f1, f2));
+ cstrings cas (step_args (tgt_args, s, f1, f2));
+ cstrings pas (step_args (pkg_args, s, f1, f2));
+
+ // Add the main package.
+ //
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (conf_uuid);
+
+ pkgs.push_back ("--checkout-root");
+ pkgs.push_back (dist_root.string ());
+
+ pkgs.insert (pkgs.end (), eas.begin (), eas.end ());
+ pkgs.insert (pkgs.end (), cas.begin (), cas.end ());
+ pkgs.insert (pkgs.end (), pas.begin (), pas.end ());
+
+ // @@ config.<pkg>.develop=false
+ //
+ // Only add the config.<pkg>.develop variable if there are no
+ // package configuration variables specified.
+ //
+ auto i (pkg_config_pkgs.find (tm.name.string ()));
+
+ if (!pkg_config_vars.empty () || i != pkg_config_pkgs.end ())
+ {
+ if (!pkg_config_vars.empty ())
+ pkgs.insert (pkgs.end (),
+ pkg_config_vars.begin (), pkg_config_vars.end ());
+
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+ }
+#if 1
+ else
+ pkgs.push_back ("config." + pkg_var + ".develop=false");
+#endif
+
+ pkgs.push_back ("}+");
+
+ pkgs.push_back (pkg_rev);
+ }
+
+ // Add the runtime test packages.
+ //
+ for (const auto& t: runtime_tests)
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (conf_uuid);
+
+ pkgs.push_back ("--checkout-root");
+ pkgs.push_back (dist_root.string ());
+
+ pkgs.insert (pkgs.end (), eas.begin (), eas.end ());
+ pkgs.insert (pkgs.end (), cas.begin (), cas.end ());
+ pkgs.insert (pkgs.end (), pas.begin (), pas.end ());
+
+ // @@ config.<pkg>.develop=false
+ //
+ // Only add the config.<pkg>.develop variable if there are no
+ // package configuration variables specified.
+ //
+ auto i (pkg_config_pkgs.find (t.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+#if 1
+ else
+ pkgs.push_back ("config." + t.name.variable () + ".develop=false");
+#endif
+
+ pkgs.push_back ("}+");
+
+ // Strip the potential reflection variable assignment.
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+ }
+
+ // Add the main package configured in the install configuration and
+ // the external build-time test packages.
//
+ {
+ step_id s (step_id::bpkg_target_configure_build);
+ step_id f1 (step_id::b_configure);
+ step_id f2 (step_id::bpkg_configure_build);
+
+ cstrings eas (step_args (env_args, s, f1, f2));
+ cstrings cas (step_args (tgt_args, s, f1, f2));
+ cstrings pas (step_args (pkg_args, s, f1, f2));
+
+ // Add the main package.
+ //
+ if (create_install)
+ {
+ common_args.push_back ("-d");
+ common_args.push_back (install_conf.string ());
+
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (install_uuid);
+
+ // Note that we do another re-distribution (with a separate
+ // --checkout-root) in case the package is missing file that
+ // are only used during installation.
+ //
+ pkgs.push_back ("--checkout-root");
+ pkgs.push_back (dist_install_root.string ());
+
+ pkgs.insert (pkgs.end (), eas.begin (), eas.end ());
+ pkgs.insert (pkgs.end (), cas.begin (), cas.end ());
+ pkgs.insert (pkgs.end (), pas.begin (), pas.end ());
+
+ pkgs.insert (pkgs.end (),
+ pkg_config_vars.begin (), pkg_config_vars.end ());
+
+ auto i (pkg_config_pkgs.find (tm.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+
+ pkgs.push_back ("}+");
+
+ pkgs.push_back (pkg_rev);
+ }
+
+ // Add the build-time test packages.
+ //
+ // @@ config.<pkg>.develop=false
+ //
+ for (const auto& t: buildtime_tests)
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (target_uuid);
+
+ pkgs.push_back ("--checkout-root");
+ pkgs.push_back (dist_root.string ());
+
+ pkgs.insert (pkgs.end (), eas.begin (), eas.end ());
+ pkgs.insert (pkgs.end (), cas.begin (), cas.end ());
+ pkgs.insert (pkgs.end (), pas.begin (), pas.end ());
+
+ // @@ config.<pkg>.develop=false
+ //
+ // Only add the config.<pkg>.develop variable if there are no
+ // package configuration variables specified.
+ //
+ auto i (pkg_config_pkgs.find (t.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ pkgs.insert (pkgs.end (), i->second.begin (), i->second.end ());
+#if 1
+ else
+ pkgs.push_back ("config." + t.name.variable () + ".develop=false");
+#endif
+
+ pkgs.push_back ("}+");
+
+ // Strip the build-time mark and potential reflection variable
+ // assignment.
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+ }
+
+ // Add the main package dependencies to those configurations where
+ // the main package is configured.
+ //
+ {
+ // Add dependencies which have some configuration variables
+ // specified and count the number of others.
+ //
+ size_t no_vars (0);
+ for (const pair<string, strings>& d: pkg_config_main_deps)
+ {
+ if (!d.second.empty ())
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (conf_uuid);
+
+ if (create_install)
+ {
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (install_uuid);
+ }
+
+ pkgs.insert (pkgs.end (), d.second.begin (), d.second.end ());
+
+ pkgs.push_back ("}+");
+
+ pkgs.push_back (d.first);
+ }
+ else
+ ++no_vars;
+ }
+
+ // Add dependencies which have no configuration variables specified.
+ //
+ if (no_vars != 0)
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (conf_uuid);
+
+ if (create_install)
+ {
+ pkgs.push_back ("--config-uuid");
+ pkgs.push_back (install_uuid);
+ }
+
+ pkgs.push_back ("}+");
+
+ if (no_vars != 1)
+ pkgs.push_back ("{");
+
+ for (const pair<string, strings>& d: pkg_config_main_deps)
+ {
+ if (d.second.empty ())
+ pkgs.push_back (d.first);
+ }
+
+ if (no_vars != 1)
+ pkgs.push_back ("}");
+ }
+ }
+ }
+
+ // Add the global system dependencies.
+ //
+ for (const pair<string, strings>& d: pkg_config_glob_deps)
+ pkgs.push_back (d.first);
+
+ // Finally, configure all the packages.
+ //
+ {
+ step_id b (step_id::bpkg_configure_build);
+ step_id s (step_id::bpkg_global_configure_build);
+
+ optional<string> dependency_checksum;
+
+ // Only log configuration UUIDs if they are specified on the command
+ // line.
+ //
+ function<pre_run_function> log_uuids (
+ [&r, &trace,
+ target_uuid, host_uuid, module_uuid, install_uuid,
+ target_pkg] ()
+ {
+ if (!target_pkg)
+ {
+ auto log = [&r, &trace] (const char* uuid, const char* name)
+ {
+ string s (uuid);
+ s += " - ";
+ s += name;
+
+ log_comment (trace, r.log, s);
+ };
+
+ log_comment (trace, r.log, "");
+
+ log (target_uuid, "target");
+ log (host_uuid, "host");
+ log (module_uuid, "module");
+ log (install_uuid, "install");
+
+ log_comment (trace, r.log, "");
+ }
+ });
+
r.status |= run_bpkg (
- step_id::bpkg_module_test,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ b,
+ trace, r.log,
+ log_uuids,
+ dependency_checksum,
+ wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
- "test",
- "--package-cwd",
- pkg);
+ "build",
+ "--configure-only",
+ "--rebuild-checksum",
+ tm.dependency_checksum ? *tm.dependency_checksum : "",
+ "--yes",
+ "-d", root_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ common_args,
+ pkg_config_opts,
+ (has_runtime_tests || has_buildtime_tests
+ ? bootstrap_import
+ : nullopt),
+ "--",
+ pkgs);
+
+ // The dependency checksum is tricky, here are the possibilities:
+ //
+ // - absent: bpkg terminated abnormally (or was not executed due to
+ // a breakpoint) -- nothing to do here.
+ //
+ // - empty: bpkg terminated normally with error before calculating the
+ // checksum -- nothing to do here either.
+ //
+ // - one line: bpkg checksum that we want.
+ //
+ // - many lines: someone else (e.g., buildfile) printed to stdout,
+ // which we consider an error.
+ //
+ if (dependency_checksum && !dependency_checksum->empty ())
+ {
+ string& s (*dependency_checksum);
+
+ // Make sure that the output contains a single line, and bail out
+ // with the error status if that's not the case.
+ //
+ if (s.find ('\n') == s.size () - 1)
+ {
+ s.pop_back ();
+
+ // If the dependency checksum didn't change, then save it to the
+ // result manifest, clean the logs and bail out with the skip
+ // result status.
+ //
+ if (tm.dependency_checksum && *tm.dependency_checksum == s)
+ {
+ l3 ([&]{trace << "skip";});
+
+ rm.status = result_status::skip;
+ rm.dependency_checksum = move (s);
+ rm.results.clear ();
+ break;
+ }
+
+ // Save the (new) dependency checksum to the result manifest.
+ //
+ // Also note that we save the checksum if bpkg failed after the
+ // checksum was printed. As a result, we won't be rebuilding the
+ // package until the error is fixed (in a package or worker) and
+ // the checksum changes, which feels like a proper behavior.
+ //
+ rm.dependency_checksum = move (s);
+ }
+ else
+ fail_operation (r,
+ "unexpected bpkg output:\n'" + s + '\'',
+ result_status::error);
+ }
if (!r.status)
break;
+ }
- rm.status |= r.status;
+ // Redistribute the main package in both build and install
+ // configurations, if required (test packages will be handled later).
+ //
+ if (exists (dist_src))
+ {
+ change_wd (trace, &r.log, main_pkg_conf);
+
+ step_id b (step_id::bpkg_configure_build);
+
+ if (!redist (b, r, dist_root, pkg_dir))
+ break;
}
- }
- // The main phase.
- //
+ if (exists (dist_install_src))
+ {
+ change_wd (trace, &r.log, rwd / install_conf);
- // Use the global override for modules that require bootstrap.
- //
- string module_import (
- module
- ? ((bootstrap ? "!config.import." : "config.import.") +
- tm.name.variable () + "=" + (rwd / module_dir).string ())
- : "");
+ step_id b (step_id::bpkg_configure_build);
- // Configure.
+ if (!redist (b, r, dist_install_root, pkg_dir))
+ break;
+ }
+
+ rm.status |= r.status;
+ }
+
+#ifdef _WIN32
+ // Give Windows a chance to (presumably) scan any files we may have just
+ // unpacked. Failed that, if we try to overwrite any such file (e.g., a
+ // generated header) we may end up with a permission denied error. Note
+ // also that this is in addition to the 2 seconds retry we have in our
+ // fdopen() implementation, which is not always enough.
//
- dir_path build_dir ("build"); // Configuration directory name.
- dir_path pkg_config (rwd / (module ? module_dir : build_dir));
+ Sleep (5000);
+#endif
+
+ auto fail_unreached_breakpoint = [&bkp_step, &fail_operation]
+ (operation_result& r)
{
- operation_result& r (configure_result != nullptr
- ? *configure_result
- : add_result ("configure"));
+ assert (bkp_step);
- change_wd (trace, &r.log, rwd);
+ fail_operation (r,
+ "interactive build breakpoint " +
+ to_string (*bkp_step) + " cannot be reached",
+ result_status::abort);
+ };
- // bpkg create <env-modules> <env-config-args> <config-args>
- //
- // bpkg.create
+ // Note that if the bpkg.update step is disabled, we also skip all the
+ // test and install related steps.
+ //
+ if (!step_disabled (step_id::bpkg_update))
+ {
+ // Update the main package.
//
{
- // If the package is a build system module, then make sure it is
- // importable in this configuration (see above about bootstrap).
+ operation_result* pr (&add_result ("update"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ change_wd (trace, &r.log, rwd / main_pkg_conf);
+
+ // bpkg update <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
//
+ step_id b (step_id::bpkg_update);
+ step_id s (step_id::bpkg_update);
+
r.status |= run_bpkg (
- step_id::bpkg_create,
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-V",
- "create",
- "-d", build_dir.string (),
- "--wipe",
- step_args (modules, step_id::bpkg_create),
- step_args (env_args, step_id::bpkg_create),
- cargs,
- module && !bootstrap ? module_import.c_str () : nullptr);
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "update",
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ pkg);
if (!r.status)
break;
+
+ rm.status |= r.status;
}
- change_wd (trace, &r.log, build_dir);
+ b_project_info prj (
+ prj_info (pkg_dir,
+ b_info_flags::ext_mods | b_info_flags::subprojects,
+ "project"));
- // bpkg add <env-config-args> <config-args> <repository-url>
+ // If the package turned out to be non-installable, then disable all the
+ // steps which may not be performed for such a package.
//
- // bpkg.configure.add
- //
- r.status |= run_bpkg (
- step_id::bpkg_configure_add,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "add",
- step_args (env_args, step_id::bpkg_configure_add),
- step_args (config_args, step_id::bpkg_configure_add),
- repo);
-
- if (!r.status)
- break;
+ if (find (prj.operations.begin (),
+ prj.operations.end (),
+ "install") == prj.operations.end ())
+ {
+ install_root = nullopt;
+ bindist = nullopt;
+ sys_install = false;
+ bindist_upload = false;
+ }
- // bpkg fetch <env-config-args> <config-args> <trust-options>
+ // Re-distribute if comes from a version control-based repository,
+ // update, and test external test packages in the bpkg configuration in
+ // the current working directory. Optionally pass the config.import.*
+ // variable override and/or set the environment variables for the bpkg
+ // processes. Return true if all operations for all packages succeeded.
//
- // bpkg.configure.fetch
+ // Pass true as the installed argument to use the test separate installed
+ // phase step ids (bpkg.test-separate-installed.*) and the test separate
+ // phase step ids (bpkg.test-separate.*) otherwise. In both cases fall
+ // back to the main phase step ids (bpkg.*) when no environment/
+ // configuration arguments are specified for them.
//
- r.status |= run_bpkg (
- step_id::bpkg_configure_fetch,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "fetch",
- step_args (env_args, step_id::bpkg_configure_fetch),
- step_args (config_args, step_id::bpkg_configure_fetch),
- trust_ops);
+ auto test = [&trace, &wre,
+ &bkp_step, &bkp_status, &aux_env, &last_cmd,
+ &step_args, &env_args, &tgt_args, &pkg_args,
+ &bootstrap_import,
+ &redist]
+ (operation_result& r,
+ const small_vector<test_dependency, 1>& tests,
+ const dir_path& dist_root,
+ bool installed,
+ bool update_only,
+ const small_vector<string, 1>& envvars = {})
+ {
+ const optional<string>& import (!installed
+ ? bootstrap_import
+ : nullopt);
- if (!r.status)
- break;
+ for (const test_dependency& td: tests)
+ {
+ const string& pkg (td.name.string ());
+
+ // Re-distribute.
+ //
+ if (exists (dist_root))
+ {
+ // Note that re-distributing the test package is a bit tricky
+ // since we don't know its version and so cannot deduce its
+ // source directory name easily. We could potentially run the
+ // bpkg-status command after the package is configured and parse
+ // the output to obtain the version. Let's, however, keep it
+ // simple and find the source directory using the package
+ // directory name pattern.
+ //
+ try
+ {
+ dir_path pkg_dir;
+
+ // Note: doesn't follow symlinks.
+ //
+ path_search (dir_path (pkg + "-*/"),
+ [&pkg_dir] (path&& pe, const string&, bool interm)
+ {
+ if (!interm)
+ pkg_dir = path_cast<dir_path> (move (pe));
+
+ return interm;
+ },
+ dist_root,
+ path_match_flags::none);
+
+ if (!pkg_dir.empty ())
+ {
+ step_id b (
+ installed
+ ? step_id::bpkg_test_separate_installed_configure_build
+ : step_id::bpkg_configure_build);
+
+ if (!redist (b, r, dist_root, pkg_dir, import, envvars))
+ return false;
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to scan directory " << dist_root << ": " << e;
+ }
+ }
+
+ // Update.
+ //
+ // bpkg update <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
+ //
+ {
+ step_id b (installed
+ ? step_id::bpkg_test_separate_installed_update
+ : step_id::bpkg_test_separate_update);
+
+ step_id s (b);
+
+ step_id f (step_id::bpkg_update);
+
+ r.status |= run_bpkg (
+ b,
+ envvars,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "update",
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ import,
+ pkg);
+
+ if (!r.status)
+ return false;
+ }
- // bpkg build --configure-only <env-config-args> <config-args>
- // <package-name>/<package-version>
+ // Test.
+ //
+ // Note that we assume that the package supports the test operation
+ // since this is its main purpose.
+ //
+ // bpkg test <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
+ //
+ if (!update_only)
+ {
+ step_id b (installed
+ ? step_id::bpkg_test_separate_installed_test
+ : step_id::bpkg_test_separate_test);
+
+ step_id s (b);
+
+ step_id f (step_id::bpkg_test);
+
+ r.status |= run_bpkg (
+ b,
+ envvars,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "test",
+ "--package-cwd", // See above for details.
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ import,
+ pkg);
+
+ if (!r.status)
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ // Test the main package.
//
- // bpkg.configure.build
+ // Run the internal tests if the test operation is supported by the
+ // project but only for the target package or if the configuration is
+ // self-hosted.
//
- if (!module) // Note: the module is already built in the pre-step.
+ bool has_internal_tests ((target_pkg || selfhost) &&
+ find (prj.operations.begin (),
+ prj.operations.end (),
+ "test") != prj.operations.end ());
+
+ if (has_internal_tests || has_runtime_tests || has_buildtime_tests)
{
- r.status |= run_bpkg (
- step_id::bpkg_configure_build,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "build",
- "--configure-only",
- "--checkout-root", dist_root,
- "--yes",
- step_args (env_args, step_id::bpkg_configure_build),
- step_args (config_args, step_id::bpkg_configure_build),
- "--",
- pkg_rev);
+ operation_result* pr (&add_result ("test"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
- if (!r.status)
- break;
+ // Run internal tests.
+ //
+ if (has_internal_tests && !step_disabled (step_id::bpkg_test))
+ {
+ // Use --package-cwd to help ported to build2 third-party packages a
+ // bit (see bpkg-pkg-test(1) for details).
+ //
+ // Note that internal tests that load the module itself don't make
+ // much sense, thus we don't pass the config.import.* variable on
+ // the command line for modules that require bootstrap.
+ //
+ // bpkg test <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
+ //
+ step_id b (step_id::bpkg_test);
+ step_id s (step_id::bpkg_test);
- bool dist (exists (dist_src));
- const dir_path& src_dir (dist ? dist_src : pkg_dir);
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "test",
+ "--package-cwd",
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ pkg);
- pm = parse_manifest<package_manifest> (src_dir / mf, "package");
+ if (!r.status)
+ break;
+ }
+ //
+ // Fail if the breakpoint refers to the bpkg.test step but the package
+ // has no internal tests or this step is disabled.
+ //
+ else if (bkp_step && *bkp_step == step_id::bpkg_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
- if (dist)
+ // External tests.
+ //
+ // Note that if the bpkg.test-separate.update step is disabled, we
+ // also skip bpkg.test-separate.test.
+ //
+ if ((has_runtime_tests || has_buildtime_tests) &&
+ !step_disabled (step_id::bpkg_test_separate_update))
{
- if (!redist (step_id::bpkg_configure_build, r, dist_root, pkg_dir))
+ bool update_only (step_disabled (step_id::bpkg_test_separate_test));
+
+ // Fail if the breakpoint refers to the bpkg.test-separate.test step
+ // but this step is disabled.
+ //
+ if (update_only &&
+ bkp_step &&
+ *bkp_step == step_id::bpkg_test_separate_test)
+ {
+ fail_unreached_breakpoint (r);
break;
+ }
- rm.status |= r.status;
+ // External runtime tests.
+ //
+ // Note that we assume that these packages belong to the dependent
+ // package's repository or its complement repositories, recursively.
+ // Thus, we test them in the configuration used to build the
+ // dependent package.
+ //
+ if (has_runtime_tests)
+ {
+ if (!test (r,
+ runtime_tests,
+ dist_root,
+ false /* installed */,
+ update_only))
+ break;
+ }
+
+ // External build-time tests.
+ //
+ if (has_buildtime_tests)
+ {
+ change_wd (trace, &r.log, rwd / target_conf);
+
+ if (!test (r,
+ buildtime_tests,
+ dist_root,
+ false /* installed */,
+ update_only))
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to some of the bpkg.test-separate.*
+ // steps but the package either has no external tests or the
+ // bpkg.test-separate.update step is disabled.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_test_separate_update &&
+ *bkp_step <= step_id::bpkg_test_separate_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
}
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to some of the test steps but the
+ // package has no tests.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_test &&
+ *bkp_step <= step_id::bpkg_test_separate_test)
+ {
+ fail_unreached_breakpoint (add_result ("test"));
+ break;
}
- rm.status |= r.status;
- }
+ // Install from source.
+ //
+ if (install_root)
+ {
+ operation_result* pr (&add_result ("install"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
- // Update.
- //
- if (!module) // Note: the module is already built in the pre-step.
- {
- operation_result& r (add_result ("update"));
+ change_wd (trace, &r.log, effective_install_conf);
+
+ // Note that for a host or module package we don't need the target
+ // configuration anymore, if present. So let's free up the space a
+ // little bit.
+ //
+ if (!target_pkg && create_target)
+ rm_r (trace, &r.log, rwd / target_conf);
+
+ // Install.
+ //
+ {
+ // bpkg install <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
+ //
+ step_id b (step_id::bpkg_install);
+ step_id s (step_id::bpkg_install);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "install",
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ pkg);
+
+ if (!r.status)
+ break;
+ }
+
+ // Run ldconfig.
+ //
+ if (step_enabled (step_id::bbot_install_ldconfig))
+ {
+ // sudo ldconfig <env-config-args> <tgt-config-args> <pkg-config-args>
+ //
+ step_id b (step_id::bbot_install_ldconfig);
+ step_id s (step_id::bbot_install_ldconfig);
+ step_id ss (step_id::bbot_install_ldconfig);
+
+ r.status |= run_ldconfig (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss));
- // bpkg update <env-config-args> <config-args> <package-name>
+ if (!r.status)
+ break;
+ }
+ //
+ // Fail if the breakpoint refers to the bbot.install.ldconfig step but
+ // this step is disabled.
+ //
+ else if (bkp_step && *bkp_step == step_id::bbot_install_ldconfig)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ rm.status |= r.status;
+ }
//
- // bpkg.update
+ // Fail if the breakpoint refers to the bpkg.install related steps but
+ // the package is not supposed to be installed from source.
//
- r.status |= run_bpkg (
- step_id::bpkg_update,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "update",
- step_args (env_args, step_id::bpkg_update),
- step_args (config_args, step_id::bpkg_update),
- pkg);
-
- if (!r.status)
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_install &&
+ *bkp_step <= step_id::bbot_install_ldconfig)
+ {
+ fail_unreached_breakpoint (add_result ("install"));
break;
+ }
- rm.status |= r.status;
- }
+ // Generate the binary distribution package.
+ //
+ // Note that if bbot.bindist.upload step is enabled, it makes sense to
+ // only copy the generated binary distribution files to the
+ // upload/bindist/<distribution>/ directory after the binary
+ // distribution packages are testes, i.e. after the potential
+ // bbot.sys-uninstall.* steps.
+ //
+ // The following bindist_* structures contain a subset of members of the
+ // corresponding structures described in the STRUCTURED RESULT section
+ // of the bpkg-pkg-bindist(1) man page. Note: needed later for
+ // uninstall, upload.
+ //
+ struct bindist_os_release
+ {
+ string name_id;
+ optional<string> version_id;
+ };
- // Run the package internal tests if the test operation is supported by
- // the project, except for the build system module which is taken care of
- // in the pre-step.
- //
- bool internal_tests;
+ struct bindist_file
+ {
+ string type;
+ bbot::path path; // Absolute and normalized.
+ optional<string> system_name;
+ };
- if (module)
- {
- internal_tests = false;
- }
- else
- {
- prj = prj_info (pkg_dir, true /* ext_mods */, "project");
+ struct bindist_package
+ {
+ string name;
+ string version;
+ optional<string> system_version;
+ vector<bindist_file> files;
+ };
- internal_tests = find (prj.operations.begin (),
- prj.operations.end (),
- "test") != prj.operations.end ();
- }
+ struct bindist_result_type
+ {
+ string distribution;
+ string architecture;
+ bindist_os_release os_release;
+ bindist_package package;
+ vector<bindist_package> dependencies;
+ };
- // Run the package external tests, if specified. But first filter them
- // against the test-exclude task manifest values using the package names.
- //
- // Note that a proper implementation should also make sure that the
- // excluded test package version matches the version that will supposedly
- // be configured by bpkg and probably abort the build if that's not the
- // case. Such a mismatch can happen due to some valid reasons (the
- // repository was updated since the task was issued, etc) and should
- // probably be followed with automatic rebuild (the flake monitor idea).
- // Anyway, this all requires additional thinking, so let's keep it simple
- // for now.
- //
- // Filter the external test dependencies in place.
- //
- pm.tests.erase (
- remove_if (pm.tests.begin (), pm.tests.end (),
- [&tm] (const test_dependency& td)
- {
- return find_if (tm.test_exclusions.begin (),
- tm.test_exclusions.end (),
- [&td] (const package& te)
- {
- return te.name == td.name;
- }) != tm.test_exclusions.end ();
- }),
- pm.tests.end ());
-
- bool external_tests (!pm.tests.empty ());
-
- // Configure, re-distribute if comes from a version control-based
- // repository, update, and test packages in the bpkg configuration in the
- // current working directory. Optionally pass the config.import.* variable
- // override and/or set the environment variables for bpkg processes.
- // Return true if all operations for all packages succeed.
- //
- // Pass true as the installed argument to use the test separate installed
- // phase step ids (bpkg.test-separate-installed.*) and the test separate
- // phase step ids (bpkg.test-separate.*) otherwise. In both cases fall
- // back to the main phase step ids (bpkg.*) when no environment/
- // configuration arguments are specified for them.
- //
- // Pass true as the sys_dep argument to configure the dependent package as
- // a system dependency, which is normally required for testing modules and
- // installed dependents. Note that bpkg configures the dependent package
- // as a special dependency for the test package.
- //
- auto test = [&trace, &wre,
- &bkp_step, &bkp_status, &last_cmd,
- &step_args, &config_args, &env_args,
- &pm,
- &redist]
- (operation_result& r,
- const dir_path& dist_root,
- bool installed,
- bool sys_dep,
- const char* import = nullptr,
- const small_vector<string, 1>& envvars = {})
- {
- for (const test_dependency& td: pm.tests)
+ bindist_result_type bindist_result;
+
+ const dir_path& bindist_conf (
+ create_install ? install_conf : main_pkg_conf);
+
+ // Make it absolute for the sake of diagnostics.
+ //
+ path bindist_result_file (rwd / bindist_conf / "bindist-result.json");
+
+ if (bindist)
{
- const string& pkg (td.name.string ());
+ operation_result* pr (&add_result ("bindist"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ // Fail if the breakpoint refers to a bpkg.bindist.* step but this
+ // step differs from the enabled one.
+ //
+ if (bkp_step &&
+ (*bkp_step == step_id::bpkg_bindist_debian ||
+ *bkp_step == step_id::bpkg_bindist_fedora ||
+ *bkp_step == step_id::bpkg_bindist_archive) &&
+ *bkp_step != *bindist)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ change_wd (trace, &r.log, rwd);
- // Configure.
+ // Note that for a host or module package we don't need the target
+ // configuration anymore, if present. So let's free up the space a
+ // little bit.
//
- // bpkg build --configure-only <env-config-args> <config-args>
- // '<package-name>[ <version-constraint>]'
+ if (!target_pkg && create_target)
+ rm_r (trace, &r.log, rwd / target_conf);
+
+ string distribution;
+ dir_path output_root;
+
+ switch (*bindist)
+ {
+ case step_id::bpkg_bindist_debian:
+ {
+ distribution = "debian";
+ output_root = dir_path ("bindist");
+ break;
+ }
+ case step_id::bpkg_bindist_fedora:
+ {
+ distribution = "fedora";
+ break;
+ }
+ case step_id::bpkg_bindist_archive:
+ {
+ distribution = "archive";
+ output_root = dir_path ("bindist");
+ break;
+ }
+ default: assert (false);
+ }
+
+ // bpkg bindist --distribution <distribution>
+ // <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
//
- // bpkg.test-separate[-installed].configure.build (bpkg.configure.build)
+ // Note that if we are installing the result, we need to generate
+ // packages for all the dependencies unless they are included in the
+ // package (with --recursive). The way we are going to arrange for
+ // this is by specifying --recursive=separate first and letting any
+ // user --recursive option override that.
//
- step_id s (installed
- ? step_id::bpkg_test_separate_installed_configure_build
- : step_id::bpkg_test_separate_configure_build);
+ step_id b (*bindist);
+ step_id s (*bindist);
r.status |= run_bpkg (
- s,
- envvars,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ b,
+ trace, r.log, bindist_result_file, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
- "build",
- "--configure-only",
- "--checkout-root", dist_root,
- "--yes",
- step_args (env_args, s, step_id::bpkg_configure_build),
- step_args (config_args, s, step_id::bpkg_configure_build),
- import,
- "--",
- td.string (),
- sys_dep ? ("?sys:" + pm.name.string ()).c_str () : nullptr);
+ "bindist",
+ "--distribution", distribution,
+ sys_install ? cstrings ({"--recursive", "separate"}) : cstrings (),
+ "--structured-result", "json",
+ (!output_root.empty ()
+ ? cstrings ({"--output-root", output_root.string ().c_str ()})
+ : cstrings ()),
+ "-d", bindist_conf,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ pkg);
if (!r.status)
- return false;
+ break;
- // Note that re-distributing the test package is a bit tricky since we
- // don't know its version and so cannot deduce its source directory
- // name easily. We could potentially run the bpkg-status command after
- // the package is configured and parse the output to obtain the
- // version. Let's, however, keep it simple and find the source
- // directory using the package directory name pattern.
+ // Parse the structured result JSON.
//
- if (exists (dist_root))
try
{
- dir_path pkg_dir;
+ ifdstream is (bindist_result_file);
+ json::parser p (is, bindist_result_file.string ());
+
+ using event = json::event;
+
+ auto bad_json = [&p] (string d)
+ {
+ throw json::invalid_json_input (p.input_name,
+ p.line (),
+ p.column (),
+ p.position (),
+ move (d));
+ };
+
+ // Parse bindist_os_release object.
+ //
+ auto parse_os_release = [&p] ()
+ {
+ // enter: after begin_object
+ // leave: after end_object
+
+ bindist_os_release r;
+
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
+
+ if (n == "name_id")
+ {
+ r.name_id = p.next_expect_string ();
+ }
+ else if (n == "version_id")
+ {
+ r.version_id = p.next_expect_string ();
+ }
+ else
+ p.next_expect_value_skip ();
+ }
+
+ return r;
+ };
+
+ // Parse a bindist_file object.
+ //
+ auto parse_file = [&p, &bad_json] ()
+ {
+ // enter: after begin_object
+ // leave: after end_object
+
+ bindist_file r;
+
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
- path_search (dir_path (pkg + "-*/"),
- [&pkg_dir] (path&& pe, const string&, bool interm)
- {
- if (!interm)
- pkg_dir = path_cast<dir_path> (move (pe));
+ if (n == "type")
+ {
+ r.type = p.next_expect_string ();
+ }
+ else if (n == "path")
+ {
+ try
+ {
+ r.path =
+ path (p.next_expect_string ()).complete ().normalize ();
+ }
+ catch (const invalid_path& e)
+ {
+ bad_json ("invalid package file path '" + e.path + "'");
+ }
+ }
+ else if (n == "system_name")
+ {
+ r.system_name = p.next_expect_string ();
+ }
+ else
+ p.next_expect_value_skip ();
+ }
- return interm;
- },
- dist_root);
+ return r;
+ };
- if (!pkg_dir.empty () &&
- !redist (s, r, dist_root, pkg_dir, import, envvars))
- return false;
+ // Parse a bindist_package object.
+ //
+ auto parse_package = [&p, &parse_file] ()
+ {
+ // enter: after begin_object
+ // leave: after end_object
+
+ bindist_package r;
+
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
+
+ if (n == "name")
+ {
+ r.name = p.next_expect_string ();
+ }
+ else if (n == "version")
+ {
+ r.version = p.next_expect_string ();
+ }
+ else if (n == "system_version")
+ {
+ r.system_version = p.next_expect_string ();
+ }
+ else if (n == "files")
+ {
+ p.next_expect (event::begin_array);
+
+ while (p.next_expect (event::begin_object, event::end_array))
+ r.files.push_back (parse_file ());
+ }
+ else
+ p.next_expect_value_skip ();
+ }
+
+ return r;
+ };
+
+ // Parse the bindist_result.
+ //
+ // Note that if the bbot.bindist.upload step is enabled, then we
+ // require bindist_result.os_release.version_id to be present. This
+ // way the uploaded binary package can be published for a specific
+ // version of the distribution.
+ //
+ p.next_expect (event::begin_object);
+
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
+
+ if (n == "distribution")
+ {
+ bindist_result.distribution = p.next_expect_string ();
+
+ if (bindist_result.distribution != distribution)
+ bad_json ("expected distribution '" + distribution +
+ "' instead of '" + bindist_result.distribution + "'");
+ }
+ else if (n == "architecture")
+ {
+ bindist_result.architecture = p.next_expect_string ();
+ }
+ else if (n == "os_release")
+ {
+ p.next_expect (event::begin_object);
+ bindist_result.os_release = parse_os_release ();
+
+ if (!bindist_result.os_release.version_id && bindist_upload)
+ bad_json ("version_id must be present if bbot.bindist.upload "
+ "step is enabled");
+ }
+ else if (n == "package")
+ {
+ p.next_expect (event::begin_object);
+ bindist_result.package = parse_package ();
+ }
+ else if (n == "dependencies")
+ {
+ p.next_expect (event::begin_array);
+
+ while (p.next_expect (event::begin_object, event::end_array))
+ bindist_result.dependencies.push_back (parse_package ());
+ }
+ else
+ p.next_expect_value_skip ();
+ }
+ }
+ catch (const json::invalid_json_input& e)
+ {
+ fail_operation (
+ r,
+ string ("invalid bpkg-pkg-bindist result json input: ") +
+ e.what (),
+ result_status::abort,
+ e.name,
+ e.line,
+ e.column);
+
+ // Fall through.
}
- catch (const system_error& e)
+ catch (const io_error& e)
{
- fail << "unable to scan directory " << dist_root << ": " << e;
+ fail << "unable to read " << bindist_result_file << ": " << e;
}
- // Update.
+ if (!r.status)
+ break;
+
+ log_line ("generated " + distribution + " package for " + pkg + '/' +
+ ver.string () + ':',
+ r.log);
+
+ for (const bindist_file& f: bindist_result.package.files)
+ log_line (" " + f.path.string (), r.log);
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to a bpkg.bindist.* step but this step
+ // is disabled.
+ //
+ else if (bkp_step &&
+ (*bkp_step == step_id::bpkg_bindist_debian ||
+ *bkp_step == step_id::bpkg_bindist_fedora ||
+ *bkp_step == step_id::bpkg_bindist_archive))
+ {
+ fail_unreached_breakpoint (add_result ("bindist"));
+ break;
+ }
+
+ // Install from the binary distribution package generated on a
+ // bpkg.bindist.* step.
+ //
+ if (sys_install)
+ {
+ operation_result* pr (&add_result ("sys-install"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ // Fail if the breakpoint refers to the bbot.sys-install step since
+ // it has no specific command associated.
+ //
+ if (bkp_step && *bkp_step == step_id::bbot_sys_install)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ // Noop, just for the log record.
+ //
+ change_wd (trace, &r.log, rwd);
+
+ // Collect the binary package files.
//
- // bpkg update <env-config-args> <config-args> <package-name>
+ // Specifically, for now we consider files with the system name
+ // specified as package files.
//
- // bpkg.test-separate[-installed].update (bpkg.update)
+ cstrings pfs;
+
+ auto add_package_files = [&pfs] (const vector<bindist_file>& bfs)
+ {
+ for (const bindist_file& f: bfs)
+ {
+ if (f.system_name)
+ pfs.push_back (f.path.string ().c_str ());
+ }
+ };
+
+ add_package_files (bindist_result.package.files);
+
+ for (const bindist_package& d: bindist_result.dependencies)
+ add_package_files (d.files);
+
+ // Install for the `debian` distribution.
//
- s = installed
- ? step_id::bpkg_test_separate_installed_update
- : step_id::bpkg_test_separate_update;
+ if (*bindist == step_id::bpkg_bindist_debian)
+ {
+ // Update package index.
+ //
+ {
+ // sudo apt-get update <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ step_id b (step_id::bbot_sys_install_apt_get_update);
+ step_id s (step_id::bbot_sys_install_apt_get_update);
+ step_id ss (step_id::bbot_sys_install_apt_get_update);
+
+ r.status |= run_apt_get (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "update",
+ "--assume-yes",
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss));
+
+ if (!r.status)
+ break;
+ }
- r.status |= run_bpkg (
- s,
- envvars,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "update",
- step_args (env_args, s, step_id::bpkg_update),
- step_args (config_args, s, step_id::bpkg_update),
- import,
- pkg);
+ // Install.
+ //
+ {
+ // sudo apt-get install <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <distribution-package-file>...
+ //
+ // Note that apt-get install requires a directory separator for an
+ // argument to be treated as a file rather than name. The paths we
+ // pass are absolute.
+ //
+ step_id b (step_id::bbot_sys_install_apt_get_install);
+ step_id s (step_id::bbot_sys_install_apt_get_install);
+ step_id ss (step_id::bbot_sys_install_apt_get_install);
+
+ r.status |= run_apt_get (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "install",
+ "--assume-yes",
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss),
+ pfs);
+
+ if (!r.status)
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to a bbot.sys-install.apt_get.* step
+ // but the distribution is other than `debian`.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bbot_sys_install_apt_get_update &&
+ *bkp_step <= step_id::bbot_sys_install_apt_get_install)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ //
+ // Install for the `fedora` distribution.
+ //
+ else if (*bindist == step_id::bpkg_bindist_fedora)
+ {
+ // sudo dnf install <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <distribution-package-file>...
+ //
+ step_id b (step_id::bbot_sys_install_dnf_install);
+ step_id s (step_id::bbot_sys_install_dnf_install);
+ step_id ss (step_id::bbot_sys_install_dnf_install);
- if (!r.status)
- return false;
+ r.status |= run_dnf (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "install",
+ "--refresh",
+ "--assumeyes",
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss),
+ pfs);
- // Test.
+ if (!r.status)
+ break;
+ }
+ //
+ // Fail if the breakpoint refers to a bbot.sys-install.dnf.* step but
+ // the distribution is other than `fedora`.
//
- // Note that we assume that the package supports the test operation
- // since this is its main purpose.
+ else if (bkp_step && *bkp_step == step_id::bbot_sys_install_dnf_install)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ //
+ // Install for the `archive` distribution.
//
- // bpkg test <env-config-args> <config-args> <package-name>
+ // Since there is no easy way to extract from multiple archives with a
+ // single command, we run tar in a loop.
//
- // bpkg.test-separate[-installed].test (bpkg.test)
+ // Note that it is assumed that the --directory and --strip-components
+ // options are passed via <*-config-args>. The extracted executables
+ // can be arranged to be found by setting config.install.root for
+ // bpkg.target.create, etc (the same way as for installing from
+ // source).
//
- s = installed
- ? step_id::bpkg_test_separate_installed_test
- : step_id::bpkg_test_separate_test;
+ else if (*bindist == step_id::bpkg_bindist_archive)
+ {
+ // If the bbot.sys-install:config.install.root variable is
+ // specified, then make sure the directory it refers to exists by
+ // the time we run `tar -xf`, so that this command doesn't fail
+ // trying to extract into a non-existent directory. Note that we do
+ // that regardless whether the package is a build system module or
+ // not.
+ //
+ optional<dir_path> ir (
+ config_install_root (step_id::bbot_sys_install));
- r.status |= run_bpkg (
- s,
- envvars,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "test",
- "--package-cwd", // See above for details.
- step_args (env_args, s, step_id::bpkg_test),
- step_args (config_args, s, step_id::bpkg_test),
- import,
- pkg);
+ if (ir)
+ mk_p (trace, &r.log, *ir, true /* sudo */);
- if (!r.status)
- return false;
- }
+ if (!module_pkg)
+ {
+ if (!ir)
+ ir = config_install_root (step_id::bpkg_target_create,
+ step_id::b_create,
+ step_id::bpkg_create);
- return true;
- };
+ if (ir)
+ install_bin = *ir / dir_path ("bin");
+ }
- if (internal_tests || external_tests)
- {
- operation_result& r (test_result != nullptr
- ? *test_result
- : add_result ("test"));
+ for (const char* f: pfs)
+ {
+ // [sudo] tar -xf <distribution-package-file> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ step_id b (step_id::bbot_sys_install_tar_extract);
+ step_id s (step_id::bbot_sys_install_tar_extract);
+ step_id ss (step_id::bbot_sys_install_tar_extract);
+
+ r.status |= run_tar (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ true /* sudo */,
+ "-xf",
+ f,
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss));
+
+ if (!r.status)
+ break;
+ }
- // Noop, just for the log record to reduce the potential confusion for
- // the combined log reader due to updating the build system module in a
- // separate configuration (see above for details).
- //
- if (module)
- change_wd (trace, &r.log, current_directory ());
+ if (!r.status)
+ break;
- // Run internal tests.
- //
- if (internal_tests) // Note: false for modules (see above).
- {
- // bpkg test <env-config-args> <config-args> <package-name>
+ // Run ldconfig.
+ //
+ if (step_enabled (step_id::bbot_sys_install_ldconfig))
+ {
+ // sudo ldconfig <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ step_id b (step_id::bbot_sys_install_ldconfig);
+ step_id s (step_id::bbot_sys_install_ldconfig);
+ step_id ss (step_id::bbot_sys_install_ldconfig);
+
+ r.status |= run_ldconfig (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss));
+
+ if (!r.status)
+ break;
+ }
+ //
+ // Fail if the breakpoint refers to the bbot.sys-install.ldconfig
+ // step but this step is disabled.
+ //
+ else if (bkp_step && *bkp_step == step_id::bbot_sys_install_ldconfig)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ }
//
- // bpkg.test
+ // Fail if the breakpoint refers to a
+ // bbot.sys-install.{tar.extract,ldconfig} step but the distribution
+ // is other than `archive`.
//
- r.status |= run_bpkg (
- step_id::bpkg_test,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "test",
- "--package-cwd", // See above for details.
- step_args (env_args, step_id::bpkg_test),
- step_args (config_args, step_id::bpkg_test),
- pkg);
-
- if (!r.status)
+ else if (bkp_step &&
+ *bkp_step >= step_id::bbot_sys_install_tar_extract &&
+ *bkp_step <= step_id::bbot_sys_install_ldconfig)
+ {
+ fail_unreached_breakpoint (r);
break;
+ }
+ else
+ assert (false);
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to a bbot.sys-install.* step but this
+ // step is disabled.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bbot_sys_install &&
+ *bkp_step <= step_id::bbot_sys_install_ldconfig)
+ {
+ fail_unreached_breakpoint (add_result ("sys-install"));
+ break;
}
- // Run external tests.
+ // Now, if the package is installed, either from source or from the
+ // binary distribution package, the overall plan is as follows:
+ //
+ // 1. If the package has subprojects that support the test operation,
+ // then configure, build, and test them out of the source tree
+ // against the installed package using the build system directly.
//
- // Note that we assume that these packages belong to the dependent
- // package's repository or its complement repositories, recursively.
- // Thus, we test them in the configuration used to build the dependent
- // package (except for the build system module).
+ // 2. If any of the test packages are specified, then configure, build,
+ // and test them in a separate bpkg configuration(s) against the
+ // installed package.
//
- if (external_tests)
+ if (install_root || sys_install)
{
- // The test separate phase.
+ // Run the internal tests if the project contains "testable"
+ // subprojects, but not for a module.
//
- if (!test (r,
- dist_root,
- false /* installed */,
- module,
- bootstrap ? module_import.c_str () : nullptr))
- break;
+ has_internal_tests = false;
+
+ dir_paths subprj_dirs; // "Testable" package subprojects.
- // Back to the main phase.
+ // Collect the "testable" subprojects.
//
- }
+ if (!module_pkg)
+ {
+ assert (!rm.results.empty ());
- rm.status |= r.status;
- }
+ // Result of the install or sys-install operation.
+ //
+ operation_result& r (rm.results.back ());
- // Install the package, optionally test the installation and uninstall
- // afterwards.
- //
- // These operations are triggered by presence of config.install.root
- // configuration variable having a non-empty value for
- // bpkg.configure.create step.
- //
- if (install_root.empty ())
- break;
+ change_wd (trace, &r.log, effective_install_conf);
- // Now the overall plan is as follows:
- //
- // 1. Install the package.
- //
- // 2. If the package has subprojects that support the test operation, then
- // configure, build, and test them out of the source tree against the
- // installed package.
- //
- // 3. If any of the test packages are specified, then configure, build,
- // and test them in a separate bpkg configuration against the installed
- // package.
- //
- // 4. Uninstall the package.
- //
- // Install.
- //
- {
- operation_result& r (add_result ("install"));
+ for (const b_project_info::subproject& sp: prj.subprojects)
+ {
+ // Retrieve the subproject information similar to how we've done it
+ // for the package.
+ //
+ b_project_info si (prj_info (pkg_dir / sp.path,
+ b_info_flags::ext_mods,
+ "subproject"));
- change_wd (trace, &r.log, pkg_config);
+ const strings& ops (si.operations);
+ if (find (ops.begin (), ops.end (), "test") != ops.end ())
+ subprj_dirs.push_back (sp.path);
+ }
- // bpkg install <env-config-args> <config-args> <package-name>
- //
- // bpkg.install
- //
- r.status |= run_bpkg (
- step_id::bpkg_install,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "install",
- step_args (env_args, step_id::bpkg_install),
- step_args (config_args, step_id::bpkg_install),
- pkg);
+ has_internal_tests = !subprj_dirs.empty ();
+ }
- if (!r.status)
- break;
+ if (has_internal_tests || has_runtime_tests || has_buildtime_tests)
+ {
+ operation_result* pr (&add_result ("test-installed"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
- rm.status |= r.status;
- }
+ change_wd (trace, &r.log, rwd);
- // The test installed phase.
- //
+ // Make sure that the installed package executables are properly
+ // imported when configuring and running tests, unless we are testing
+ // the build system module (that supposedly doesn't install any
+ // executables).
+ //
+ small_vector<string, 1> envvars;
- // Make sure that the installed package executables are properly imported
- // when configuring and running tests, unless we are testing the build
- // system module (that supposedly doesn't install any executables).
- //
- small_vector<string, 1> envvars;
+ if (install_bin)
+ {
+ // Note that we add the $config.install.root/bin directory at the
+ // beginning of the PATH environment variable value, so the
+ // installed executables are found first.
+ //
+ const string& ib (install_bin->string ());
- dir_paths subprj_dirs; // "Testable" package subprojects.
+ log_comment (trace, r.log,
+ "add " + ib + " to PATH environment variable");
- // We expect the build system modules to not have any testable subprojects
- // but to have external tests package instead.
- //
- if (module)
- internal_tests = false;
- else
- {
- // Note that we add the $config.install.root/bin directory at the
- // beginning of the PATH environment variable value, so the installed
- // executables are found first.
- //
- string paths ("PATH=" + (install_root / "bin").string ());
+ string paths ("PATH=" + ib);
- if (optional<string> s = getenv ("PATH"))
- {
- paths += path::traits_type::path_separator;
- paths += *s;
- }
+ if (optional<string> s = getenv ("PATH"))
+ {
+ paths += path::traits_type::path_separator;
+ paths += *s;
+ }
- envvars.push_back (move (paths));
+ envvars.push_back (move (paths));
+ }
- // Collect the "testable" subprojects.
- //
- for (const b_project_info::subproject& sp: prj.subprojects)
- {
- // Retrieve the subproject information similar to how we've done it
- // for the package.
- //
- b_project_info si (prj_info (pkg_dir / sp.path,
- true /* ext_mods */,
- "subproject"));
+ // Run internal tests.
+ //
+ if (has_internal_tests)
+ {
+ // Create the configuration.
+ //
+ // b create(<dir>, <env-modules>) <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ // Amalgamation directory that will contain configuration
+ // subdirectory for package tests out of source tree build.
+ //
+ dir_path out_dir ("build-installed");
- const strings& ops (si.operations);
- if (find (ops.begin (), ops.end (), "test") != ops.end ())
- subprj_dirs.push_back (sp.path);
- }
+ {
+ step_id b (step_id::b_test_installed_create);
+ step_id s (step_id::b_test_installed_create);
+ step_id f (step_id::b_create);
- // If there are any "testable" subprojects, then configure them
- // (sequentially) and test/build in parallel afterwards.
- //
- internal_tests = !subprj_dirs.empty ();
- }
+ string mods; // build2 create meta-operation parameters.
- if (internal_tests || external_tests)
- {
- operation_result& r (add_result ("test-installed"));
+ for (const char* m: step_args (modules, s, f))
+ {
+ mods += mods.empty () ? ", " : " ";
+ mods += m;
+ }
- change_wd (trace, &r.log, rwd);
+ r.status |= run_b (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create('" + out_dir.representation () + '\'' + mods + ')',
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f));
+
+ if (!r.status)
+ break;
+ }
- // Run internal tests.
- //
- if (internal_tests)
- {
- string mods; // build2 create meta-operation parameters.
+ // Configure testable subprojects sequentially and test/build in
+ // parallel afterwards.
+ //
+ // It feels right to configure internal tests also passing the
+ // main package configuration variables, since they may need to
+ // align with the main package setup (enable some testscripts,
+ // etc).
+ //
+ strings test_specs;
+ for (const dir_path& d: subprj_dirs)
+ {
+ // b configure(<subprj-src-dir>@<subprj-out-dir>) <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <pkg-vars>
+ //
+ step_id b (step_id::b_test_installed_configure);
+ step_id s (step_id::b_test_installed_configure);
+ step_id f (step_id::b_configure);
+
+ dir_path subprj_src_dir (exists (dist_src)
+ ? dist_src / d
+ : main_pkg_conf / pkg_dir / d);
+
+ dir_path subprj_out_dir (out_dir / d);
+
+ r.status |= run_b (
+ b,
+ envvars,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "configure('" +
+ subprj_src_dir.representation () + "'@'" +
+ subprj_out_dir.representation () + "')",
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ pkg_config_vars);
+
+ if (!r.status)
+ break;
+
+ test_specs.push_back (
+ "test('" + subprj_out_dir.representation () + "')");
+ }
+
+ if (!r.status)
+ break;
+
+ // Build/test subprojects.
+ //
+ // b test(<subprj-out-dir>)... <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ if (!step_disabled (step_id::b_test_installed_test))
+ {
+ step_id b (step_id::b_test_installed_test);
+ step_id s (step_id::b_test_installed_test);
+
+ r.status |= run_b (
+ b,
+ envvars,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ test_specs,
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s));
+
+ if (!r.status)
+ break;
+ }
+ //
+ // Fail if the breakpoint refers to the b.test-installed.test step
+ // but this step is disabled.
+ //
+ else if (bkp_step && *bkp_step == step_id::b_test_installed_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to some of the b.test-installed.*
+ // steps but the package doesn't have any internal tests.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::b_test_installed_create &&
+ *bkp_step <= step_id::b_test_installed_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ // Run runtime and build-time tests.
+ //
+ // Note that we only build runtime tests for target packages and for
+ // host packages in self-hosted configurations.
+ //
+ if (has_runtime_tests || has_buildtime_tests)
+ {
+ // Create the required build configurations.
+ //
+ dir_path target_conf ("build-installed-bpkg");
+ dir_path host_conf ("build-installed-bpkg-host");
+ dir_path module_conf ("build-installed-bpkg-module");
+
+ // Create the target configuration if this is a target package
+ // having external runtime tests or a host/module package having
+ // external build-time tests.
+ //
+ bool create_target (target_pkg || has_buildtime_tests);
+
+ // Note that even if there are no runtime tests for a host/module
+ // package, we still need to create the host/build2 configuration
+ // to configure the system package in.
+ //
+ bool create_host (host_pkg || module_pkg);
+
+ bool create_module (module_pkg ||
+ (host_pkg && has_buildtime_tests));
+
+ // Note: a module package cannot have runtime tests and so the
+ // module configuration is only created to serve build-time tests.
+ // Thus, the host or target configuration is always created as
+ // well and the module configuration is never a root
+ // configuration.
+ //
+ assert (create_target || create_host);
+
+ // Root configuration through which we will be configuring the
+ // cluster.
+ //
+ const dir_path& root_conf (create_target ? target_conf : host_conf);
+
+ // Runtime tests configuration. Should only be used if there are
+ // any.
+ //
+ const dir_path& runtime_tests_conf (target_pkg
+ ? target_conf
+ : host_conf);
+
+ // Create the target configuration.
+ //
+ // bpkg create <env-modules> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ if (create_target)
+ {
+ step_id b (step_id::bpkg_test_separate_installed_create);
+
+ // Note that here and below the _for_* step ids are determined
+ // by the main package type (and, yes, that means we will use
+ // the same step ids for target and host configuration -- that,
+ // however, should be ok since host configuration will only be
+ // created in the self-hosted case).
+ //
+ step_id s (
+ target_pkg
+ ? step_id::bpkg_test_separate_installed_create_for_target
+ : host_pkg
+ ? step_id::bpkg_test_separate_installed_create_for_host
+ : step_id::bpkg_test_separate_installed_create_for_module);
+
+ // Note: no fallback for modules.
+ //
+ optional<step_id> f (!module_pkg
+ ? step_id::bpkg_test_separate_installed_create
+ : optional<step_id> ());
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create",
+ "-d", target_conf,
+ step_args (modules, s, f),
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f));
+
+ if (!r.status)
+ break;
+ }
+
+ // Create the host configuration.
+ //
+ if (create_host)
+ {
+ // bpkg create --type host <env-modules> <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ step_id b (step_id::bpkg_test_separate_installed_create);
+
+ step_id s (host_pkg
+ ? step_id::bpkg_test_separate_installed_create_for_host
+ : step_id::bpkg_test_separate_installed_create_for_module);
+
+ // Note: no fallback for modules.
+ //
+ optional<step_id> f (!module_pkg
+ ? step_id::bpkg_test_separate_installed_create
+ : optional<step_id> ());
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create",
+ "-d", host_conf,
+ "--type", "host",
+ "--name", "host",
+ step_args (modules, s, f),
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f));
+
+ if (!r.status)
+ break;
+ }
+
+ // Create the module configuration.
+ //
+ // Note that we never build any tests in it but only configure the
+ // system package. Note, however, that the host/module package
+ // build-time tests can potentially build some other modules here.
+ //
+ if (create_module)
+ {
+ // b create(<dir>) config.config.load=~build2
+ //
+ step_id b (step_id::bpkg_test_separate_installed_create);
+
+ r.status |= run_b (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-V",
+ "create(" + module_conf.representation () + ",cc)",
+ "config.config.load=~build2",
+ "config.config.persist+='config.*'@unused=drop");
+
+ if (!r.status)
+ break;
+
+ // bpkg create --existing --type build2
+ //
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "create",
+ "--existing",
+ "-d", module_conf,
+ "--type", "build2",
+ "--name", "module");
+
+ if (!r.status)
+ break;
+ }
+
+ // Link the configurations.
+ //
+ // bpkg link -d <dir> <dir>
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_link);
+
+ if (create_target)
+ {
+ if (create_host)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", target_conf,
+ host_conf);
+
+ if (!r.status)
+ break;
+ }
+
+ if (create_module)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", target_conf,
+ module_conf);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ if (create_host)
+ {
+ if (create_module)
+ {
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "link",
+ "-d", host_conf,
+ module_conf);
+
+ if (!r.status)
+ break;
+ }
+ }
+ }
+
+ // Add and fetch the repositories.
+ //
+ if (has_runtime_tests)
+ {
+ // bpkg add <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <repository-url>
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_configure_add);
+ step_id s (step_id::bpkg_test_separate_installed_configure_add);
+ step_id f (step_id::bpkg_configure_add);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "add",
+ "-d", runtime_tests_conf,
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ repo);
+
+ if (!r.status)
+ break;
+ }
+
+ // bpkg fetch <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <trust-options>
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_configure_fetch);
+ step_id s (step_id::bpkg_test_separate_installed_configure_fetch);
+ step_id f (step_id::bpkg_configure_fetch);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "fetch",
+ "-d", runtime_tests_conf,
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ trust_ops);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ if (has_buildtime_tests)
+ {
+ // bpkg add <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <repository-url>
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_configure_add);
+ step_id s (step_id::bpkg_test_separate_installed_configure_add);
+ step_id f (step_id::bpkg_configure_add);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "add",
+ "-d", target_conf,
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ repo);
+
+ if (!r.status)
+ break;
+ }
+
+ // bpkg fetch <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <trust-options>
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_configure_fetch);
+ step_id s (step_id::bpkg_test_separate_installed_configure_fetch);
+ step_id f (step_id::bpkg_configure_fetch);
+
+ r.status |= run_bpkg (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "fetch",
+ "-d", target_conf,
+ step_args (env_args, s, f),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, s, f),
+ trust_ops);
+
+ if (!r.status)
+ break;
+ }
+ }
+
+ // Configure all the packages using a single bpkg-pkg-build command.
+ //
+ // bpkg build --configure-only <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // { <config> <rtt-config-vars> }+ <runtime-test>...
+ // { <config> }+ { <runtime-test>... }
+ // { <btt-config-vars> }+ <buildtime-test>...
+ // ?sys:<pkg>
+ // <glob-dep>...
+ //
+ strings pkgs;
+
+ if (has_runtime_tests)
+ {
+ // Note that only host package runtime tests can (but not
+ // necessarily) be configured in a linked configuration and
+ // require --config-name to be specified for them.
+ //
+ assert (!module_pkg);
+
+ string conf_name (runtime_tests_conf == root_conf
+ ? ""
+ : "host");
+
+ // If there are any runtime tests with configuration variables,
+ // then add them to the command line as following:
+ //
+ // { --config-name <name> <config-var>... }+ <runtime-test>...
+ //
+ // Also count the number of runtime tests without configuration
+ // variables.
+ //
+ size_t no_vars (0);
+ for (const auto& t: runtime_tests)
+ {
+ auto i (pkg_config_pkgs.find (t.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ {
+ pkgs.push_back ("{");
+
+ if (!conf_name.empty ())
+ {
+ pkgs.push_back ("--config-name");
+ pkgs.push_back (conf_name);
+ }
+
+ pkgs.insert (pkgs.end (),
+ i->second.begin (), i->second.end ());
+
+ pkgs.push_back ("}+");
+
+ // Strip the potential reflection variable assignment.
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+ else
+ ++no_vars;
+ }
+
+ // If there are any runtime tests without configuration
+ // variables, then add them to the command line as following:
+ //
+ // { --config-name <name> }+ { <runtime-test>... }
+ //
+ if (no_vars != 0)
+ {
+ bool og (!conf_name.empty ());
+
+ if (og)
+ {
+ pkgs.push_back ("{");
+
+ pkgs.push_back ("--config-name");
+ pkgs.push_back (conf_name);
+
+ pkgs.push_back ("}+");
+ }
+
+ if (og && no_vars != 1)
+ pkgs.push_back ("{");
+
+ for (const auto& t: runtime_tests)
+ {
+ if (pkg_config_pkgs.find (t.name.string ()) ==
+ pkg_config_pkgs.end ())
+ {
+ // Strip the potential reflection variable assignment.
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+ }
+
+ if (og && no_vars != 1)
+ pkgs.push_back ("}");
+ }
+ }
+
+ if (has_buildtime_tests)
+ {
+ for (const auto& t: buildtime_tests)
+ {
+ auto i (pkg_config_pkgs.find (t.name.string ()));
+
+ if (i != pkg_config_pkgs.end ())
+ {
+ pkgs.push_back ("{");
+
+ pkgs.insert (pkgs.end (),
+ i->second.begin (), i->second.end ());
+
+ pkgs.push_back ("}+");
+ }
+
+ // Strip the build-time mark and potential reflection variable
+ // assignment.
+ //
+ pkgs.push_back (t.dependency::string ());
+ }
+ }
+
+ pkgs.push_back ("?sys:" + pkg_rev);
+
+ // Add the global system dependencies.
+ //
+ for (const pair<string, strings>& d: pkg_config_glob_deps)
+ pkgs.push_back (d.first);
+
+ // Finally, configure all the test packages.
+ //
+ {
+ step_id b (step_id::bpkg_test_separate_installed_configure_build);
+
+ step_id g (step_id::bpkg_global_configure_build); // Global.
+
+ step_id s (
+ target_pkg
+ ? step_id::bpkg_test_separate_installed_configure_build_for_target
+ : host_pkg
+ ? step_id::bpkg_test_separate_installed_configure_build_for_host
+ : step_id::bpkg_test_separate_installed_configure_build_for_module);
+
+ step_id f (step_id::bpkg_test_separate_installed_configure_build);
+
+ r.status |= run_bpkg (
+ b,
+ envvars,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "-v",
+ "build",
+ "--configure-only",
+ "--checkout-root", dist_installed_root,
+ "--yes",
+ "-d", root_conf,
+ step_args (env_args, g),
+ step_args (env_args, s, f),
+ step_args (tgt_args, g),
+ step_args (tgt_args, s, f),
+ step_args (pkg_args, g),
+ step_args (pkg_args, s, f),
+ "--",
+ pkgs);
+
+ if (!r.status)
+ break;
+ }
+
+#ifdef _WIN32
+ Sleep (5000); // See above.
+#endif
- for (const string& m: step_args (modules,
- step_id::b_test_installed_create))
+ // Note that if bpkg.test-separate-installed.update step is
+ // disabled, we also skip bpkg.test-separate-installed.test.
+ //
+ if (!step_disabled (step_id::bpkg_test_separate_installed_update))
+ {
+ bool update_only (
+ step_disabled (step_id::bpkg_test_separate_installed_test));
+
+ // Fail if the breakpoint refers to the
+ // bpkg.test-separate-installed.test step but this step is
+ // disabled.
+ //
+ if (update_only &&
+ bkp_step &&
+ *bkp_step == step_id::bpkg_test_separate_installed_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ // External runtime tests.
+ //
+ if (has_runtime_tests)
+ {
+ const dir_path& runtime_tests_conf (target_pkg
+ ? target_conf
+ : host_conf);
+
+ change_wd (trace, &r.log, runtime_tests_conf);
+
+ if (!test (r,
+ runtime_tests,
+ dist_installed_root,
+ true /* installed */,
+ update_only,
+ envvars))
+ break;
+ }
+
+ // External build-time tests.
+ //
+ if (has_buildtime_tests)
+ {
+ change_wd (trace, &r.log, rwd / target_conf);
+
+ if (!test (r,
+ buildtime_tests,
+ dist_installed_root,
+ true /* installed */,
+ update_only,
+ envvars))
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to some of the
+ // bpkg.test-separate-installed.{update,test} steps but the
+ // bpkg.test-separate-installed.update step is disabled.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_test_separate_installed_update &&
+ *bkp_step <= step_id::bpkg_test_separate_installed_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to some of the
+ // bpkg.test-separate-installed.* steps but the package has no
+ // external tests.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_test_separate_installed_create &&
+ *bkp_step <= step_id::bpkg_test_separate_installed_test)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to some of the test installed steps
+ // but the package has no tests.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::b_test_installed_create &&
+ *bkp_step <= step_id::bpkg_test_separate_installed_test)
{
- mods += mods.empty () ? ", " : " ";
- mods += m;
+ fail_unreached_breakpoint (add_result ("test-installed"));
+ break;
}
+ }
+ //
+ // Fail if the breakpoint refers to some of the test installed steps but
+ // the package is not supposed to be installed neither from source nor
+ // from the binary distribution package.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::b_test_installed_create &&
+ *bkp_step <= step_id::bpkg_test_separate_installed_test)
+ {
+ fail_unreached_breakpoint (add_result ("test-installed"));
+ break;
+ }
- // b create(<dir>, <env-modules>) <env-config-args> <config-args>
- //
- // b.test-installed.create
+ // Uninstall, if installed from the binary distribution package.
+ //
+ // Note: noop for the archive distribution.
+ //
+ if (sys_install &&
+ (*bindist == step_id::bpkg_bindist_debian ||
+ *bindist == step_id::bpkg_bindist_fedora))
+ {
+ operation_result* pr (&add_result ("sys-uninstall"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ // Noop, just for the log record.
//
- // Amalgamation directory that will contain configuration subdirectory
- // for package tests out of source tree build.
+ change_wd (trace, &r.log, rwd);
+
+ // Collect the binary package system names.
//
- dir_path out_dir ("build-installed");
+ cstrings pns;
- r.status |= run_b (
- step_id::b_test_installed_create,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-V",
- "create('" + out_dir.representation () + "'" + mods + ")",
- step_args (env_args, step_id::b_test_installed_create),
- step_args (config_args, step_id::b_test_installed_create));
+ auto add_package_names = [&pns] (const vector<bindist_file>& bfs)
+ {
+ for (const bindist_file& f: bfs)
+ {
+ if (f.system_name)
+ pns.push_back (f.system_name->c_str ());
+ }
+ };
- if (!r.status)
- break;
+ add_package_names (bindist_result.package.files);
- // Configure subprojects and create buildspecs for their testing.
+ for (const bindist_package& d: bindist_result.dependencies)
+ add_package_names (d.files);
+
+ // Uninstall for the `debian` distribution.
//
- strings test_specs;
- for (const dir_path& d: subprj_dirs)
+ if (*bindist == step_id::bpkg_bindist_debian)
{
- // b configure(<subprj-src-dir>@<subprj-out-dir>) <env-config-args>
- // <config-args>
- //
- // b.test-installed.configure
+ // sudo apt-get remove <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <distribution-package-name>...
//
- dir_path subprj_src_dir (exists (dist_src)
- ? dist_src / d
- : build_dir / pkg_dir / d);
+ step_id b (step_id::bbot_sys_uninstall_apt_get_remove);
+ step_id s (step_id::bbot_sys_uninstall_apt_get_remove);
+ step_id ss (step_id::bbot_sys_uninstall_apt_get_remove);
- dir_path subprj_out_dir (out_dir / d);
-
- r.status |= run_b (
- step_id::b_test_installed_configure,
- envvars,
+ r.status |= run_apt_get (
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "configure('" +
- subprj_src_dir.representation () + "'@'" +
- subprj_out_dir.representation () + "')",
- step_args (env_args, step_id::b_test_installed_configure),
- step_args (config_args, step_id::b_test_installed_configure));
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "remove",
+ "--assume-yes",
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss),
+ pns);
if (!r.status)
break;
-
- test_specs.push_back (
- "test('" + subprj_out_dir.representation () + "')");
}
-
- if (!r.status)
+ //
+ // Fail if the breakpoint refers to the
+ // bbot.sys-uninstall.apt-get.remove step but the distribution is
+ // other than `debian`.
+ //
+ else if (bkp_step &&
+ *bkp_step == step_id::bbot_sys_uninstall_apt_get_remove)
+ {
+ fail_unreached_breakpoint (r);
break;
+ }
+ //
+ // Uninstall for the `fedora` distribution.
+ //
+ else if (*bindist == step_id::bpkg_bindist_fedora)
+ {
+ // sudo dnf remove <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // <distribution-package-name>...
+ //
+ step_id b (step_id::bbot_sys_uninstall_dnf_remove);
+ step_id s (step_id::bbot_sys_uninstall_dnf_remove);
+ step_id ss (step_id::bbot_sys_uninstall_dnf_remove);
+
+ r.status |= run_dnf (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ "remove",
+ "--assumeyes",
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss),
+ pns);
- // Build/test subprojects.
+ if (!r.status)
+ break;
+ }
//
- // b test(<subprj-out-dir>)... <env-config-args> <config-args>
+ // Fail if the breakpoint refers to the bbot.sys-uninstall.dnf.remove
+ // step but the distribution is other than `fedora`.
//
- // b.test-installed.test
+ else if (bkp_step &&
+ *bkp_step == step_id::bbot_sys_uninstall_dnf_remove)
+ {
+ fail_unreached_breakpoint (r);
+ break;
+ }
+ else
+ assert (false);
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to a bbot.sys-uninstall.* step but
+ // this step is disabled.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bbot_sys_uninstall_apt_get_remove &&
+ *bkp_step <= step_id::bbot_sys_uninstall_dnf_remove)
+ {
+ fail_unreached_breakpoint (add_result ("sys-uninstall"));
+ break;
+ }
+
+ // Uninstall, if installed from source.
+ //
+ if (install_root)
+ {
+ operation_result* pr (&add_result ("uninstall"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ change_wd (trace, &r.log, effective_install_conf);
+
+ // bpkg uninstall <env-config-args> <tgt-config-args> <pkg-config-args>
+ // <package-name>
//
- r.status |= run_b (
- step_id::b_test_installed_test,
- envvars,
+ step_id b (step_id::bpkg_uninstall);
+ step_id s (step_id::bpkg_uninstall);
+
+ r.status |= run_bpkg (
+ b,
trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
"-v",
- test_specs,
- step_args (env_args, step_id::b_test_installed_test),
- step_args (config_args, step_id::b_test_installed_test));
+ "uninstall",
+ step_args (env_args, s),
+ step_args (tgt_args, s),
+ step_args (pkg_args, s),
+ pkg);
if (!r.status)
break;
+
+ rm.status |= r.status;
+ }
+ //
+ // Fail if the breakpoint refers to the bpkg.uninstall step but the
+ // package was not installed from source.
+ //
+ else if (bkp_step && *bkp_step == step_id::bpkg_uninstall)
+ {
+ fail_unreached_breakpoint (add_result ("uninstall"));
+ break;
}
- // Run external tests.
+ // Now, after the package is fully tested, let's prepare the build
+ // artifacts for the upload, using the upload operation log.
+ //
+
+ // Prepare the bindist artifacts.
+ //
+ // Move the binary distribution files generated for the main package and
+ // bindist-result.json to the upload/bindist/<distribution>/ directory.
+ // Also serialize the subset of the bindist result as
+ // bindist-result.manifest.
//
- if (external_tests)
+ // Fail if the breakpoint refers to the bbot.bindist.upload step since
+ // it has no specific command associated.
+ //
+ if (bkp_step && *bkp_step == step_id::bbot_bindist_upload)
+ {
+ fail_unreached_breakpoint (add_result ("upload"));
+ break;
+ }
+
+ if (bindist_upload)
{
- // Configure.
+ operation_result* pr (&add_result ("upload"));
+ operation_result& r (*pr); // @@ TMP: Apple Clang 14.0.3 ICE
+
+ change_wd (trace, &r.log, rwd);
+
+ dir_path d (upload_dir /
+ dir_path ("bindist") /
+ dir_path (bindist_result.distribution));
+
+ log_step_id (trace, r.log, step_id::bbot_bindist_upload);
+
+ mk_p (trace, &r.log, d);
+
+ // Move a file to the upload/bindist/<distribution>/ directory.
//
- // bpkg create <env-modules> <env-config-args> <config-args>
+ // On Windows copy the file instead of moving not to fail if it is
+ // being scanned by the Windows Defender or some such.
//
- // bpkg.test-installed.create (bpkg.create)
+ auto mv = [&d, &r, &rwd, &trace] (const path& p)
+ {
+#ifndef _WIN32
+ bool mv (true);
+#else
+ bool mv (false);
+#endif
+ // Use relative path, if possible, to keep the operation log tidy
+ // (won't be the case on Fedora).
+ //
+ const path& rp (p.sub (rwd) ? p.leaf (rwd) : p);
+
+ if (mv)
+ mv_into (trace, &r.log, rp, d);
+ else
+ cp_into (trace, &r.log, rp, d);
+ };
+
+ // Main package files.
//
- dir_path config_dir ("build-installed-bpkg");
+ for (const bindist_file& f: bindist_result.package.files)
+ mv (f.path);
- r.status |= run_bpkg (
- step_id::bpkg_test_installed_create,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-V",
- "create",
- "-d", config_dir.string (),
- "--wipe",
+ // Bindist result JSON.
+ //
+ mv (bindist_result_file);
- step_args (modules,
- step_id::bpkg_test_installed_create,
- step_id::bpkg_create),
+ // Bindist result manifest.
+ //
+ path mf (d / "bindist-result.manifest");
- step_args (env_args,
- step_id::bpkg_test_installed_create,
- step_id::bpkg_create),
+ try
+ {
+ ofdstream os (mf);
+ serializer s (os, mf.string ());
- step_args (config_args,
- step_id::bpkg_test_installed_create,
- step_id::bpkg_create));
+ // Serialize package manifest.
+ //
+ s.next ("", "1"); // Start of manifest.
- if (!r.status)
- break;
+ s.next ("distribution", bindist_result.distribution);
+ s.next ("architecture", bindist_result.architecture);
- change_wd (trace, &r.log, config_dir);
+ s.next ("os-release-name-id", bindist_result.os_release.name_id);
- // bpkg add <env-config-args> <config-args> <repository-url>
- //
- // bpkg.test-installed.configure.add (bpkg.configure.add)
- //
- r.status |= run_bpkg (
- step_id::bpkg_test_installed_configure_add,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "add",
+ // Should have failed earlier.
+ //
+ assert (bindist_result.os_release.version_id);
- step_args (env_args,
- step_id::bpkg_test_installed_configure_add,
- step_id::bpkg_configure_add),
+ s.next ("os-release-version-id",
+ *bindist_result.os_release.version_id);
- step_args (config_args,
- step_id::bpkg_test_installed_configure_add,
- step_id::bpkg_configure_add),
+ s.next ("package-name", bindist_result.package.name);
+ s.next ("package-version", bindist_result.package.version);
- repo);
+ if (bindist_result.package.system_version)
+ s.next ("package-system-version",
+ *bindist_result.package.system_version);
- if (!r.status)
- break;
+ s.next ("", ""); // End of manifest.
- // bpkg fetch <env-config-args> <config-args> <trust-options>
- //
- // bpkg.test-installed.configure.fetch (bpkg.configure.fetch)
- //
- r.status |= run_bpkg (
- step_id::bpkg_test_installed_configure_fetch,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "fetch",
+ // Serialize package file manifests.
+ //
+ for (const bindist_file& f: bindist_result.package.files)
+ {
+ s.next ("", "1"); // Start of manifest.
- step_args (env_args,
- step_id::bpkg_test_installed_configure_fetch,
- step_id::bpkg_configure_fetch),
+ s.next ("package-file-type", f.type);
- step_args (config_args,
- step_id::bpkg_test_installed_configure_fetch,
- step_id::bpkg_configure_fetch),
+ // Note: the simple path representation is POSIX.
+ //
+ s.next ("package-file-path", f.path.leaf ().string ());
- trust_ops);
+ if (f.system_name)
+ s.next ("package-file-system-name", *f.system_name);
- if (!r.status)
- break;
+ s.next ("", ""); // End of manifest.
+ }
+
+ s.next ("", ""); // End of stream.
+
+ os.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to '" << mf << "': " << e;
+ }
+ catch (const serialization& e)
+ {
+ fail << "unable to serialize bindist result: " << e;
+ }
+ }
+
+ // Create the archive of the build artifacts for subsequent upload, if
+ // the upload/ directory is not empty.
+ //
+ // Note that the archive will be uploaded later, right before uploading
+ // the result manifest, unless the task has been aborted by the user or
+ // the result status is error or worse.
+ //
+ if (!empty (rwd / upload_dir) && !step_disabled (step_id::bbot_upload))
+ {
+ // The upload operation log must have been added as part of the
+ // build artifacts preparation for upload.
+ //
+ operation_result& r (rm.results.back ());
- // The test separate installed phase.
+ // Fail if the breakpoint refers to the bbot.upload step since it has
+ // no specific command associated.
//
- if (!test (r,
- rwd / dir_path ("dist-installed"),
- true /* installed */,
- true /* sys_dep */,
- nullptr /* import */,
- envvars))
+ if (bkp_step && *bkp_step == step_id::bbot_upload)
+ {
+ fail_unreached_breakpoint (r);
break;
+ }
+
+ change_wd (trace, &r.log, rwd);
- // Back to the test installed phase.
+ // Archive the build artifacts.
//
- }
+ {
+ // tar -cf upload.tar <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ // upload/
+ //
+ step_id b (step_id::bbot_upload_tar_create);
+ step_id s (step_id::bbot_upload_tar_create);
+ step_id ss (step_id::bbot_upload_tar_create);
- rm.status |= r.status;
- }
+ // Make sure the archive is portable.
+ //
+ // Note that OpenBSD tar does not support --format but it appear
+ // ustar is the default (see bpkg/system-package-manager-archive.cxx
+ // for details).
+ //
+ r.status |= run_tar (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ false /* sudo */,
+#ifndef __OpenBSD__
+ "--format", "ustar",
+#endif
+ "-cf",
+ upload_archive,
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss),
+ upload_dir);
- // Back to the main phase.
- //
- // Uninstall.
- //
- {
- operation_result& r (add_result ("uninstall"));
+ if (!r.status)
+ break;
+ }
+
+ // It feels useful to also print the archive content to the log.
+ //
+ {
+ // tar -tf upload.tar <env-config-args>
+ // <tgt-config-args>
+ // <pkg-config-args>
+ //
+ step_id b (step_id::bbot_upload_tar_list);
+ step_id s (step_id::bbot_upload_tar_list);
+ step_id ss (step_id::bbot_upload_tar_list);
- change_wd (trace, &r.log, pkg_config);
+ r.status |= run_tar (
+ b,
+ trace, r.log, wre,
+ bkp_step, bkp_status, aux_env, last_cmd,
+ false /* sudo */,
+ "-tf",
+ upload_archive,
+ step_args (env_args, s, nullopt, nullopt, ss),
+ step_args (tgt_args, s, nullopt, nullopt, ss),
+ step_args (pkg_args, s, nullopt, nullopt, ss));
- // bpkg uninstall <env-config-args> <config-args> <package-name>
+ if (!r.status)
+ break;
+ }
+
+ rm.status |= r.status;
+ }
//
- // bpkg.uninstall
+ // Fail if the breakpoint refers to some of the bbot.upload.* steps but
+ // there is either nothing to upload or the bbot.upload step is
+ // disabled.
//
- r.status |= run_bpkg (
- step_id::bpkg_uninstall,
- trace, r.log, wre,
- bkp_step, bkp_status, last_cmd,
- "-v",
- "uninstall",
- step_args (env_args, step_id::bpkg_uninstall),
- step_args (config_args, step_id::bpkg_uninstall),
- pkg);
+ else if (bkp_step &&
+ *bkp_step >= step_id::bbot_upload &&
+ *bkp_step <= step_id::bbot_upload_tar_list)
+ {
+ // If the upload operation log have been added as part of the build
+ // artifacts preparation for upload, then use this log to report the
+ // error. Otherwise, add the new log for that.
+ //
+ // @@ TMP: Apple Clang 14.0.3 ICE
+ //
+ operation_result* pr (&rm.results.back ());
- if (!r.status)
- break;
+ if (pr->operation != "upload")
+ pr = &add_result ("upload");
- rm.status |= r.status;
+ fail_unreached_breakpoint (*pr);
+ break;
+ }
+ }
+ //
+ // Fail if the breakpoint refers to bpkg.update or any dependent step but
+ // the bpkg.update step is disabled.
+ //
+ else if (bkp_step &&
+ *bkp_step >= step_id::bpkg_update &&
+ *bkp_step <= step_id::bbot_upload)
+ {
+ fail_unreached_breakpoint (add_result ("update"));
+ break;
}
break;
@@ -2048,16 +5883,6 @@ build (size_t argc, const char* argv[])
rm.status |= r.status; // Merge last in case of a break.
- // Also merge statuses of the configure and test operations, which logs
- // can potentially be shared across multiple steps and which results may
- // not be the last in the list.
- //
- if (configure_result != nullptr)
- rm.status |= configure_result->status;
-
- if (test_result != nullptr)
- rm.status |= test_result->status;
-
// Unless there is an error (or worse) encountered, log the special 'end'
// step and, if this step is specified in the interactive manifest value,
// ask the user if to continue the task execution.
@@ -2067,9 +5892,11 @@ build (size_t argc, const char* argv[])
if (!error)
{
r.status |= run_cmd (step_id::end,
- trace, r.log, regexes (),
+ trace, r.log,
+ nullptr /* out_str */, path () /* out_file */,
+ regexes (),
"" /* name */,
- bkp_step, bkp_status, last_cmd,
+ bkp_step, bkp_status, aux_env, last_cmd,
process_env ());
rm.status |= r.status;
@@ -2116,11 +5943,45 @@ build (size_t argc, const char* argv[])
}
}
else
- assert (rm.status == result_status::abort);
+ assert (rm.status == result_status::abort ||
+ rm.status == result_status::skip);
if (!rwd.empty ())
+ {
change_wd (trace, nullptr /* log */, rwd);
+ // Upload the build artifacts archive, if exists.
+ //
+ bool error (!rm.status);
+ if (exists (upload_archive) && !error)
+ {
+ const string url (
+ "tftp://" + ops.tftp_host () + '/' + upload_archive.string ());
+
+ try
+ {
+ tftp_curl c (trace,
+ upload_archive,
+ nullfd,
+ curl::put,
+ url,
+ "--tftp-blksize", tftp_blksize,
+ "--max-time", tftp_put_timeout);
+
+ if (!c.wait ())
+ fail << "curl " << *c.exit;
+ }
+ catch (const process_error& e)
+ {
+ fail << "unable to execute curl: " << e;
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to upload build artifacts to " << url << ": " << e;
+ }
+ }
+ }
+
// Upload the result.
//
const string url ("tftp://" + ops.tftp_host () + "/result.manifest.lz4");
@@ -2145,6 +6006,109 @@ build (size_t argc, const char* argv[])
return 3;
}
+// Parse the task_manifest::auxiliary_environment value into the list of
+// environment variable assignments as expected by the process API. Throw
+// invalid_argument if the auxiliary environment is invalid.
+//
+// If comment is not NULL, then add blank and comment lines prefixed with this
+// string (which is normally either '#' or 'rem'). This mode is used to print
+// the environment into the build log.
+//
+static strings
+parse_auxiliary_environment (const string& s, const char* comment = nullptr)
+{
+ strings r;
+
+ // Note: parse observing blanks.
+ //
+ for (size_t b (0), e (0), m (0), n (s.size ());
+ next_word (s, n, b, e, m, '\n', '\r'), b != n; )
+ {
+ string line (s, b, e - b);
+
+ if (trim (line).empty ()) // Blank.
+ {
+ if (comment != nullptr)
+ r.push_back (comment);
+
+ continue;
+ }
+
+ if (line.front () == '#') // Comment.
+ {
+ if (comment != nullptr)
+ {
+ line.erase (0, 1);
+ line.insert (0, comment);
+ r.push_back (move (line));
+ }
+
+ continue;
+ }
+
+ size_t p (line.find ('='));
+
+ if (p == string::npos)
+ throw invalid_argument ("missing '=' in '" + line + '\'');
+
+ string name (line, 0, p);
+
+ if (trim_right (name).empty () ||
+ name.find_first_not_of (
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") != string::npos)
+ {
+ throw invalid_argument ("invalid variable name '" + name + '\'');
+ }
+
+ // Disallow certain well-known environment variables.
+ //
+ if (name == "PATH"
+#if defined(_WIN32)
+#elif defined(__APPLE__)
+ || name == "DYLD_LIBRARY_PATH"
+#else // Linux, FreeBSD, NetBSD, OpenBSD
+ || name == "LD_LIBRARY_PATH"
+#endif
+ )
+ {
+ throw invalid_argument ("disallowed variable name '" + name + '\'');
+ }
+
+ line.erase (0, p + 1); // Value.
+
+ // Note: we allow empty values.
+ //
+ if (!trim_left (line).empty ())
+ {
+ // Unquote.
+ //
+ char c (line.front ());
+ if (c == '"' || c == '\'')
+ {
+ if (line.size () == 1 || line.back () != c)
+ throw invalid_argument ("invalid quoted value '" + line + '\'');
+
+ line.pop_back ();
+ line.erase (0, 1);
+ }
+ }
+
+ // Reassemble.
+ //
+ line.insert (0, 1, '=');
+ line.insert (0, name);
+
+ r.push_back (move (line));
+ }
+
+ // Pop the final blank line comment.
+ //
+ if (comment != nullptr && r.back () == comment)
+ r.pop_back ();
+
+ return r;
+}
+
static int
startup ()
{
@@ -2154,11 +6118,13 @@ startup ()
//
// 1. Download the task manifest into the build directory (CWD).
//
- // 2. Parse it and get the target.
+ // 2. Parse it and get the target, environment name, and auxiliary
+ // environment.
//
- // 3. Find the environment setup executable for this target.
+ // 3. Find the environment setup executable for this name.
//
- // 4. Execute the environment setup executable.
+ // 4. Execute the environment setup executable for this target in the
+ // auxiliary environment.
//
// 5. If the environment setup executable fails, then upload the (failed)
// result ourselves.
@@ -2173,6 +6139,33 @@ startup ()
//
task_manifest tm;
+ auto upload_result = [&trace, &tm] (result_status rs,
+ operation_results&& ors)
+ {
+ const string url ("tftp://" + ops.tftp_host () + "/result.manifest.lz4");
+
+ // 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 () ? bpkg::package_name ("unknown") : tm.name,
+ tm.version.empty () ? bpkg::version ("0") : tm.version,
+ rs,
+ move (ors),
+ worker_checksum,
+ nullopt /* dependency_checksum */
+ };
+
+ try
+ {
+ upload_manifest (trace, url, rm, "result");
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to upload result manifest to " << url << ": " << e;
+ }
+ };
+
try
{
// Download the task.
@@ -2254,6 +6247,31 @@ startup ()
fail << "no default environment setup executable in " << env_dir;
}
+ // Auxiliary environment.
+ //
+ strings aux_env;
+ if (tm.auxiliary_environment)
+ {
+ try
+ {
+ aux_env = parse_auxiliary_environment (*tm.auxiliary_environment);
+ }
+ catch (const invalid_argument& e)
+ {
+ // Note: include (unparsed) environment into the log so that we can
+ // see what we are dealing with.
+ //
+ operation_result r {
+ "configure",
+ result_status::abort,
+ *tm.auxiliary_environment + "\n" +
+ "error: invalid auxiliary environment: " + e.what () + '\n'};
+
+ upload_result (result_status::abort, {move (r)});
+ return 1;
+ }
+ }
+
// Run it.
//
strings os;
@@ -2291,7 +6309,12 @@ startup ()
// result manifest. There is no reason to retry (most likely there is
// nobody listening on the other end anymore).
//
- switch (run_io_exit (trace, 0, 2, 2, pp, tg, argv0.effect_string (), os))
+ switch (run_io_exit (trace,
+ 0 /* stdin */, 2 /* stdout */, 2 /* stderr */,
+ process_env (pp, aux_env),
+ tg,
+ argv0.effect_string (),
+ os))
{
case 3:
case 2: return 1;
@@ -2301,27 +6324,7 @@ startup ()
}
catch (const failed&)
{
- const string url ("tftp://" + ops.tftp_host () + "/result.manifest.lz4");
-
- // 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 () ? bpkg::package_name ("unknown") : tm.name,
- tm.version.empty () ? bpkg::version ("0") : tm.version,
- result_status::abnormal,
- operation_results {}
- };
-
- try
- {
- upload_manifest (trace, url, rm, "result");
- }
- catch (const io_error& e)
- {
- fail << "unable to upload result manifest to " << url << ": " << e;
- }
-
+ upload_result (result_status::abnormal, operation_results {});
return 1;
}
}
@@ -2410,7 +6413,7 @@ try
<< "libbpkg " << LIBBPKG_VERSION_ID << endl
<< "libbutl " << LIBBUTL_VERSION_ID << endl
<< "Copyright (c) " << BBOT_COPYRIGHT << "." << endl
- << "TBC; All rights reserved" << endl;
+ << "This is free software released under the MIT license." << endl;
return 0;
}