From 1845141809aa91b03718066a6f46863885a6a887 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 7 Mar 2019 09:06:37 +0200 Subject: Add support for alternative build file/directory naming scheme Now the build/*.build, buildfile, and .buildignore filesystem entries in a project can alternatively (but consistently) be called build2/*.build2, build2file, and .build2ignore. See a note at the beginning of the Project Structure section in the manual for details (motivation, restrictions, etc). --- build2/b-options.cxx | 17 +- build2/b.cli | 14 +- build2/b.cxx | 89 +++++++--- build2/cc/compile-rule.cxx | 8 +- build2/cc/init.cxx | 8 +- build2/cc/utility.cxx | 2 +- build2/cc/utility.hxx | 8 +- build2/config/init.cxx | 2 +- build2/config/operation.cxx | 41 +++-- build2/config/utility.cxx | 10 +- build2/config/utility.hxx | 9 + build2/dist/operation.cxx | 4 +- build2/file.cxx | 381 ++++++++++++++++++++++++++++++------------ build2/file.hxx | 43 ++--- build2/file.ixx | 15 -- build2/filesystem.cxx | 23 ++- build2/filesystem.hxx | 14 +- build2/parser.cxx | 31 +++- build2/scope.hxx | 25 +++ build2/search.cxx | 2 +- build2/target-key.hxx | 12 +- build2/target-type.hxx | 31 ++-- build2/target.cxx | 82 +++++---- build2/target.hxx | 19 +-- build2/target.txx | 2 +- build2/test/rule.cxx | 13 +- build2/test/script/runner.cxx | 24 ++- build2/test/script/script.cxx | 6 +- build2/test/script/script.hxx | 5 +- build2/test/target.cxx | 2 +- doc/manual.cli | 35 ++++ 31 files changed, 657 insertions(+), 320 deletions(-) diff --git a/build2/b-options.cxx b/build2/b-options.cxx index d9a9866..8306176 100644 --- a/build2/b-options.cxx +++ b/build2/b-options.cxx @@ -618,7 +618,7 @@ namespace build2 match_only_ (), no_column_ (), no_line_ (), - buildfile_ ("buildfile"), + buildfile_ (), buildfile_specified_ (false), config_guess_ (), config_guess_specified_ (false), @@ -835,13 +835,14 @@ namespace build2 os << std::endl << "\033[1m--buildfile\033[0m \033[4mpath\033[0m The alternative file to read build information from. The" << ::std::endl - << " default is \033[1mbuildfile\033[0m. If \033[4mpath\033[0m is '\033[1m-\033[0m', then read from" << ::std::endl - << " \033[1mSTDIN\033[0m. Note that this option only affects the files read" << ::std::endl - << " as part of the buildspec processing. Specifically, it has" << ::std::endl - << " no effect on the \033[1msource\033[0m and \033[1minclude\033[0m directives. As a" << ::std::endl - << " result, this option is primarily intended for testing" << ::std::endl - << " rather than changing the build file names in real" << ::std::endl - << " projects." << ::std::endl; + << " default is \033[1mbuildfile\033[0m or \033[1mbuild2file\033[0m, depending on the" << ::std::endl + << " project's build file/directory naming scheme. If \033[4mpath\033[0m is" << ::std::endl + << " '\033[1m-\033[0m', then read from \033[1mSTDIN\033[0m. Note that this option only" << ::std::endl + << " affects the files read as part of the buildspec" << ::std::endl + << " processing. Specifically, it has no effect on the \033[1msource\033[0m" << ::std::endl + << " and \033[1minclude\033[0m directives. As a result, this option is" << ::std::endl + << " primarily intended for testing rather than changing the" << ::std::endl + << " build file names in real projects." << ::std::endl; os << std::endl << "\033[1m--config-guess\033[0m \033[4mpath\033[0m The path to the \033[1mconfig.guess(1)\033[0m script that should be used" << ::std::endl diff --git a/build2/b.cli b/build2/b.cli index a88693d..35d5e2e 100644 --- a/build2/b.cli +++ b/build2/b.cli @@ -561,15 +561,17 @@ namespace build2 "Don't print line and column numbers in diagnostics." } - path --buildfile = "buildfile" + path --buildfile { "", "The alternative file to read build information from. The default is - \cb{buildfile}. If is '\cb{-}', then read from \cb{STDIN}. Note - that this option only affects the files read as part of the buildspec - processing. Specifically, it has no effect on the \cb{source} and - \cb{include} directives. As a result, this option is primarily intended - for testing rather than changing the build file names in real projects." + \cb{buildfile} or \cb{build2file}, depending on the project's build + file/directory naming scheme. If is '\cb{-}', then read from + \cb{STDIN}. Note that this option only affects the files read as part + of the buildspec processing. Specifically, it has no effect on the + \cb{source} and \cb{include} directives. As a result, this option is + primarily intended for testing rather than changing the build file + names in real projects." } path --config-guess diff --git a/build2/b.cxx b/build2/b.cxx index 0698e18..a7207b9 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -519,23 +519,59 @@ main (int argc, char* argv[]) // continuing in the parent directories until root. Return empty path if // not found. // - auto find_buildfile = [] (const dir_path& d, const dir_path& root) + auto find_buildfile = [] (const dir_path& sd, + const dir_path& root, + optional& altn) { - const path& n (ops.buildfile ()); + const path& n (ops.buildfile_specified () ? ops.buildfile () : path ()); if (n.string () == "-") return n; - for (path f (d / n);; ) + path f; + dir_path p; + + for (;;) { - if (exists (f)) + const dir_path& d (p.empty () ? sd : p.directory ()); + + // Note that we don't attempt to derive the project's naming scheme + // from the buildfile name specified by the user. + // + bool e; + if (!n.empty () || altn) + { + f = d / (!n.empty () ? n : (*altn + ? alt_buildfile_file + : std_buildfile_file)); + e = exists (f); + } + else + { + // Note: this case seems to be only needed for simple projects. + // + + // Check the alternative name first since it is more specific. + // + f = d / alt_buildfile_file; + + if ((e = exists (f))) + altn = true; + else + { + f = d / std_buildfile_file; + + if ((e = exists (f))) + altn = false; + } + } + + if (e) return f; - dir_path p (f.directory ()); + p = f.directory (); if (p == root) break; - - f = p.directory () / n; } return path (); @@ -751,6 +787,10 @@ main (int argc, char* argv[]) dir_path src_root; dir_path out_root; + // Standard/alternative build file/directory naming. + // + optional altn; + // Update these in buildspec. // bool& forwarded (ts.forwarded); @@ -782,7 +822,7 @@ main (int argc, char* argv[]) // If the src_base was explicitly specified, search for src_root. // - src_root = find_src_root (src_base); + src_root = find_src_root (src_base, altn); // If not found, assume this is a simple project with src_root // being the same as src_base. @@ -812,7 +852,7 @@ main (int argc, char* argv[]) { // If no src_base was explicitly specified, search for out_root. // - auto p (find_out_root (out_base)); + auto p (find_out_root (out_base, altn)); if (p.second) // Also src_root. { @@ -821,7 +861,7 @@ main (int argc, char* argv[]) // Handle a forwarded configuration. Note that if we've changed // out_root then we also have to remap out_base. // - out_root = bootstrap_fwd (src_root); + out_root = bootstrap_fwd (src_root, altn); if (src_root != out_root) { out_base = out_root / out_base.leaf (src_root); @@ -840,12 +880,12 @@ main (int argc, char* argv[]) // try to look for outer buildfiles since we don't have the root // to stop at. However, this shouldn't be an issue since simple // project won't normally have targets in subdirectories (or, in - // other words, we are not very interested "complex simple + // other words, we are not very interested in "complex simple // projects"). // if (out_root.empty ()) { - if (find_buildfile (out_base, out_base).empty ()) + if (find_buildfile (out_base, out_base, altn).empty ()) { fail << "no buildfile in " << out_base << info << "consider explicitly specifying its src_base"; @@ -873,7 +913,7 @@ main (int argc, char* argv[]) if (!bstrapped) { - bootstrap_out (rs); + bootstrap_out (rs, altn); // See if the bootstrap process set/changed src_root. // @@ -928,21 +968,28 @@ main (int argc, char* argv[]) // Now that we have src_root, load the src_root bootstrap file, // if there is one. // - bootstrap_pre (rs); - bootstrap_src (rs); + bootstrap_pre (rs, altn); + bootstrap_src (rs, altn); // bootstrap_post() delayed until after create_bootstrap_outer(). } 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; + + // Sync local variable that are used below with actual values. + // + if (src_root.empty ()) + src_root = rs.src_path (); + + if (!altn) + altn = rs.root_extra->altn; + else + assert (*altn == rs.root_extra->altn); } // At this stage we should have both roots and out_base figured @@ -1201,7 +1248,7 @@ main (int argc, char* argv[]) // data from the cash (we don't really care about the negative // answer since this is a degenerate case). // - path bf (find_buildfile (src_base, src_base)); + path bf (find_buildfile (src_base, src_base, altn)); if (bf.empty ()) { // If the target is a directory and the implied buildfile is @@ -1210,12 +1257,12 @@ main (int argc, char* argv[]) // if ((tn.directory () || tn.type == "dir") && exists (src_base) && - dir::check_implied (src_base)) + dir::check_implied (rs, src_base)) ; // Leave bf empty. else { if (src_base != src_root) - bf = find_buildfile (src_base.directory (), src_root); + bf = find_buildfile (src_base.directory (), src_root, altn); if (bf.empty ()) fail << "no buildfile in " << src_base << " or parent " diff --git a/build2/cc/compile-rule.cxx b/build2/cc/compile-rule.cxx index eacce7b..e0faf7f 100644 --- a/build2/cc/compile-rule.cxx +++ b/build2/cc/compile-rule.cxx @@ -3922,7 +3922,10 @@ namespace build2 // So the first step is to check if the project has already been created // and/or loaded and if not, then to go ahead and do so. // - dir_path pd (as->out_path () / modules_sidebuild_dir /= x); + dir_path pd (as->out_path () / + as->root_extra->build_dir / + modules_sidebuild_dir /= + x); { const scope* ps (&scopes.find (pd)); @@ -3942,7 +3945,8 @@ namespace build2 // The project might already be created in which case we just need // to load it. // - if (!is_src_root (pd)) + optional altn (false); // Standard naming scheme. + if (!is_src_root (pd, altn)) { // Copy our standard and force modules. // diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index edb9961..d9c1371 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -27,7 +27,9 @@ namespace build2 static target_state clean_module_sidebuilds (action, const scope& rs, const dir&) { - dir_path d (rs.out_path () / modules_sidebuild_dir); + const dir_path& out_root (rs.out_path ()); + + dir_path d (out_root / rs.root_extra->build_dir / modules_sidebuild_dir); if (exists (d)) { @@ -35,7 +37,7 @@ namespace build2 { // Clean up cc/ if it became empty. // - d = rs.out_path () / module_dir; + d = out_root / rs.root_extra->build_dir / module_dir; if (empty (d)) { rmdir (d); @@ -43,7 +45,7 @@ namespace build2 // And build/ if it also became empty (e.g., in case of a build // with a transient configuration). // - d = rs.out_path () / build_dir; + d = out_root / rs.root_extra->build_dir; if (empty (d)) rmdir (d); } diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 7e5ce53..39f5c35 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -19,7 +19,7 @@ namespace build2 { using namespace bin; - const dir_path module_dir (dir_path ("build") /= "cc"); + const dir_path module_dir ("cc"); const dir_path modules_sidebuild_dir (dir_path (module_dir) /= "modules"); lorder diff --git a/build2/cc/utility.hxx b/build2/cc/utility.hxx index bf96949..6b629de 100644 --- a/build2/cc/utility.hxx +++ b/build2/cc/utility.hxx @@ -19,8 +19,12 @@ namespace build2 namespace cc { - extern const dir_path module_dir; // build/cc/ - extern const dir_path modules_sidebuild_dir; // build/cc/modules/ + // To form the complete path do: + // + // root.out_path () / root.root_extra->build_dir / module_dir + // + extern const dir_path module_dir; // cc/ + extern const dir_path modules_sidebuild_dir; // cc/modules/ // Compile output type. // diff --git a/build2/config/init.cxx b/build2/config/init.cxx index 25c7022..9dc3a3c 100644 --- a/build2/config/init.cxx +++ b/build2/config/init.cxx @@ -88,7 +88,7 @@ namespace build2 const variable& c_v (vp.insert ("config.version", false)); { - path f (out_root / config_file); + path f (config_file (rs)); if (exists (f)) { diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 5acc42b..1064ebb 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -28,9 +28,12 @@ namespace build2 // configure // static void - save_src_root (const dir_path& out_root, const dir_path& src_root) + save_src_root (const scope& root) { - path f (out_root / src_root_file); + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path f (out_root / root.root_extra->src_root_file); if (verb >= 2) text << "cat >" << f; @@ -54,9 +57,12 @@ namespace build2 } static void - save_out_root (const dir_path& out_root, const dir_path& src_root) + save_out_root (const scope& root) { - path f (src_root / out_root_file); + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path f (src_root / root.root_extra->out_root_file); if (verb) text << (verb >= 2 ? "cat >" : "save ") << f; @@ -84,8 +90,7 @@ namespace build2 static void save_config (const scope& root, const project_set& projects) { - const dir_path& out_root (root.out_path ()); - path f (out_root / config_file); + path f (config_file (root)); if (verb) text << (verb >= 2 ? "cat >" : "save ") << f; @@ -319,8 +324,8 @@ namespace build2 // if (out_root != src_root) { - mkdir_p (out_root / build_dir); - mkdir (out_root / bootstrap_dir, 2); + mkdir_p (out_root / root.root_extra->build_dir); + mkdir (out_root / root.root_extra->bootstrap_dir, 2); } // We distinguish between a complete configure and operation- @@ -333,7 +338,7 @@ namespace build2 // Save src-root.build unless out_root is the same as src. // if (out_root != src_root) - save_src_root (out_root, src_root); + save_src_root (root); // Save config.build. // @@ -378,8 +383,8 @@ namespace build2 return; } - mkdir (src_root / bootstrap_dir, 2); // Make sure exists. - save_out_root (out_root, src_root); + mkdir (src_root / root.root_extra->bootstrap_dir, 2); // Make sure exists. + save_out_root (root); // Configure subprojects. Since we don't load buildfiles if configuring // a forward, we do it for all known subprojects. @@ -636,19 +641,19 @@ namespace build2 { l5 ([&]{trace << "completely disfiguring " << out_root;}); - r = rmfile (out_root / config_file) || r; + r = rmfile (config_file (root)) || r; if (out_root != src_root) { - r = rmfile (out_root / src_root_file, 2) || r; + r = rmfile (out_root / root.root_extra->src_root_file, 2) || r; // 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 / root.root_extra->root_dir, 2) || r; + r = rmdir (out_root / root.root_extra->bootstrap_dir, 2) || r; + r = rmdir (out_root / root.root_extra->build_dir, 2) || r; switch (rmdir (out_root)) { @@ -712,8 +717,8 @@ namespace build2 // Remove the out-root.build file and try to remove the bootstrap/ // directory if it is empty. // - r = rmfile (src_root / out_root_file) || r; - r = rmdir (src_root / bootstrap_dir, 2) || r; + r = rmfile (src_root / root.root_extra->out_root_file) || r; + r = rmdir (src_root / root.root_extra->bootstrap_dir, 2) || r; return r; } diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx index 2fee5f7..d2e8699 100644 --- a/build2/config/utility.cxx +++ b/build2/config/utility.cxx @@ -183,12 +183,14 @@ namespace build2 // Create the build/ subdirectory. // - mkdir (d / build_dir, verbosity); + // Note that for now we use the standard build file/directory scheme. + // + mkdir (d / std_build_dir, verbosity); // Write build/bootstrap.build. // { - path f (d / bootstrap_file); + path f (d / std_bootstrap_file); if (verb >= verbosity) text << (verb >= 2 ? "cat >" : "save ") << f; @@ -232,7 +234,7 @@ namespace build2 // Write build/root.build. // { - path f (d / root_file); + path f (d / std_root_file); if (verb >= verbosity) text << (verb >= 2 ? "cat >" : "save ") << f; @@ -281,7 +283,7 @@ namespace build2 // if (buildfile) { - path f (d / buildfile_file); + path f (d / std_buildfile_file); if (verb >= verbosity) text << (verb >= 2 ? "cat >" : "save ") << f; diff --git a/build2/config/utility.hxx b/build2/config/utility.hxx index 6f384f5..eff1a02 100644 --- a/build2/config/utility.hxx +++ b/build2/config/utility.hxx @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -160,6 +161,14 @@ namespace build2 bool buildfile, // Create root buildfile. const char* who, // Who is creating it. uint16_t verbosity = 1); // Diagnostic verbosity. + + inline path + config_file (const scope& root) + { + return (root.out_path () / + root.root_extra->build_dir / + "config." + root.root_extra->build_ext); + } } } diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index 75a5793..b227bbd 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -220,7 +220,7 @@ namespace build2 } }; - add_adhoc (*rs, export_file); + add_adhoc (*rs, rs->root_extra->export_file); // The same for subprojects that have been loaded. // @@ -238,7 +238,7 @@ namespace build2 if (!nrs.src_path ().sub (src_root)) // Not a strong amalgamation. continue; - add_adhoc (nrs, export_file); + add_adhoc (nrs, nrs.root_extra->export_file); } } diff --git a/build2/file.cxx b/build2/file.cxx index 7f51ebb..e205f27 100644 --- a/build2/file.cxx +++ b/build2/file.cxx @@ -24,23 +24,37 @@ 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"); - const path bootstrap_file (build_dir / "bootstrap.build"); - const path src_root_file (bootstrap_dir / "src-root.build"); - const path out_root_file (bootstrap_dir / "out-root.build"); - const path export_file (build_dir / "export.build"); - - // While strictly speaking it belongs in, say, config/module.cxx, the static - // initialization order strikes again. If we ever make the config module - // loadable, then we can move it there. + // Standard and alternative build file/directory naming schemes. // - const path config_file (build_dir / "config.build"); + const dir_path std_build_dir ("build"); + const dir_path std_root_dir (dir_path (std_build_dir) /= "root"); + const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap"); - const path buildfile_file ("buildfile"); + const path std_root_file (std_build_dir / "root.build"); + const path std_bootstrap_file (std_build_dir / "bootstrap.build"); + const path std_src_root_file (std_bootstrap_dir / "src-root.build"); + const path std_out_root_file (std_bootstrap_dir / "out-root.build"); + const path std_export_file (std_build_dir / "export.build"); + + const string std_build_ext ("build"); + const path std_buildfile_file ("buildfile"); + const path std_buildignore_file (".buildignore"); + + // + + const dir_path alt_build_dir ("build2"); + const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root"); + const dir_path alt_bootstrap_dir (dir_path (alt_build_dir) /= "bootstrap"); + + const path alt_root_file (alt_build_dir / "root.build2"); + const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2"); + const path alt_src_root_file (alt_bootstrap_dir / "src-root.build2"); + const path alt_out_root_file (alt_bootstrap_dir / "out-root.build2"); + const path alt_export_file (alt_build_dir / "export.build2"); + + const string alt_build_ext ("build2"); + const path alt_buildfile_file ("build2file"); + const path alt_buildignore_file (".build2ignore"); ostream& operator<< (ostream& os, const subprojects& sps) @@ -60,26 +74,61 @@ namespace build2 return os; } + // Check if the standard/alternative file/directory exists, returning empty + // path if it does not. + // + template + static T + exists (const dir_path& d, const T& s, const T& a, optional& altn) + { + T p; + bool e; + + if (altn) + { + p = d / (*altn ? a : s); + e = exists (p); + } + else + { + // Check the alternative name first since it is more specific. + // + p = d / a; + + if ((e = exists (p))) + altn = true; + else + { + p = d / s; + + if ((e = exists (p))) + altn = false; + } + } + + return e ? p : T (); + } + bool - is_src_root (const dir_path& d) + is_src_root (const dir_path& d, optional& altn) { - // We can't have root without bootstrap. + // We can't have root without bootstrap.build. // - return exists (d / bootstrap_file); + return !exists (d, std_bootstrap_file, alt_bootstrap_file, altn).empty (); } bool - is_out_root (const dir_path& d) + is_out_root (const dir_path& d, optional& altn) { - return exists (d / src_root_file); + return !exists (d, std_src_root_file, alt_src_root_file, altn).empty (); } dir_path - find_src_root (const dir_path& b) + find_src_root (const dir_path& b, optional& altn) { for (dir_path d (b); !d.root () && d != home; d = d.directory ()) { - if (is_src_root (d)) + if (is_src_root (d, altn)) return d; } @@ -87,12 +136,12 @@ namespace build2 } pair - find_out_root (const dir_path& b) + find_out_root (const dir_path& b, optional& altn) { for (dir_path d (b); !d.root () && d != home; d = d.directory ()) { bool s; - if ((s = is_src_root (d)) || is_out_root (d)) + if ((s = is_src_root (d, altn)) || is_out_root (d, altn)) return make_pair (move (d), s); } @@ -167,17 +216,12 @@ namespace build2 } // Source (once) pre-*.build (pre is true) or post-*.build (otherwise) hooks - // from the specified subdirectory (build/bootstrap/ or build/root/) of - // out_root/. + // from the specified directory (build/{bootstrap,root}/ of out_root) which + // must exist. // - void - source_hooks (scope& root, const dir_path& sd, bool pre) + static void + source_hooks (scope& root, const dir_path& d, 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. @@ -196,7 +240,7 @@ namespace build2 if (n.string ().compare (0, pre ? 4 : 5, pre ? "pre-" : "post-") != 0 || - n.extension () != "build") + n.extension () != root.root_extra->build_ext) continue; path f (d / n); @@ -387,20 +431,20 @@ namespace build2 } dir_path - bootstrap_fwd (const dir_path& src_root) + bootstrap_fwd (const dir_path& src_root, optional& altn) { - // We cannot just source the buildfile since there is no scope to do - // this on yet. - // - path bf (src_root / out_root_file); + path f (exists (src_root, std_out_root_file, alt_out_root_file, altn)); - if (!exists (bf)) + if (f.empty ()) return src_root; - auto p (extract_variable (bf, *var_out_root)); + // We cannot just source the buildfile since there is no scope to do + // this on yet. + // + auto p (extract_variable (f, *var_out_root)); if (!p.second) - fail << "variable out_root expected as first line in " << bf; + fail << "variable out_root expected as first line in " << f; try { @@ -408,25 +452,50 @@ namespace build2 } catch (const invalid_argument& e) { - fail << "invalid out_root value in " << bf << ": " << e << endf; + fail << "invalid out_root value in " << f << ": " << e << endf; } } + static void + setup_root_extra (scope& root, optional& altn) + { + assert (altn && root.root_extra == nullptr); + bool a (*altn); + + root.root_extra = unique_ptr ( + new scope::root_data { + a, + a ? alt_build_ext : std_build_ext, + a ? alt_build_dir : std_build_dir, + a ? alt_buildfile_file : std_buildfile_file, + a ? alt_buildignore_file : std_buildignore_file, + a ? alt_root_dir : std_root_dir, + a ? alt_bootstrap_dir : std_bootstrap_dir, + a ? alt_root_file : std_root_file, + a ? alt_export_file : std_export_file, + a ? alt_src_root_file : std_src_root_file, + a ? alt_out_root_file : std_out_root_file}); + } + void - bootstrap_out (scope& root) + bootstrap_out (scope& root, optional& altn) { - path bf (root.out_path () / src_root_file); + const dir_path& out_root (root.out_path ()); - if (!exists (bf)) + path f (exists (out_root, std_src_root_file, alt_src_root_file, altn)); + + if (f.empty ()) return; - //@@ 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) in - // buildfiles. + if (root.root_extra == nullptr) + setup_root_extra (root, altn); + + //@@ TODO: if bootstrap files can source other bootstrap files (for + // example, as a 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) in buildfiles. // - source_once (root, root, bf); + source_once (root, root, f); } pair @@ -470,7 +539,8 @@ namespace build2 static project_name find_project_name (const dir_path& out_root, const dir_path& fallback_src_root, - bool* src_hint = nullptr) + optional out_src, // True if out_root is src_root. + optional& altn) { tracer trace ("find_project_name"); @@ -482,6 +552,14 @@ namespace build2 if (s.root_scope () == &s && s.out_path () == out_root) { + if (s.root_extra != nullptr) + { + if (!altn) + altn = s.root_extra->altn; + else + assert (*altn == s.root_extra->altn); + } + if (lookup l = s.vars[var_project]) return cast (l); @@ -496,14 +574,22 @@ namespace build2 if (src_root == nullptr) { - if (src_hint != nullptr ? *src_hint : is_src_root (out_root)) + if (out_src ? *out_src : is_src_root (out_root, altn)) src_root = &out_root; else { - path f (out_root / src_root_file); + path f (exists (out_root, std_src_root_file, alt_src_root_file, altn)); + + if (f.empty ()) + { + // Note: the same diagnostics as in main(). + // + if (fallback_src_root.empty ()) + fail << "no bootstrapped src_root for " << out_root << + info << "consider reconfiguring this out_root"; - if (!fallback_src_root.empty () && !exists (f)) src_root = &fallback_src_root; + } else { auto p (extract_variable (f, *var_src_root)); @@ -523,7 +609,11 @@ namespace build2 project_name name; { - path f (*src_root / bootstrap_file); + path f (exists (*src_root, std_bootstrap_file, alt_bootstrap_file, altn)); + + if (f.empty ()) + fail << "no build/bootstrap.build in " << *src_root; + auto p (extract_variable (f, *var_project)); if (!p.second) @@ -559,7 +649,10 @@ namespace build2 dir_path sd (d / path_cast (de.path ())); bool src (false); - if (!((out && is_out_root (sd)) || (src = is_src_root (sd)))) + optional altn; + + if (!((out && is_out_root (sd, altn)) || + (src = is_src_root (sd, altn)))) { // We used to scan for subproject recursively but this is probably // too loose (think of some tests laying around). In the future we @@ -579,7 +672,7 @@ namespace build2 // Load its name. Note that here we don't use fallback src_root // since this function is used to scan both out_root and src_root. // - project_name name (find_project_name (sd, dir_path (), &src)); + project_name name (find_project_name (sd, dir_path (), src, altn)); // If the name is empty, then is is an unnamed project. While the // 'project' variable stays empty, here we come up with a surrogate @@ -618,7 +711,7 @@ namespace build2 } bool - bootstrap_src (scope& root) + bootstrap_src (scope& root, optional& altn) { tracer trace ("bootstrap_src"); @@ -627,21 +720,33 @@ namespace build2 const dir_path& out_root (root.out_path ()); const dir_path& src_root (root.src_path ()); - path bf (src_root / bootstrap_file); + { + path f (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn)); + + if (!f.empty ()) + { + // We assume that bootstrap out cannot load this file explicitly. It + // feels wrong to allow this since that makes the whole bootstrap + // process hard to reason about. But we may try to bootstrap the same + // root scope multiple time. + // + if (root.buildfiles.insert (f).second) + source (root, root, f, true); + else + l5 ([&]{trace << "skipping already sourced " << f;}); + + r = true; + } + } - if (exists (bf)) + if (root.root_extra == nullptr) { - // We assume that bootstrap out cannot load this file explicitly. It - // feels wrong to allow this since that makes the whole bootstrap - // process hard to reason about. But we may try to bootstrap the - // same root scope multiple time. + // If nothing so far has indicated the naming, assume standard. // - if (root.buildfiles.insert (bf).second) - source (root, root, bf, true); - else - l5 ([&]{trace << "skipping already sourced " << bf;}); + if (!altn) + altn = false; - r = true; + setup_root_extra (root, altn); } // See if we are a part of an amalgamation. There are two key players: the @@ -704,7 +809,8 @@ namespace build2 // outer directories is a project's out_root. If so, then // that's our amalgamation. // - const dir_path& ad (find_out_root (out_root.directory ()).first); + optional altn; + const dir_path& ad (find_out_root (out_root.directory (), altn).first); if (!ad.empty ()) { @@ -816,11 +922,15 @@ namespace build2 // if (n.empty ()) { - // Pass fallback src_root since this is a subproject that - // was specified by the user so it is most likely in our - // src. + optional altn; + + // Pass fallback src_root since this is a subproject that was + // specified by the user so it is most likely in our src. // - n = find_project_name (out_root / d, src_root / d); + n = find_project_name (out_root / d, + src_root / d, + nullopt /* out_src */, + altn); // See find_subprojects() for details on unnamed projects. // @@ -842,6 +952,40 @@ namespace build2 return r; } + void + bootstrap_pre (scope& root, optional& altn) + { + const dir_path& out_root (root.out_path ()); + + // This test is a bit loose in a sense that there can be a stray + // build/bootstrap/ directory that will make us mis-treat a project as + // following the standard naming scheme (the other way, while also + // possible, is a lot less likely). If this does becomes a problem, we can + // always tighten the test by also looking for a hook file with the + // correct extension. + // + dir_path d (exists (out_root, std_bootstrap_dir, alt_bootstrap_dir, altn)); + + if (!d.empty ()) + { + if (root.root_extra == nullptr) + setup_root_extra (root, altn); + + source_hooks (root, d, true /* pre */); + } + } + + void + bootstrap_post (scope& root) + { + const dir_path& out_root (root.out_path ()); + + dir_path d (out_root / root.root_extra->bootstrap_dir); + + if (exists (d)) + source_hooks (root, d, false /* pre */); + } + bool bootstrapped (scope& root) { @@ -859,7 +1003,8 @@ namespace build2 static inline bool forwarded (const scope& orig, const dir_path& out_root, - const dir_path& src_root) + const dir_path& src_root, + optional& altn) { // The conditions are: // @@ -871,7 +1016,7 @@ namespace build2 // return (out_root != src_root && cast_false (orig.vars[var_forwarded]) && - bootstrap_fwd (src_root) == out_root); + bootstrap_fwd (src_root, altn) == out_root); } void @@ -900,15 +1045,16 @@ namespace build2 bool bstrapped (bootstrapped (rs)); + optional altn; if (!bstrapped) { - bootstrap_out (rs); // #3 happens here (or it can be #1). + bootstrap_out (rs, altn); // #3 happens here (or it can be #1). value& v (rs.assign (var_src_root)); if (!v) { - if (is_src_root (out_root)) // #2 + if (is_src_root (out_root, altn)) // #2 v = out_root; else // #1 { @@ -920,14 +1066,16 @@ namespace build2 else remap_src_root (v); // Remap if inside old_src_root. - setup_root (rs, forwarded (root, out_root, v.as ())); - bootstrap_pre (rs); - bootstrap_src (rs); + setup_root (rs, forwarded (root, out_root, v.as (), altn)); + bootstrap_pre (rs, altn); + bootstrap_src (rs, altn); // bootstrap_post() delayed until after create_bootstrap_outer(). } else { - if (forwarded (root, rs.out_path (), rs.src_path ())) + altn = rs.root_extra->altn; + + if (forwarded (root, rs.out_path (), rs.src_path (), altn)) rs.assign (var_forwarded) = true; // Only upgrade (see main()). } @@ -960,29 +1108,31 @@ namespace build2 // scope& rs (create_root (root, out_root, dir_path ())->second); + optional altn; if (!bootstrapped (rs)) { - bootstrap_out (rs); + bootstrap_out (rs, altn); value& v (rs.assign (var_src_root)); if (!v) { - v = is_src_root (out_root) + v = is_src_root (out_root, altn) ? out_root : (root.src_path () / p.second); } else remap_src_root (v); // Remap if inside old_src_root. - setup_root (rs, forwarded (root, out_root, v.as ())); - bootstrap_pre (rs); - bootstrap_src (rs); + setup_root (rs, forwarded (root, out_root, v.as (), altn)); + bootstrap_pre (rs, altn); + bootstrap_src (rs, altn); bootstrap_post (rs); } else { - if (forwarded (root, rs.out_path (), rs.src_path ())) + altn = rs.root_extra->altn; + if (forwarded (root, rs.out_path (), rs.src_path (), altn)) rs.assign (var_forwarded) = true; // Only upgrade (see main()). } @@ -1008,12 +1158,15 @@ namespace build2 { tracer trace ("load_root"); + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + // 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); + path f (src_root / root.root_extra->root_file); - if (root.buildfiles.find (bf) != root.buildfiles.end ()) + if (root.buildfiles.find (f) != root.buildfiles.end ()) return; // First load outer roots, if any. @@ -1047,9 +1200,12 @@ namespace build2 // 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 */); + dir_path hd (out_root / root.root_extra->root_dir); + bool he (exists (hd)); + + if (he) source_hooks (root, hd, true /* pre */); + if (exists (f)) source_once (root, root, f); + if (he) source_hooks (root, hd, false /* pre */); } scope& @@ -1066,10 +1222,11 @@ namespace build2 if (!bootstrapped (rs)) { - bootstrap_out (rs); + optional altn; + bootstrap_out (rs, altn); setup_root (rs, forwarded); - bootstrap_pre (rs); - bootstrap_src (rs); + bootstrap_pre (rs, altn); + bootstrap_src (rs, altn); bootstrap_post (rs); } else @@ -1267,10 +1424,11 @@ namespace build2 // create_bootstrap_inner(). // bool fwd (false); - if (is_src_root (out_root)) + optional altn; + if (is_src_root (out_root, altn)) { src_root = move (out_root); - out_root = bootstrap_fwd (src_root); + out_root = bootstrap_fwd (src_root, altn); fwd = (src_root != out_root); } @@ -1284,7 +1442,7 @@ namespace build2 if (!bstrapped) { - bootstrap_out (*root); + bootstrap_out (*root, altn); // Check that the bootstrap process set src_root. // @@ -1305,20 +1463,24 @@ namespace build2 fail (loc) << "unable to determine src_root for imported " << proj << info << "consider configuring " << out_root; - setup_root ( - *root, - top ? fwd : forwarded (*proot, out_root, l->as ())); - bootstrap_pre (*root); - bootstrap_src (*root); + setup_root (*root, + (top + ? fwd + : forwarded (*proot, out_root, l->as (), altn))); + + bootstrap_pre (*root, altn); + bootstrap_src (*root, altn); if (!top) bootstrap_post (*root); } else { + altn = root->root_extra->altn; + if (src_root.empty ()) src_root = root->src_path (); - if (top ? fwd : forwarded (*proot, out_root, src_root)) + if (top ? fwd : forwarded (*proot, out_root, src_root, altn)) root->assign (var_forwarded) = true; // Only upgrade (see main()). } @@ -1343,8 +1505,9 @@ namespace build2 if (i != m.end ()) { const dir_path& d ((*i).second); + altn = nullopt; out_root = root->out_path () / d; - src_root = is_src_root (out_root) ? out_root : dir_path (); + src_root = is_src_root (out_root, altn) ? out_root : dir_path (); continue; } } @@ -1380,7 +1543,7 @@ namespace build2 // stub will normally switch to the imported root scope at some // point. // - path es (root->src_path () / export_file); + path es (root->src_path () / root->root_extra->export_file); try { diff --git a/build2/file.hxx b/build2/file.hxx index bfbf097..c46d2d5 100644 --- a/build2/file.hxx +++ b/build2/file.hxx @@ -24,24 +24,26 @@ namespace build2 ostream& operator<< (ostream&, const subprojects&); // Print as name@dir sequence. - 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 dir_path std_build_dir; // build/ + extern const path std_root_file; // build/root.build + extern const path std_bootstrap_file; // build/bootstrap.build - extern const path root_file; // build/root.build - extern const path bootstrap_file; // build/bootstrap.build - extern const path src_root_file; // build/bootstrap/src-root.build - extern const path out_root_file; // build/bootstrap/out-root.build - extern const path export_file; // build/export.build - extern const path config_file; // build/config.build - - extern const path buildfile_file; // buildfile + extern const path std_buildfile_file; // buildfile + extern const path alt_buildfile_file; // build2file + // If the altn argument value is present, then it indicates whether we are + // using the standard or the alternative build file/directory naming. + // + // The overall plan is to run various "file exists" tests using the standard + // and the alternative names. The first test that succeeds determines the + // naming scheme (by setting altn) and from then on all the remaining tests + // only look for things in this scheme. + // bool - is_src_root (const dir_path&); + is_src_root (const dir_path&, optional& altn); bool - is_out_root (const dir_path&); + is_out_root (const dir_path&, optional& altn); // Given an src_base directory, look for a project's src_root based on the // presence of known special files. Return empty path if not found. Note @@ -49,7 +51,7 @@ namespace build2 // well. // dir_path - find_src_root (const dir_path&); + find_src_root (const dir_path&, optional& altn); // The same as above but for project's out. Note that we also check whether // a directory happens to be src_root, in case this is an in-tree build with @@ -57,7 +59,7 @@ namespace build2 // input is normalized/actualized, then the output will be as well. // pair - find_out_root (const dir_path&); + find_out_root (const dir_path&, optional& altn); // The old/new src_root paths. See main() (where they are set) for details. // @@ -126,21 +128,22 @@ namespace build2 bool load = true); // Bootstrap the project's forward. Return the forwarded-to out_root or - // src_root if there is no forward. + // src_root if there is no forward. See is_{src,out}_root() for the altn + // argument semantics. // dir_path - bootstrap_fwd (const dir_path& src_root); + bootstrap_fwd (const dir_path& src_root, optional& altn); // Bootstrap the project's root scope, the out part. // void - bootstrap_out (scope& root); + bootstrap_out (scope& root, optional& altn); // 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); + bootstrap_src (scope& root, optional& altn); // Return true if this scope has already been bootstrapped, that is, the // following calls have already been made: @@ -156,7 +159,7 @@ namespace build2 // only be called once per project bootstrap. // void - bootstrap_pre (scope& root); + bootstrap_pre (scope& root, optional& altn); void bootstrap_post (scope& root); diff --git a/build2/file.ixx b/build2/file.ixx index 2d6e99d..a94d605 100644 --- a/build2/file.ixx +++ b/build2/file.ixx @@ -26,19 +26,4 @@ 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/filesystem.cxx b/build2/filesystem.cxx index fc34d4d..97f540b 100644 --- a/build2/filesystem.cxx +++ b/build2/filesystem.cxx @@ -190,17 +190,15 @@ namespace build2 } } - const path buildignore_file (".buildignore"); - fs_status - mkdir_buildignore (const dir_path& d, uint16_t verbosity) + mkdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity) { fs_status r (mkdir (d, verbosity)); // Create the .buildignore file if the directory was created (and so is // empty) or the file doesn't exist. // - path p (d / buildignore_file); + path p (d / n); if (r || !exists (p)) touch (p, true /* create */, verbosity); @@ -208,7 +206,7 @@ namespace build2 } bool - empty_buildignore (const dir_path& d) + empty_buildignore (const dir_path& d, const path& n) { try { @@ -217,8 +215,7 @@ namespace build2 // The .buildignore filesystem entry should be of the regular file // type. // - if (de.path () != buildignore_file || - de.ltype () != entry_type::regular) + if (de.path () != n || de.ltype () != entry_type::regular) return false; } } @@ -231,7 +228,7 @@ namespace build2 } fs_status - rmdir_buildignore (const dir_path& d, uint16_t verbosity) + rmdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity) { // We should remove the .buildignore file only if the subsequent rmdir() // will succeed. In other words if the directory stays after the function @@ -239,13 +236,13 @@ namespace build2 // first check that the directory is otherwise empty and doesn't contain // the working directory. // - path p (d / buildignore_file); - if (exists (p) && empty_buildignore (d) && !work.sub (d)) + path p (d / n); + if (exists (p) && empty_buildignore (d, n) && !work.sub (d)) rmfile (p, verbosity); - // Note that in case of a system error the directory is likely to stay and - // the .buildfile is already removed. Trying to restore it feels like an - // overkill here. + // Note that in case of a system error the directory is likely to stay with + // the .buildignore file already removed. Trying to restore it feels like + // an overkill here. // return rmdir (d, verbosity); } diff --git a/build2/filesystem.hxx b/build2/filesystem.hxx index b575c4a..ccc5a73 100644 --- a/build2/filesystem.hxx +++ b/build2/filesystem.hxx @@ -129,27 +129,25 @@ namespace build2 bool empty (const dir_path&); - // Directories containing .buildignore file are automatically ignored by - // recursive names patterns. For now the file is just a marker and its - // contents don't matter. - // - extern const path buildignore_file; // .buildignore + // Directories containing .buildignore (or .build2ignore in the alternative + // naming scheme) file are automatically ignored by recursive name patterns. + // For now the file is just a marker and its contents don't matter. // Create a directory containing an empty .buildignore file. // fs_status - mkdir_buildignore (const dir_path&, uint16_t verbosity = 1); + mkdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1); // Return true if the directory is empty or only contains the .buildignore // file. Fail if the directory doesn't exist. // bool - empty_buildignore (const dir_path&); + empty_buildignore (const dir_path&, const path&); // Remove a directory if it is empty or only contains the .buildignore file. // fs_status - rmdir_buildignore (const dir_path&, uint16_t verbosity = 1); + rmdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1); } #include diff --git a/build2/parser.cxx b/build2/parser.cxx index f703e90..ef6691d 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -1236,15 +1236,24 @@ namespace build2 // 'buildfile'. // path p (move (n.dir)); + + bool a; if (n.value.empty ()) - p /= buildfile_file; + a = true; else { - bool d (path::traits::is_separator (n.value.back ())); - + a = path::traits::is_separator (n.value.back ()); p /= path (move (n.value)); - if (d) - p /= buildfile_file; + } + + if (a) + { + // This shouldn't happen but let's make sure. + // + if (root_->root_extra == nullptr) + fail (l) << "build file naming scheme is not yet known"; + + p /= root_->root_extra->buildfile_file; } l6 ([&]{trace (l) << "relative path " << p;}); @@ -3228,15 +3237,21 @@ namespace build2 include_match (move (v), move (e), a); }; - auto process = [&e, &appf, sp] (path&& m, const string& p, bool interm) + auto process = [this, &e, &appf, sp] (path&& m, + const string& p, + bool interm) { // Ignore entries that start with a dot unless the pattern that // matched them also starts with a dot. Also ignore directories - // containing the .buildignore file. + // containing the .buildignore file (ignoring the test if we don't + // have a sufficiently setup project root). // const string& s (m.string ()); if ((p[0] != '.' && s[path::traits::find_leaf (s)] == '.') || - (m.to_directory () && exists (*sp / m / buildignore_file))) + (root_ != nullptr && + root_->root_extra != nullptr && + m.to_directory () && + exists (*sp / m / root_->root_extra->buildignore_file))) return !interm; // Note that we have to make copies of the extension since there will diff --git a/build2/scope.hxx b/build2/scope.hxx index 3bb7269..fe7b0b6 100644 --- a/build2/scope.hxx +++ b/build2/scope.hxx @@ -279,6 +279,31 @@ namespace build2 public: loaded_module_map modules; // Only on root scope. + // Extra root scope-only data. + // + public: + struct root_data + { + bool altn; // True if using alternative build file/directory naming. + + // Build file/directory naming scheme used by this project. + // + const string& build_ext; // build or build2 (no dot) + const dir_path& build_dir; // build/ or build2/ + const path& buildfile_file; // buildfile or build2file + const path& buildignore_file; // buildignore or build2ignore + + const dir_path& root_dir; // build[2]/root/ + const dir_path& bootstrap_dir; // build[2]/bootstrap/ + + const path& root_file; // build[2]/root.build[2] + const path& export_file; // build[2]/export.build[2] + const path& src_root_file; // build[2]/bootstrap/src-root.build[2] + const path& out_root_file; // build[2]/bootstrap/src-root.build[2] + }; + + unique_ptr root_extra; + public: // RW access. // diff --git a/build2/search.cxx b/build2/search.cxx index 05e2680..c4b9ae3 100644 --- a/build2/search.cxx +++ b/build2/search.cxx @@ -115,7 +115,7 @@ namespace build2 if (!ext) { if (auto f = ctk.type->fixed_extension) - ext = f (ctk); + ext = f (ctk, s->root_scope ()); else if (auto f = ctk.type->default_extension) ext = f (ctk, *s, nullptr, true); diff --git a/build2/target-key.hxx b/build2/target-key.hxx index 4877bbd..ed9a0ed 100644 --- a/build2/target-key.hxx +++ b/build2/target-key.hxx @@ -50,8 +50,16 @@ namespace build2 return !x.ext || !y.ext || *x.ext == *y.ext; else { - const char* xe (x.ext ? x.ext->c_str () : tt.fixed_extension (x)); - const char* ye (y.ext ? y.ext->c_str () : tt.fixed_extension (y)); + // Note that for performance reasons here we use the specified extension + // without calling fixed_extension(). + // + const char* xe (x.ext + ? x.ext->c_str () + : tt.fixed_extension (x, nullptr /* root scope */)); + + const char* ye (y.ext + ? y.ext->c_str () + : tt.fixed_extension (y, nullptr /* root scope */)); return strcmp (xe, ye) == 0; } diff --git a/build2/target-type.hxx b/build2/target-type.hxx index d9e53a5..aec1bcf 100644 --- a/build2/target-type.hxx +++ b/build2/target-type.hxx @@ -25,14 +25,19 @@ namespace build2 // // If the extension derivation functions are NULL, then it means this target // type does not use extensions. Note that this is relied upon when deciding - // whether to print the extension; if the target does use extensions but the - // defaults couldn't (and its ok), couldn't (and its not ok), or shouldn't - // (logically) be obtained, then use target_extension_{null,fail,assert}(), - // respectively. The fixed extension function should return the fixed - // extension (which can point to the key's ext member if the explicit - // extension specificaton is allowed). If the default extension function - // returns NULL, then it means the default extension for this target could - // not be derived. + // whether to print the extension. + // + // The fixed extension function should return the fixed extension (which can + // point to the key's ext member; note that for performance reasons we + // currently only verify the explicitly specified extension on target + // insersion -- see target_key comparison for details). + // + // The root scope argument to the fixed extension function may be NULL which + // means the root scope is not known. A target type that relies on this must + // be prepared to resolve the root scope itself and handle the cases where + // the target is not (yet) in any project (this is currently only used to + // handle the alternative build file/directory naming scheme and hopefully + // it will stay that way). // // The default extension is used in two key (there are others) places: // search_existing_file() (called for a prerequisite with the last argument @@ -41,7 +46,8 @@ namespace build2 // The third argument is the default extension that is supplied (e.g., by a // rule) to derive_extension(), if any. The implementation can decide which // takes precedence, etc (see the exe{} target type for some interesting - // logic). + // logic). If the default extension function returns NULL, then it means the + // default extension for this target could not be derived. // // If the pattern function is not NULL, then it is used to amend a pattern // or match (reverse is false) and then, if the amendment call returned @@ -56,14 +62,15 @@ namespace build2 target* (*factory) (const target_type&, dir_path, dir_path, string); - const char* (*fixed_extension) (const target_key&); + const char* (*fixed_extension) (const target_key&, + const scope* root); optional (*default_extension) (const target_key&, - const scope&, + const scope& base, const char*, bool search); bool (*pattern) (const target_type&, - const scope&, + const scope& base, string& name, optional& extension, const location&, diff --git a/build2/target.cxx b/build2/target.cxx index b3dd29d..b8e2a60 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -371,9 +371,10 @@ namespace build2 // assert (phase != run_phase::execute); - optional e (tt.fixed_extension != nullptr - ? string (tt.fixed_extension (tk)) - : move (tk.ext)); + optional e ( + tt.fixed_extension != nullptr + ? string (tt.fixed_extension (tk, nullptr /* root scope */)) + : move (tk.ext)); t = tt.factory (tt, move (dir), move (out), move (name)); @@ -680,19 +681,6 @@ namespace build2 return search_existing_file (pk); } - optional - target_extension_null (const target_key&, const scope&, const char*, bool) - { - return nullopt; - } - - optional - target_extension_assert (const target_key&, const scope&, const char*, bool) - { - assert (false); // Attempt to obtain the default extension. - throw failed (); - } - void target_print_0_ext_verb (ostream& os, const target_key& k) { @@ -796,7 +784,7 @@ namespace build2 // dir // bool dir:: - check_implied (const dir_path& d) + check_implied (const scope& rs, const dir_path& d) { try { @@ -806,14 +794,14 @@ namespace build2 { case entry_type::directory: { - if (check_implied (d / path_cast (e.path ()))) + if (check_implied (rs, d / path_cast (e.path ()))) return true; break; } case entry_type::regular: { - if (e.path () == buildfile_file) + if (e.path () == rs.root_extra->buildfile_file) return true; break; @@ -938,7 +926,7 @@ namespace build2 const dir_path& src_base (base.src_path ()); - path bf (src_base / buildfile_file); + path bf (src_base / root.root_extra->buildfile_file); if (exists (bf)) { @@ -1091,17 +1079,43 @@ namespace build2 }; static const char* - buildfile_target_extension (const target_key& tk) + buildfile_target_extension (const target_key& tk, const scope* root) { - // If the name is special 'buildfile', then there is no extension, - // otherwise it is .build. + // If the name is the special 'buildfile', then there is no extension, + // otherwise it is 'build' (or 'build2file' and 'build2' in the + // alternative naming scheme). + + // Let's try hard not to need the root scope by trusting the extensions + // we were given. + // + // BTW, one way to get rid of all this root scope complication is to + // always require explicit extension specification for buildfiles. Since + // they are hardly ever mentioned explicitly, this should probably be ok. // - return *tk.name == "buildfile" ? "" : "build"; + if (tk.ext) + return tk.ext->c_str (); + + if (root == nullptr) + { + // The same login as in target::root_scope(). + // + // Note: we are guaranteed the scope is never NULL for prerequisites + // (where out/dir could be relative and none of this will work). + // + root = scopes.find (tk.out->empty () ? *tk.dir : *tk.out).root_scope (); + + if (root == nullptr || root->root_extra == nullptr) + fail << "unable to determine extension for buildfile target " << tk; + } + + return *tk.name == root->root_extra->buildfile_file.string () + ? "" + : root->root_extra->build_ext.c_str (); } static bool buildfile_target_pattern (const target_type&, - const scope&, + const scope& base, string& v, optional& e, const location& l, @@ -1116,10 +1130,18 @@ namespace build2 { e = target::split_name (v, l); - if (!e && v != "buildfile") + if (!e) { - e = "build"; - return true; + const scope* root (base.root_scope ()); + + if (root == nullptr || root->root_extra == nullptr) + fail (l) << "unable to determine extension for buildfile pattern"; + + if (v != root->root_extra->buildfile_file.string ()) + { + e = root->root_extra->build_ext; + return true; + } } } @@ -1153,7 +1175,7 @@ namespace build2 }; static const char* - man_extension (const target_key& tk) + man_extension (const target_key& tk, const scope*) { if (!tk.ext) fail << "man target " << tk << " must include extension (man section)"; @@ -1190,7 +1212,7 @@ namespace build2 }; static const char* - manifest_target_extension (const target_key& tk) + manifest_target_extension (const target_key& tk, const scope*) { // If the name is special 'manifest', then there is no extension, // otherwise it is .manifest. diff --git a/build2/target.hxx b/build2/target.hxx index d2d4b1c..c86a10b 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -1669,11 +1669,12 @@ namespace build2 search_implied (const scope&, const K&, tracer&); // Return true if the implied buildfile is plausible for the specified - // directory, that is, there is a buildfile in at least one of its - // subdirectories. Note that the directory must exist. + // subdirectory of a project with the specified root scope. That is, there + // is a buildfile in at least one of its subdirectories. Note that the + // directory must exist. // static bool - check_implied (const dir_path&); + check_implied (const scope& root, const dir_path&); private: static prerequisites_type @@ -1813,7 +1814,7 @@ namespace build2 // template const char* - target_extension_fix (const target_key&); + target_extension_fix (const target_key&, const scope*); template bool @@ -1834,16 +1835,6 @@ namespace build2 string&, optional&, const location&, bool); - // Always return NULL extension. - // - optional - target_extension_null (const target_key&, const scope&, const char*, bool); - - // Assert if called. - // - optional - target_extension_assert (const target_key&, const scope&, const char*, bool); - // Target print functions. // diff --git a/build2/target.txx b/build2/target.txx index eb570a0..3cc249b 100644 --- a/build2/target.txx +++ b/build2/target.txx @@ -43,7 +43,7 @@ namespace build2 // template const char* - target_extension_fix (const target_key& tk) + target_extension_fix (const target_key& tk, const scope*) { // A generic file target type doesn't imply any extension while a very // specific one (say man1) may have a fixed extension. So if one wasn't diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx index 2ff7ebf..941609d 100644 --- a/build2/test/rule.cxx +++ b/build2/test/rule.cxx @@ -437,9 +437,11 @@ namespace build2 // backlink_*() in algorithm.cxx for details.) // const scope& bs (t.base_scope ()); + const scope& rs (*bs.root_scope ()); + const path& buildignore_file (rs.root_extra->buildignore_file); dir_path bl; - if (cast_false (bs.root_scope ()->vars[var_forwarded])) + if (cast_false (rs.vars[var_forwarded])) { bl = bs.src_path () / wd.leaf (bs.out_path ()); clean_backlink (bl, verb_never); @@ -471,10 +473,11 @@ namespace build2 bool fail (before == output_before::fail); (fail ? error : warn) << "working directory " << wd << " exists " - << (empty_buildignore (wd) + << (empty_buildignore (wd, buildignore_file) ? "" : "and is not empty ") << "at the beginning of the test"; + if (fail) throw failed (); } @@ -513,7 +516,7 @@ namespace build2 { if (mk) { - mkdir_buildignore (wd, 2); + mkdir_buildignore (wd, buildignore_file, 2); mk = false; } @@ -568,11 +571,11 @@ namespace build2 // if (!bad && !one && !mk && after == output_after::clean) { - if (!empty_buildignore (wd)) + if (!empty_buildignore (wd, buildignore_file)) fail << "working directory " << wd << " is not empty at the " << "end of the test"; - rmdir_buildignore (wd, 2); + rmdir_buildignore (wd, buildignore_file, 2); } // Backlink if the working directory exists. diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index c881031..0d3716f 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -719,9 +719,13 @@ namespace build2 // alike utility functions so the failure message can contain // location info? // - fs_status r (sp.parent == nullptr - ? mkdir_buildignore (sp.wd_path, 2) - : mkdir (sp.wd_path, 2)); + fs_status r ( + sp.parent == nullptr + ? mkdir_buildignore ( + sp.wd_path, + sp.root->target_scope.root_scope ()->root_extra->buildignore_file, + 2) + : mkdir (sp.wd_path, 2)); if (r == mkdir_status::already_exists) fail << "working directory " << sp.wd_path << " already exists" << @@ -914,11 +918,15 @@ namespace build2 // a file cleanup when try to rmfile() directory instead of // file. // - rmdir_status r (recursive - ? rmdir_r (d, !wd, static_cast (v)) - : wd && sp.parent == nullptr - ? rmdir_buildignore (d, v) - : rmdir (d, v)); + rmdir_status r ( + recursive + ? rmdir_r (d, !wd, static_cast (v)) + : (wd && sp.parent == nullptr + ? rmdir_buildignore ( + d, + sp.root->target_scope.root_scope ()->root_extra->buildignore_file, + v) + : rmdir (d, v))); if (r == rmdir_status::success || (r == rmdir_status::not_exist && t == cleanup_type::maybe)) diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index f2a8d03..94d6d8b 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -517,6 +517,7 @@ namespace build2 const dir_path& rwd) : group (st.name == "testscript" ? string () : st.name, this), test_target (tt), + target_scope (tt.base_scope ()), script_target (st) { // Set the script working dir ($~) to $out_base/test/ (id_path @@ -563,7 +564,7 @@ namespace build2 // // @@ OUT: what if this is a @-qualified pair of names? // - t = search_existing (*n, tt.base_scope ()); + t = search_existing (*n, target_scope); if (t == nullptr) fail << "unknown target '" << *n << "' in test variable"; @@ -653,8 +654,7 @@ namespace build2 if (p.first) { if (var.override != nullptr) - p = s.test_target.base_scope ().find_override ( - var, move (p), true); + p = s.target_scope.find_override (var, move (p), true); return p.first; } diff --git a/build2/test/script/script.hxx b/build2/test/script/script.hxx index 479001b..4da9d97 100644 --- a/build2/test/script/script.hxx +++ b/build2/test/script/script.hxx @@ -536,8 +536,9 @@ namespace build2 script& operator= (const script&) = delete; public: - const target& test_target; // Target we are testing. - const testscript& script_target; // Target of the testscript file. + const target& test_target; // Target we are testing. + const build2::scope& target_scope; // Base scope of test target. + const testscript& script_target; // Target of the testscript file. // Pre-parse data. // diff --git a/build2/test/target.cxx b/build2/test/target.cxx index c440325..f75b556 100644 --- a/build2/test/target.cxx +++ b/build2/test/target.cxx @@ -12,7 +12,7 @@ namespace build2 namespace test { static const char* - testscript_target_extension (const target_key& tk) + testscript_target_extension (const target_key& tk, const scope*) { // If the name is special 'testscript', then there is no extension, // otherwise it is .testscript. diff --git a/doc/manual.cli b/doc/manual.cli index 2ac3b8f..15d7c31 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -459,6 +459,41 @@ $ bdep new --no-init -t exe -l c++,cpp hello | +\N|It is also possible to use an alternative build file/directory naming +scheme where every instance of the word \i{build} is replaced with \i{build2}, +for example: + +\ +hello/ +├── build2/ +│ ├── bootstrap.build2 +│ └── root.build2 +├── ... +└── build2file +\ + +Note that the naming must be consistent within a project with all the +filesystem entries either following \i{build} or \i{build2} scheme. In +other words, we cannot call the directory \c{build2/} while still using +\c{buildfile}. + +The alternative naming scheme is primarily useful when adding \c{build2} +support to an existing project along with other build systems. In this case, +the fairly generic standard names might already be in use. For example, it is +customary to have \c{build/} in \c{.gitignore}. Plus more specific naming will +make it easier to identify files and directories as belonging to the +\c{build2} support. For new projects as well as for existing projects that are +switching exclusively to \c{build2} the standard naming scheme is recommended. + +To create a project with the alternative naming using \l{bdep-new(1)} pass +the \c{alt-naming} project type sub-option. For example: + +\ +$ bdep new -t exe,alt-naming ... +\ + +| + To support lazy loading of subprojects (discussed later), reading of the project's build information is split into two phases: bootstrapping and loading. During bootstrapping the project's \c{build/bootstrap.build} file is -- cgit v1.1