From 977d07a3ae47ef204665d1eda2d642e5064724f3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 24 Jun 2019 12:01:19 +0200 Subject: Split build system into library and driver --- libbuild2/operation.cxx | 617 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 617 insertions(+) create mode 100644 libbuild2/operation.cxx (limited to 'libbuild2/operation.cxx') diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx new file mode 100644 index 0000000..9d84cc2 --- /dev/null +++ b/libbuild2/operation.cxx @@ -0,0 +1,617 @@ +// file : libbuild2/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // cout + +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + // action + // + ostream& + operator<< (ostream& os, action a) + { + uint16_t + m (a.meta_operation ()), + i (a.operation ()), + o (a.outer_operation ()); + + os << '(' << m << ','; + + if (o != 0) + os << o << '('; + + os << i; + + if (o != 0) + os << ')'; + + os << ')'; + + return os; + } + + // noop + // + const meta_operation_info mo_noop { + noop_id, + "noop", + "", // Presumably we will never need these since we are not going + "", // to do anything. + "", + "", + true, // bootstrap_outer + nullptr, // meta-operation pre + nullptr, // operation pre + &load, + nullptr, // search + nullptr, // match + nullptr, // execute + nullptr, // operation post + nullptr, // meta-operation post + nullptr // include + }; + + // perform + // + void + load (const values&, + scope& root, + const path& bf, + const dir_path& out_base, + const dir_path& src_base, + const location&) + { + // Load project's root.build. + // + load_root (root); + + // Create the base scope. Note that its existence doesn't mean it was + // already setup as a base scope; it can be the same as root. + // + auto i (scopes.rw (root).insert (out_base)); + scope& base (setup_base (i, out_base, src_base)); + + // Load the buildfile unless it is implied. + // + if (!bf.empty ()) + source_once (root, base, bf, root); + } + + void + search (const values&, + const scope&, + const scope& bs, + const path& bf, + const target_key& tk, + const location& l, + action_targets& ts) + { + tracer trace ("search"); + + phase_lock pl (run_phase::match); + + const target* t (targets.find (tk, trace)); + + // Only do the implied buildfile if we haven't loaded one. Failed that we + // may try go this route even though we've concluded the implied buildfile + // is implausible and have loaded an outer buildfile (see main() for + // details). + // + if (t == nullptr && tk.is_a () && bf.empty ()) + t = dir::search_implied (bs, tk, trace); + + if (t == nullptr) + { + diag_record dr (fail (l)); + + dr << "unknown target " << tk; + + if (!bf.empty ()) + dr << " in " << bf; + } + + ts.push_back (t); + } + + void + match (const values&, action a, action_targets& ts, uint16_t diag, bool prog) + { + tracer trace ("match"); + + { + phase_lock l (run_phase::match); + + // Setup progress reporting if requested. + // + string what; // Note: must outlive monitor_guard. + scheduler::monitor_guard mg; + + if (prog && show_progress (2 /* max_verb */)) + { + size_t incr (stderr_term ? 1 : 10); // Scale depending on output type. + + what = " targets to " + diag_do (a); + + mg = sched.monitor ( + target_count, + incr, + [incr, &what] (size_t c) -> size_t + { + diag_progress_lock pl; + diag_progress = ' '; + diag_progress += to_string (c); + diag_progress += what; + return c + incr; + }); + } + + // Start asynchronous matching of prerequisites keeping track of how + // many we have started. Wait with unlocked phase to allow phase + // switching. + // + size_t i (0), n (ts.size ()); + { + atomic_count task_count (0); + wait_guard wg (task_count, true); + + for (; i != n; ++i) + { + const target& t (ts[i].as_target ()); + l5 ([&]{trace << diag_doing (a, t);}); + + target_state s (match_async (a, t, 0, task_count, false)); + + // Bail out if the target has failed and we weren't instructed to + // keep going. + // + if (s == target_state::failed && !keep_going) + { + ++i; + break; + } + } + + wg.wait (); + } + + // Clear the progress if present. + // + if (mg) + { + diag_progress_lock pl; + diag_progress.clear (); + } + + // We are now running serially. Re-examine targets that we have matched. + // + bool fail (false); + for (size_t j (0); j != n; ++j) + { + action_target& at (ts[j]); + const target& t (at.as_target ()); + + target_state s (j < i + ? match (a, t, false) + : target_state::postponed); + switch (s) + { + case target_state::postponed: + { + // We bailed before matching it (leave state in action_target as + // unknown). + // + if (verb != 0 && diag >= 1) + info << "not " << diag_did (a, t); + + break; + } + case target_state::unknown: + case target_state::unchanged: + { + break; // Matched successfully. + } + case target_state::failed: + { + // Things didn't go well for this target. + // + if (verb != 0 && diag >= 1) + info << "failed to " << diag_do (a, t); + + at.state = s; + fail = true; + break; + } + default: + assert (false); + } + } + + if (fail) + throw failed (); + } + + // Phase restored to load. + // + assert (phase == run_phase::load); + } + + void + execute (const values&, action a, action_targets& ts, + uint16_t diag, bool prog) + { + tracer trace ("execute"); + + // Reverse the order of targets if the execution mode is 'last'. + // + if (current_mode == execution_mode::last) + reverse (ts.begin (), ts.end ()); + + // Tune the scheduler. + // + switch (current_inner_oif->concurrency) + { + case 0: sched.tune (1); break; // Run serially. + case 1: break; // Run as is. + default: assert (false); // Not yet supported. + } + + phase_lock pl (run_phase::execute); // Never switched. + + // Set the dry-run flag. + // + dry_run = dry_run_option; + + // Setup progress reporting if requested. + // + string what; // Note: must outlive monitor_guard. + scheduler::monitor_guard mg; + + if (prog && show_progress (1 /* max_verb */)) + { + size_t init (target_count.load (memory_order_relaxed)); + size_t incr (init > 100 ? init / 100 : 1); // 1%. + + if (init != incr) + { + what = "% of targets " + diag_did (a); + + mg = sched.monitor ( + target_count, + init - incr, + [init, incr, &what] (size_t c) -> size_t + { + size_t p ((init - c) * 100 / init); + size_t s (skip_count.load (memory_order_relaxed)); + + diag_progress_lock pl; + diag_progress = ' '; + diag_progress += to_string (p); + diag_progress += what; + + if (s != 0) + { + diag_progress += " ("; + diag_progress += to_string (s); + diag_progress += " skipped)"; + } + + return c - incr; + }); + } + } + + // Similar logic to execute_members(): first start asynchronous execution + // of all the top-level targets. + // + { + atomic_count task_count (0); + wait_guard wg (task_count); + + for (const action_target& at: ts) + { + const target& t (at.as_target ()); + + l5 ([&]{trace << diag_doing (a, t);}); + + target_state s (execute_async (a, t, 0, task_count, false)); + + // Bail out if the target has failed and we weren't instructed to keep + // going. + // + if (s == target_state::failed && !keep_going) + break; + } + + wg.wait (); + } + + // We are now running serially. + // + + sched.tune (0); // Restore original scheduler settings. + + // Clear the dry-run flag. + // + dry_run = false; + + // Clear the progress if present. + // + if (mg) + { + diag_progress_lock pl; + diag_progress.clear (); + } + + // Print skip count if not zero. Note that we print it regardless of the + // diag level since this is essentially a "summary" of all the commands + // that we did not (and, in fact, used to originally) print. + // + if (verb != 0) + { + if (size_t s = skip_count.load (memory_order_relaxed)) + { + text << "skipped " << diag_doing (a) << ' ' << s << " targets"; + } + } + + // Re-examine all the targets and print diagnostics. + // + bool fail (false); + for (action_target& at: ts) + { + const target& t (at.as_target ()); + + switch ((at.state = t.executed_state (a, false))) + { + case target_state::unknown: + { + // We bailed before executing it (leave state in action_target as + // unknown). + // + if (verb != 0 && diag >= 1) + info << "not " << diag_did (a, t); + + break; + } + case target_state::unchanged: + { + // Nothing had to be done. + // + if (verb != 0 && diag >= 2) + info << diag_done (a, t); + + break; + } + case target_state::changed: + { + // Something has been done. + // + break; + } + case target_state::failed: + { + // Things didn't go well for this target. + // + if (verb != 0 && diag >= 1) + info << "failed to " << diag_do (a, t); + + fail = true; + break; + } + default: + assert (false); + } + } + + if (fail) + throw failed (); + + // We should have executed every target that we matched, provided we + // haven't failed (in which case we could have bailed out early). + // + assert (target_count.load (memory_order_relaxed) == 0); + assert (dependency_count.load (memory_order_relaxed) == 0); + } + + const meta_operation_info mo_perform { + perform_id, + "perform", + "", + "", + "", + "", + true, // bootstrap_outer + nullptr, // meta-operation pre + nullptr, // operation pre + &load, + &search, + &match, + &execute, + nullptr, // operation post + nullptr, // meta-operation post + nullptr // include + }; + + // info + // + static operation_id + info_operation_pre (const values&, operation_id o) + { + if (o != default_id) + fail << "explicit operation specified for meta-operation info"; + + return o; + } + + void + info_load (const values&, + scope& rs, + const path&, + const dir_path& out_base, + const dir_path& src_base, + const location& l) + { + // For info we don't want to go any further than bootstrap so that it can + // be used in pretty much any situation (unresolved imports, etc). We do + // need to setup root as base though. + + if (rs.out_path () != out_base || rs.src_path () != src_base) + fail (l) << "meta-operation info target must be project root directory"; + + setup_base (scopes.rw (rs).insert (out_base), out_base, src_base); + } + + void + info_search (const values&, + const scope& rs, + const scope&, + const path&, + const target_key& tk, + const location& l, + action_targets& ts) + { + // Collect all the projects we need to print information about. + + // We've already verified the target is in the project root. Now verify + // it is dir{}. + // + if (!tk.type->is_a ()) + fail (l) << "meta-operation info target must be project root directory"; + + ts.push_back (&rs); + } + + static void + info_execute (const values&, action, action_targets& ts, uint16_t, bool) + { + for (size_t i (0); i != ts.size (); ++i) + { + // Separate projects with blank lines. + // + if (i != 0) + cout << endl; + + const scope& rs (*static_cast (ts[i].target)); + + // Print [meta_]operation names. Due to the way our aliasing works, we + // have to go through the [meta_]operation_table. + // + auto print_ops = [] (const auto& ov, const auto& ot) + { + // This is a sparse vector with NULL holes. id 0 is invalid while 1 is + // the noop meta-operation and the default operation; we omit printing + // both. + // + for (uint8_t id (2); id < ov.size (); ++id) + { + if (ov[id] != nullptr) + cout << ' ' << ot[id]; + } + }; + + // This could be a simple project that doesn't set project name. + // + cout + << "project: " << cast_empty (rs[var_project]) << endl + << "version: " << cast_empty (rs[var_version]) << endl + << "summary: " << cast_empty (rs[var_project_summary]) << endl + << "url: " << cast_empty (rs[var_project_url]) << endl + << "src_root: " << cast (rs[var_src_root]) << endl + << "out_root: " << cast (rs[var_out_root]) << endl + << "amalgamation: " << cast_empty (rs[var_amalgamation]) << endl + << "subprojects: " << cast_empty (rs[var_subprojects]) << endl + << "operations:"; print_ops (rs.root_extra->operations, operation_table); cout << endl + << "meta-operations:"; print_ops (rs.root_extra->meta_operations, meta_operation_table); cout << endl; + } + } + + const meta_operation_info mo_info { + info_id, + "info", + "", + "", + "", + "", + false, // bootstrap_outer + nullptr, // meta-operation pre + &info_operation_pre, + &info_load, + &info_search, + nullptr, // match + &info_execute, + nullptr, // operation post + nullptr, // meta-operation post + nullptr // include + }; + + // operations + // + const operation_info op_default { + default_id, + 0, + "", + "", + "", + "", + "", + execution_mode::first, + 1, + nullptr, + nullptr + }; + +#ifndef _MSC_VER + constexpr +#else + // VC doesn't "see" this can be const-initialized so we have to hack around + // to ensure correct initialization order. + // + #pragma warning(disable: 4073) + #pragma init_seg(lib) + const +#endif + operation_info op_update { + update_id, + 0, + "update", + "update", + "updating", + "updated", + "is up to date", + execution_mode::first, + 1, + nullptr, + nullptr + }; + + const operation_info op_clean { + clean_id, + 0, + "clean", + "clean", + "cleaning", + "cleaned", + "is clean", + execution_mode::last, + 1, + nullptr, + nullptr + }; + + // Tables. + // + string_table meta_operation_table; + string_table operation_table; +} -- cgit v1.1