From 08de44303033cc5ad966f4e75a7fa4a3cb06635f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 26 Aug 2019 10:02:18 +0200 Subject: Implement updating build system modules --- build/export.build | 2 +- build2/b.cxx | 4 +- libbuild2/algorithm.hxx | 2 +- libbuild2/context.cxx | 12 +++- libbuild2/context.hxx | 23 +++++- libbuild2/function.cxx | 2 +- libbuild2/function.hxx | 2 +- libbuild2/module.cxx | 176 ++++++++++++++++++++++++++++++++++++++++------ libbuild2/target-type.hxx | 3 + libbuild2/target.cxx | 15 ++++ libbuild2/target.hxx | 4 ++ 11 files changed, 211 insertions(+), 34 deletions(-) diff --git a/build/export.build b/build/export.build index 345baf0..e2f247c 100644 --- a/build/export.build +++ b/build/export.build @@ -19,7 +19,7 @@ else } d = [dir_path] $out_root/libbuild2/ - if ($import.target != lib{build2}) + if ($name($import.target) != 'build2') { # Assume one of the modules. # diff --git a/build2/b.cxx b/build2/b.cxx index 51b024e..49a4d34 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -621,9 +621,9 @@ main (int argc, char* argv[]) { ctx = nullptr; // Free first. ctx.reset (new context (sched, - cmd_vars, ops.dry_run (), - !ops.serial_stop () /* keep_going */)); + !ops.serial_stop () /* keep_going */, + cmd_vars)); }; new_context (); diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index cda464b..f105fbe 100644 --- a/libbuild2/algorithm.hxx +++ b/libbuild2/algorithm.hxx @@ -403,7 +403,7 @@ namespace build2 // Unless already known, match, and, if necessary, execute the group in // order to resolve its members list. Note that even after that the member's - // list might still not be available (e.g., if some wildcard/ fallback rule + // list might still not be available (e.g., if some wildcard/fallback rule // matched). // // If the action is for an outer operation, then it is changed to inner diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index db46319..e35f308 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -54,7 +54,11 @@ namespace build2 }; context:: - context (scheduler& s, const strings& cmd_vars, bool dr, bool kg) + context (scheduler& s, + bool dr, + bool kg, + const strings& cmd_vars, + optional> mc) : data_ (new data (*this)), sched (s), dry_run_option (dr), @@ -67,7 +71,9 @@ namespace build2 functions (data_->functions), global_scope (create_global_scope (data_->scopes)), global_target_types (data_->global_target_types), - global_override_cache (data_->global_override_cache) + global_override_cache (data_->global_override_cache), + module_context (mc ? mc->get () : nullptr), + module_context_storage (move (mc)) { tracer trace ("context"); @@ -76,7 +82,7 @@ namespace build2 scope_map& sm (data_->scopes); variable_pool& vp (data_->var_pool); - register_builtin_functions (functions); + insert_builtin_functions (functions); // Initialize the meta/operation tables. Note that the order should match // the id constants in . diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index f9432ae..338b8a1 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -101,6 +101,11 @@ namespace build2 // @@ CTX: document (backlinks, non-overlap etc). RW story. // + // A context can be preempted to execute another context (we do this, for + // example, to update build system modules). + // + // @@ Progress could clash. + // class LIBBUILD2_SYMEXPORT context { struct data; @@ -380,12 +385,26 @@ namespace build2 dir_path old_src_root; dir_path new_src_root; + // Nested context for updating build system modules. + // + // Note that such a context itself should normally have modules_context + // setup to point to itself (see import_module() for details). + // + context* module_context; + optional> module_context_storage; //@@ CTX: shared_ptr + public: + // If mod_ctx is absent, then automatic updating of build system modules + // is disabled. If it is NULL, then the context will be created lazily if + // and when necessary. Otherwise, it should be a properly setup context + // (including, normally, a self-reference in modules_context). + // explicit context (scheduler&, - const strings& cmd_vars = {}, bool dry_run = false, - bool keep_going = true); + bool keep_going = true, + const strings& cmd_vars = {}, + optional> mod_ctx = unique_ptr ()); // Set current meta-operation and operation. // diff --git a/libbuild2/function.cxx b/libbuild2/function.cxx index ebf880a..f790809 100644 --- a/libbuild2/function.cxx +++ b/libbuild2/function.cxx @@ -379,7 +379,7 @@ namespace build2 void project_name_functions (function_map&); // functions-target-triplet.cxx void - register_builtin_functions (function_map& m) + insert_builtin_functions (function_map& m) { builtin_functions (m); filesystem_functions (m); diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx index bb3fe3a..d1de4ac 100644 --- a/libbuild2/function.hxx +++ b/libbuild2/function.hxx @@ -217,7 +217,7 @@ namespace build2 }; LIBBUILD2_SYMEXPORT void - register_builtin_functions (function_map&); + insert_builtin_functions (function_map&); class LIBBUILD2_SYMEXPORT function_family { diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx index ff70de6..3a66750 100644 --- a/libbuild2/module.cxx +++ b/libbuild2/module.cxx @@ -12,9 +12,11 @@ # endif #endif -#include // import() +#include // import_*() #include +#include #include +#include #include // Core modules bundled with libbuild2. @@ -50,7 +52,7 @@ namespace build2 } static module_load_function* - import_module (scope& /*bs*/, + import_module (scope& bs, const string& mod, const location& loc, bool boot, @@ -87,38 +89,164 @@ namespace build2 fail (loc) << "unknown build system module " << mod << info << "running bootstrap build system"; #else - path lib; + context& ctx (bs.ctx); -#if 0 // See if we can import a target for this module. // - // Check if one of the bundled modules, if so, the project name is - // build2, otherwise -- libbuild2-. + path lib; + + // If this is a top-level module update, then we use the nested context. + // If, however, this is a nested module update (i.e., a module required + // while updating a module), then we reuse the same module context. // - // The target we are looking for is %lib{build2-}. + // If you are wondering why don't we always use the top-level context, the + // reason is that it might be running a different meta/operation (say, + // configure or clean); with the nested context we always know it is + // perform update. // - name tgt ( - import (bs, - name (bundled ? "build2" : "libbuild2-" + mod, - dir_path (), - "lib", - "build2-" + mod), - loc)); - - if (!tgt.qualified ()) + // And the reason for not simply creating a nested context for each nested + // module update is due to the no-overlap requirement of contexts: while + // we can naturally expect the top-level project(s) and the modules they + // require to be in separate configurations that don't shared anything, + // the same does not hold for build system modules. In fact, it would be + // natural to have a single build configuration for all of them and they + // could plausibly share some common libraries. + // + bool nested (ctx.module_context == &ctx); + + // If this is one of the bundled modules, the project name is build2, + // otherwise -- libbuild2-. + // + // The target we are looking for is %libs{build2-}. + // + // We only search in subprojects if this is a nested module update + // (remember, if it's top-level, then it must be in an isolated + // configuration). + // + pair ir ( + import_search ( + bs, + name (project_name (bundled ? "build2" : "libbuild2-" + mod), + dir_path (), + "libs", + "build2-" + mod), + loc, + nested /* subprojects */)); + + if (!ir.second.empty ()) { - // Switch the phase and update the target. This will also give us the - // shared library path. + // We found the module as a target in a project. Now we need to update + // the target (which will also give us the shared library path). + + // Create the build context if necessary. + // + if (ctx.module_context == nullptr) + { + if (!ctx.module_context_storage) + fail (loc) << "unable to update build system module " << mod << + info << "updating of build system modules is disabled"; + + assert (*ctx.module_context_storage == nullptr); + + // Since we are using the same scheduler, it makes sense to reuse + // the same mutex shards. Also disable nested module context for + // good measure. + // + ctx.module_context_storage->reset ( + new context (ctx.sched, + false, /* dry_run */ + ctx.keep_going, + {}, /* cmd_vars */ + nullopt)); /* module_context */ + + // We use the same context for building any nested modules that + // might be required while building modules. + // + ctx.module_context = ctx.module_context_storage->get (); + ctx.module_context->module_context = ctx.module_context; + + // Setup the context to perform update. In a sense we have a long- + // running perform meta-operation batch (indefinite, in fact, since we + // never call the meta-operation's *_post() callbacks) in which we + // periodically execute the update operation. + // + if (mo_perform.meta_operation_pre != nullptr) + mo_perform.meta_operation_pre ({} /* parameters */, loc); + + ctx.module_context->current_meta_operation (mo_perform); + + if (mo_perform.operation_pre != nullptr) + mo_perform.operation_pre ({} /* parameters */, update_id); + + ctx.module_context->current_operation (op_update); + } + + // "Switch" to the module context. // - // @@ TODO + context& ctx (*bs.ctx.module_context); + + // Load the imported project in the module context. // + pair lr (import_load (ctx, move (ir), loc)); + + // When happens next depends on whether this is a top-level or nested + // module update. + // + if (nested) + { + // This could be initial or exclusive load. + // + // @@ TODO + // + fail (loc) << "nested build system module updates not yet supported"; + } + else + { + const scope& rs (lr.second); + target_key tk (rs.find_target_key (lr.first, loc)); + + action_targets tgs; + action a (perform_id, update_id); + + // Note that for now we suppress progress since it would clash with + // the progress of what we are already doing (maybe in the future we + // can do save/restore but then we would need some sort of diagnostics + // that we have switched to another task). + // + mo_perform.search ({}, /* parameters */ + rs, /* root scope */ + rs, /* base scope */ + path (), /* buildfile */ + tk, + loc, + tgs); + + mo_perform.match ({}, /* parameters */ + a, + tgs, + 1, /* diag (failures only) */ + false /* progress */); + + mo_perform.execute ({}, /* parameters */ + a, + tgs, + 1, /* diag (failures only) */ + false /* progress */); + + assert (tgs.size () == 1); + const target& l (tgs[0].as_target ()); + + if (!l.is_a ("libs")) + fail (loc) << "wrong export from build system module " << mod; + + lib = l.as ().path (); + } } else -#endif { - // No luck. Form the shared library name (incorporating build system - // core version) and try using system-default search (installed, rpath, - // etc). + // No module project found. Form the shared library name (incorporating + // build system core version) and try using system-default search + // (installed, rpath, etc). // @@ This is unfortunate: it would have been nice to do something // similar to what we've done for exe{}. While libs{} is in the bin @@ -139,6 +267,8 @@ namespace build2 lib = path (pfx + mod + '-' + build_version_interface + sfx); } + // The build2__load() symbol name. + // string sym (sanitize_identifier ("build2_" + mod + "_load")); // Note that we don't unload our modules since it's not clear what would diff --git a/libbuild2/target-type.hxx b/libbuild2/target-type.hxx index 8b308c3..3325df1 100644 --- a/libbuild2/target-type.hxx +++ b/libbuild2/target-type.hxx @@ -101,6 +101,9 @@ namespace build2 } bool + is_a (const char*) const; // Defined in target.cxx + + bool is_a_base (const target_type&) const; // Defined in target.cxx }; diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index dbca6cd..22f5e66 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -4,6 +4,8 @@ #include +#include // strcmp() + #include #include #include @@ -19,6 +21,19 @@ namespace build2 // target_type // bool target_type:: + is_a (const char* n) const + { + if (strcmp (name, n) == 0) + return true; + + for (const target_type* b (base); b != nullptr; b = b->base) + if (strcmp (b->name, n) == 0) + return true; + + return false; + } + + bool target_type:: is_a_base (const target_type& tt) const { for (const target_type* b (base); b != nullptr; b = b->base) diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index b0d46e9..d543da8 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -730,6 +730,10 @@ namespace build2 const T* is_a () const {return dynamic_cast (this);} + const target* + is_a (const char* n) const { + return type ().is_a (n) ? this : nullptr;} + // Unchecked cast. // template -- cgit v1.1