From 3813b05824fa2616b8ab9c18ed158c0c02265337 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 27 Apr 2018 12:01:09 +0200 Subject: Add support for build hooks The following buildfiles are loaded (if present) at appropriate times from the out_root subdirectories of a project: build/bootstrap/pre-*.build # before loading bootstrap.build build/bootstrap/post-*.build # after loading bootstrap.build build/root/pre-*.build # before loading root.build build/root/post-*.build # after loading root.build --- build2/b.cxx | 32 +++++--- build2/config/operation.cxx | 5 +- build2/file.cxx | 178 +++++++++++++++++++++++++++++++++----------- build2/file.hxx | 34 ++++++--- build2/file.ixx | 15 ++++ build2/operation.cxx | 4 +- tests/hooks/buildfile | 5 ++ tests/hooks/testscript | 30 ++++++++ 8 files changed, 233 insertions(+), 70 deletions(-) create mode 100644 tests/hooks/buildfile create mode 100644 tests/hooks/testscript diff --git a/build2/b.cxx b/build2/b.cxx index e427237..6921a97 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -822,9 +822,9 @@ main (int argc, char* argv[]) scope& rs ( create_root (*scope::global_, out_root, src_root)->second); - bool bootstrapped (build2::bootstrapped (rs)); + bool bstrapped (bootstrapped (rs)); - if (!bootstrapped) + if (!bstrapped) { bootstrap_out (rs); @@ -860,22 +860,27 @@ main (int argc, char* argv[]) v = src_root; } - setup_root (rs); + setup_root (rs, forwarded); // Now that we have src_root, load the src_root bootstrap file, // if there is one. // - bootstrapped = bootstrap_src (rs); + bootstrap_pre (rs); + bootstrap_src (rs); + // bootstrap_post() delayed until after create_bootstrap_outer(). } - else if (src_root.empty ()) - src_root = rs.src_path (); + else + { + if (src_root.empty ()) + src_root = rs.src_path (); - // Note that we only "upgrade" the forwarded value since the same - // project root can be arrived at via multiple paths (think command - // line and import). - // - if (forwarded) - rs.assign (var_forwarded) = true; + // Note that we only "upgrade" the forwarded value since the same + // project root can be arrived at via multiple paths (think + // command line and import). + // + if (forwarded) + rs.assign (var_forwarded) = true; + } // At this stage we should have both roots and out_base figured // out. If src_base is still undetermined, calculate it. @@ -917,6 +922,9 @@ main (int argc, char* argv[]) // create_bootstrap_outer (rs); + if (!bstrapped) + bootstrap_post (rs); + // The src bootstrap should have loaded all the modules that // may add new meta/operations. So at this stage they should // all be known. We store the combined action id in uint8_t; diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 1bd5d4d..cdf2f9a 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -641,8 +641,11 @@ namespace build2 // Clean up the directories. // + // Note: try to remove the root/ hooks directory if it is empty. + // + r = rmdir (out_root / root_dir, 2) || r; r = rmdir (out_root / bootstrap_dir, 2) || r; - r = rmdir (out_root / build_dir, 2) || r; + r = rmdir (out_root / build_dir, 2) || r; switch (rmdir (out_root)) { diff --git a/build2/file.cxx b/build2/file.cxx index ae24b77..7cc6b5e 100644 --- a/build2/file.cxx +++ b/build2/file.cxx @@ -25,6 +25,7 @@ using namespace butl; namespace build2 { const dir_path build_dir ("build"); + const dir_path root_dir (dir_path (build_dir) /= "root"); const dir_path bootstrap_dir (dir_path (build_dir) /= "bootstrap"); const path root_file (build_dir / "root.build"); @@ -145,6 +146,50 @@ namespace build2 return true; } + // Source (once) pre-*.build (pre is true) or post-*.build (otherwise) hooks + // from the specified subdirectory (build/bootstrap/ or build/root/) of + // out_root/. + // + void + source_hooks (scope& root, const dir_path& sd, bool pre) + { + dir_path d (root.out_path () / sd); + + if (!exists (d)) + return; + + // While we could have used the wildcard pattern matching functionality, + // our needs are pretty basic and performance is quite important, so let's + // handle this ourselves. + // + for (const dir_entry& de: dir_iterator (d)) + { + // If this is a link, then type() will try to stat() it. And if the link + // is dangling or points to something inaccessible, it will fail. So + // let's first check that the name matches and only then check the type. + // + const path& n (de.path ()); + + if (n.string ().compare (0, pre ? 4 : 5, pre ? "pre-" : "post-") != 0 || + n.extension () != "build") + continue; + + path f (d / n); + + try + { + if (de.type () != entry_type::regular) + continue; + } + catch (const system_error& e) + { + fail << "unable to read buildfile " << f << ": " << e; + } + + source_once (root, root, f); + } + } + scope_map::iterator create_root (scope& l, const dir_path& out_root, const dir_path& src_root) { @@ -215,7 +260,7 @@ namespace build2 } void - setup_root (scope& s) + setup_root (scope& s, bool forwarded) { // The caller must have made sure src_root is set on this scope. // @@ -227,6 +272,8 @@ namespace build2 s.src_path_ = &d; else assert (s.src_path_ == &d); + + s.assign (var_forwarded) = forwarded; } scope& @@ -298,7 +345,7 @@ namespace build2 // Switch to the new root scope. // if (rs != &root) - load_root_pre (*rs); // Load new root(s) recursively. + load_root (*rs); // Load new root(s) recursively. // Now we can figure out src_base and finish setting the scope. // @@ -346,7 +393,8 @@ namespace build2 //@@ TODO: if bootstrap files can source other bootstrap files // (the way to express dependecies), then we need a way to // prevent multiple sourcing. We handle it here but we still - // need something like source_once (once [scope] source). + // need something like source_once (once [scope] source) in + // buildfiles. // source_once (root, root, bf); } @@ -760,10 +808,10 @@ namespace build2 } // Return true if the inner/outer project (identified by out/src_root) of - // the 'origin' project (identified by root) should be forwarded. + // the 'origin' project (identified by orig) should be forwarded. // static inline bool - forwarded (const scope& root, + forwarded (const scope& orig, const dir_path& out_root, const dir_path& src_root) { @@ -776,7 +824,7 @@ namespace build2 // 3. Inner/outer out-root.build exists in src_root and refers out_root. // return (out_root != src_root && - cast_false (root.vars[var_forwarded]) && + cast_false (orig.vars[var_forwarded]) && bootstrap_fwd (src_root) == out_root); } @@ -804,7 +852,9 @@ namespace build2 // scope& rs (create_root (root, out_root, dir_path ())->second); - if (!bootstrapped (rs)) + bool bstrapped (bootstrapped (rs)); + + if (!bstrapped) { bootstrap_out (rs); // #3 happens here (or it can be #1). @@ -822,15 +872,22 @@ namespace build2 } } - setup_root (rs); + setup_root (rs, forwarded (root, out_root, v.as ())); + bootstrap_pre (rs); bootstrap_src (rs); + // bootstrap_post() delayed until after create_bootstrap_outer(). + } + else + { + if (forwarded (root, rs.out_path (), rs.src_path ())) + rs.assign (var_forwarded) = true; // Only upgrade (see main()). } - - if (forwarded (root, rs.out_path (), rs.src_path ())) - rs.assign (var_forwarded) = true; create_bootstrap_outer (rs); + if (!bstrapped) + bootstrap_post (rs); + // Check if we are strongly amalgamated by this outer root scope. // if (root.src_path ().sub (rs.src_path ())) @@ -864,8 +921,15 @@ namespace build2 ? out_root : (root.src_path () / p.second); - setup_root (rs); + setup_root (rs, forwarded (root, out_root, v.as ())); + bootstrap_pre (rs); bootstrap_src (rs); + bootstrap_post (rs); + } + else + { + if (forwarded (root, rs.out_path (), rs.src_path ())) + rs.assign (var_forwarded) = true; // Only upgrade (see main()). } // Check if we strongly amalgamated this inner root scope. @@ -873,9 +937,6 @@ namespace build2 if (rs.src_path ().sub (root.src_path ())) rs.strong_ = root.strong_scope (); // Itself or some outer scope. - if (forwarded (root, rs.out_path (), rs.src_path ())) - rs.assign (var_forwarded) = true; - // See if there are more inner roots. // return create_bootstrap_inner (rs, out_base); @@ -886,14 +947,22 @@ namespace build2 } void - load_root_pre (scope& root) + load_root (scope& root) { - tracer trace ("root_pre"); + tracer trace ("load_root"); + + // As an optimization, check if we have already loaded root.build. If + // that's the case, then we have already been called for this project. + // + path bf (root.src_path () / root_file); + + if (root.buildfiles.find (bf) != root.buildfiles.end ()) + return; // First load outer roots, if any. // if (scope* rs = root.parent_scope ()->root_scope ()) - load_root_pre (*rs); + load_root (*rs); // Finish off loading bootstrapped modules. // @@ -913,12 +982,17 @@ namespace build2 load_module (root, root, p.first, s.loc); } - // Load root.build. + // Load hooks and root.build. // - path bf (root.src_path () / root_file); - - if (exists (bf)) - source_once (root, root, bf); + // We can load the pre hooks before finishing off loading the bootstrapped + // modules (which, in case of config would load config.build) or after and + // one can come up with a plausible use-case for either approach. Note, + // however, that one can probably achieve adequate pre-modules behavior + // with a post-bootstrap hook. + // + source_hooks (root, root_dir, true /* pre */); + if (exists (bf)) source_once (root, root, bf); + source_hooks (root, root_dir, false /* pre */); } scope& @@ -936,16 +1010,20 @@ namespace build2 if (!bootstrapped (rs)) { bootstrap_out (rs); - setup_root (rs); + setup_root (rs, forwarded); + bootstrap_pre (rs); bootstrap_src (rs); + bootstrap_post (rs); + } + else + { + if (forwarded) + rs.assign (var_forwarded) = true; // Only upgrade (see main()). } - - if (forwarded) - rs.assign (var_forwarded) = true; if (load) { - load_root_pre (rs); + load_root (rs); setup_base (i, out_root, src_root); // Setup as base. } @@ -1141,15 +1219,20 @@ namespace build2 for (const scope* proot (nullptr); ; proot = root) { + bool top (proot == nullptr); + root = &create_root (iroot, out_root, src_root)->second; - if (!bootstrapped (*root)) + bool bstrapped (bootstrapped (*root)); + + if (!bstrapped) { bootstrap_out (*root); // Check that the bootstrap process set src_root. // - if (auto l = root->vars[*var_src_root]) + auto l (root->vars[*var_src_root]); + if (l) { const dir_path& p (cast (l)); @@ -1161,16 +1244,30 @@ namespace build2 fail (loc) << "unable to determine src_root for imported " << proj << info << "consider configuring " << out_root; - setup_root (*root); + setup_root ( + *root, + top ? fwd : forwarded (*proot, out_root, l->as ())); + bootstrap_pre (*root); bootstrap_src (*root); + if (!top) + bootstrap_post (*root); } - else if (src_root.empty ()) - src_root = root->src_path (); + else + { + if (src_root.empty ()) + src_root = root->src_path (); - if (proot == nullptr - ? fwd - : forwarded (*proot, root->out_path (), root->src_path ())) - root->assign (var_forwarded) = true; + if (top ? fwd : forwarded (*proot, out_root, src_root)) + root->assign (var_forwarded) = true; // Only upgrade (see main()). + } + + if (top) + { + create_bootstrap_outer (*root); + + if (!bstrapped) + bootstrap_post (*root); + } // Now we know this project's name as well as all its subprojects. // @@ -1194,14 +1291,9 @@ namespace build2 fail (loc) << out_root << " is not out_root for " << proj; } - // Bootstrap outer roots if any. Loading will be done by load_root_pre() - // below. - // - create_bootstrap_outer (*root); - // Load the imported root scope. // - load_root_pre (*root); + load_root (*root); // Create a temporary scope so that the export stub does not mess // up any of our variables. diff --git a/build2/file.hxx b/build2/file.hxx index 74f2f64..23362c8 100644 --- a/build2/file.hxx +++ b/build2/file.hxx @@ -24,8 +24,9 @@ namespace build2 ostream& operator<< (ostream&, const subprojects&); // Print as name@dir sequence. - extern const dir_path build_dir; // build - extern const dir_path bootstrap_dir; // build/bootstrap + extern const dir_path build_dir; // build/ + extern const dir_path root_dir; // build/root/ + extern const dir_path bootstrap_dir; // build/bootstrap/ extern const path root_file; // build/root.build extern const path bootstrap_file; // build/bootstrap.build @@ -79,11 +80,11 @@ namespace build2 scope_map::iterator create_root (scope&, const dir_path& out_root, const dir_path& src_root); - // Setup root scope. Note that it assumes the src_root variable - // has already been set. + // Setup root scope. Note that it assumes the src_root variable has already + // been set. // void - setup_root (scope&); + setup_root (scope&, bool forwarded); // Setup the base scope (set *_base variables, etc). // @@ -128,8 +129,8 @@ namespace build2 void bootstrap_out (scope& root); - // Bootstrap the project's root scope, the src part. Return true if - // we loaded anything (which confirms the src_root is not bogus). + // Bootstrap the project's root scope, the src part. Return true if we + // loaded anything (which confirms the src_root is not bogus). // bool bootstrap_src (scope& root); @@ -144,8 +145,17 @@ namespace build2 bool bootstrapped (scope& root); + // Execute pre/post-bootstrap hooks. Similar to bootstrap_out/sr(), should + // only be called once per project bootstrap. + // + void + bootstrap_pre (scope& root); + + void + bootstrap_post (scope& root); + // Create and bootstrap outer root scopes, if any. Loading is done by - // load_root_pre(). + // load_root(). // void create_bootstrap_outer (scope& root); @@ -153,17 +163,17 @@ namespace build2 // Create and bootstrap inner root scopes between root and base, if any. If // out_base is empty, then bootstrap all the way in. Return the innermost // created root scope or root if none were created. Note: loading is done by - // load_root_pre(). + // load_root(). // scope& create_bootstrap_inner (scope& root, const dir_path& out_base = dir_path ()); - // Load project's root[-pre].build unless already loaded. Also - // make sure all outer root scopes are loaded prior to loading + // Load project's root.build (and root pre/post hooks) unless already + // loaded. Also make sure all outer root scopes are loaded prior to loading // this root scope. // void - load_root_pre (scope& root); + load_root (scope& root); // Extract the specified variable value from a buildfile. It is expected to // be the first non-comment line and not to rely on any variable expansion diff --git a/build2/file.ixx b/build2/file.ixx index 15fa8dc..ef944cf 100644 --- a/build2/file.ixx +++ b/build2/file.ixx @@ -26,4 +26,19 @@ namespace build2 assert (phase == run_phase::match || phase == run_phase::execute); return import (pk, true); } + + void + source_hooks (scope&, const dir_path&, bool); + + inline void + bootstrap_pre (scope& root) + { + source_hooks (root, bootstrap_dir, true /* pre */); + } + + inline void + bootstrap_post (scope& root) + { + source_hooks (root, bootstrap_dir, false /* pre */); + } } diff --git a/build2/operation.cxx b/build2/operation.cxx index dabebcc..10ae6e7 100644 --- a/build2/operation.cxx +++ b/build2/operation.cxx @@ -72,9 +72,9 @@ namespace build2 const dir_path& src_base, const location&) { - // Load project's root[-pre].build. + // Load project's root.build. // - load_root_pre (root); + 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. diff --git a/tests/hooks/buildfile b/tests/hooks/buildfile new file mode 100644 index 0000000..c951d56 --- /dev/null +++ b/tests/hooks/buildfile @@ -0,0 +1,5 @@ +# file : tests/hooks/buildfile +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +./: test{testscript} $b diff --git a/tests/hooks/testscript b/tests/hooks/testscript new file mode 100644 index 0000000..7ad8028 --- /dev/null +++ b/tests/hooks/testscript @@ -0,0 +1,30 @@ +# file : tests/hooks/testscript +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include ../common.test + ++cat <'print bootstrap' >+build/bootstrap.build ++cat <'print root' >=build/root.build + ++mkdir build/bootstrap ++cat <'print pre-bootstrap' >=build/bootstrap/pre-bootstrap.build ++cat <'print post-bootstrap' >=build/bootstrap/post-bootstrap.build + ++mkdir build/root ++cat <'print pre-root' >=build/root/pre-root.build ++cat <'print post-root' >=build/root/post-root.build + +: basics +: +$* <>EOO +print buildfile +EOI +pre-bootstrap +bootstrap +post-bootstrap +pre-root +root +post-root +buildfile +EOO -- cgit v1.1