From d5f769f2fc3fed6fe2a0a27b97e9b83bcc7d2960 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 21 Mar 2024 06:33:33 +0200 Subject: Handle auxiliary environment in bbot-worker --- bbot/worker/worker.cxx | 212 ++++++++++++++++++++++++++++++++++++++++++------- doc/manual.cli | 6 +- 2 files changed, 189 insertions(+), 29 deletions(-) diff --git a/bbot/worker/worker.cxx b/bbot/worker/worker.cxx index 8217c45..8388d03 100644 --- a/bbot/worker/worker.cxx +++ b/bbot/worker/worker.cxx @@ -1219,6 +1219,9 @@ 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:: @@ -2356,6 +2359,26 @@ build (size_t argc, const char* argv[]) 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 (tm.auxiliary_environment) + { + strings es (parse_auxiliary_environment (*tm.auxiliary_environment, + comment_begin)); + if (!es.empty ()) + { + for (const string& e: es) + { + r.log += e; + r.log += '\n'; + } + + // Add a trailing blank line to separate this from the rest. + // + r.log += '\n'; + } + } + // Noop, just for the log record. // change_wd (trace, &r.log, rwd); @@ -5942,6 +5965,104 @@ 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)); + } + + return r; +} + static int startup () { @@ -5951,11 +6072,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. @@ -5970,6 +6093,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. @@ -6051,6 +6201,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; @@ -6088,7 +6263,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; @@ -6098,29 +6278,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 {}, - 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; - } - + upload_result (result_status::abnormal, operation_results {}); return 1; } } diff --git a/doc/manual.cli b/doc/manual.cli index 0de6d0b..2fa3248 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -621,8 +621,10 @@ one per line, in the form: \ Whitespaces before \c{}, around \c{=}, and after \c{} as well as -blank lines and lines that start with \c{#} are ignored. The \c{} part -as a whole can be single ('\ ') or double (\"\ \") quoted. For example: +blank lines and lines that start with \c{#} are ignored. The \c{} part +must only contain capital alphabetic, numeric, and \c{_} characters. The +\c{} part as a whole can be single ('\ ') or double (\"\ \") +quoted. For example: \ DATABASE_HOST=192.168.0.1 -- cgit v1.1