diff options
Diffstat (limited to 'libbuild2/file.cxx')
-rw-r--r-- | libbuild2/file.cxx | 711 |
1 files changed, 572 insertions, 139 deletions
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index c93a86f..1b00662 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -7,6 +7,7 @@ #include <iomanip> // left, setw() #include <sstream> +#include <libbuild2/rule.hxx> #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> #include <libbuild2/context.hxx> @@ -28,6 +29,8 @@ namespace build2 { // Standard and alternative build file/directory naming schemes. // + extern const dir_path std_export_dir; + extern const dir_path alt_export_dir; // build: @@ -35,6 +38,7 @@ namespace build2 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 dir_path std_build_build_dir (dir_path (std_build_dir) /= "build"); + const dir_path std_export_dir (dir_path (std_build_dir) /= "export"); const path std_root_file (std_build_dir / "root.build"); const path std_bootstrap_file (std_build_dir / "bootstrap.build"); @@ -52,6 +56,7 @@ namespace 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 dir_path alt_build_build_dir (dir_path (alt_build_dir) /= "build"); + const dir_path alt_export_dir (dir_path (alt_build_dir) /= "export"); const path alt_root_file (alt_build_dir / "root.build2"); const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2"); @@ -218,7 +223,7 @@ namespace build2 // Checking for plausability feels expensive since we have to recursively // traverse the directory tree. Note, however, that if the answer is // positive, then shortly after we will be traversing this tree anyway and - // presumably this time getting the data from the cash (we don't really + // presumably this time getting the data from the cache (we don't really // care about the negative answer since this is a degenerate case). // optional<path> bf; @@ -306,7 +311,7 @@ namespace build2 { tracer trace ("source_once"); - if (!once.buildfiles.insert (bf).second) + if (!once.root_extra->insert_buildfile (bf)) { l5 ([&]{trace << "skipping already sourced " << bf;}); return false; @@ -357,7 +362,7 @@ namespace build2 // try { - for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */)) + for (const dir_entry& de: dir_iterator (d, dir_iterator::no_follow)) { // 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. @@ -522,10 +527,14 @@ namespace build2 pair<scope&, scope*> switch_scope (scope& root, const dir_path& out_base, bool proj) { + context& ctx (root.ctx); + + assert (ctx.phase == run_phase::load); + // First, enter the scope into the map and see if it is in any project. If // it is not, then there is nothing else to do. // - auto i (root.ctx.scopes.rw (root).insert_out (out_base)); + auto i (ctx.scopes.rw (root).insert_out (out_base)); scope& base (*i->second.front ()); scope* rs (nullptr); @@ -546,7 +555,7 @@ namespace build2 // Switch to the new root scope. // - if (rs != &root) + if (rs != &root && !rs->root_extra->loaded) load_root (*rs); // Load new root(s) recursively. // Now we can figure out src_base and finish setting the scope. @@ -581,37 +590,37 @@ namespace build2 fail << "variable out_root expected as first line in " << f << endf; } + scope::root_extra_type:: + root_extra_type (scope& root, bool a) + : altn (a), + loaded (false), + + build_ext (a ? alt_build_ext : std_build_ext), + build_dir (a ? alt_build_dir : std_build_dir), + buildfile_file (a ? alt_buildfile_file : std_buildfile_file), + buildignore_file (a ? alt_buildignore_file : std_buildignore_file), + root_dir (a ? alt_root_dir : std_root_dir), + bootstrap_dir (a ? alt_bootstrap_dir : std_bootstrap_dir), + build_build_dir (a ? alt_build_build_dir : std_build_build_dir), + bootstrap_file (a ? alt_bootstrap_file : std_bootstrap_file), + root_file (a ? alt_root_file : std_root_file), + export_file (a ? alt_export_file : std_export_file), + src_root_file (a ? alt_src_root_file : std_src_root_file), + out_root_file (a ? alt_out_root_file : std_out_root_file), + + var_pool (&root.ctx, &root.ctx.var_pool.rw (root), nullptr) + { + root.var_pool_ = &var_pool; + } + static void setup_root_extra (scope& root, optional<bool>& altn) { assert (altn && root.root_extra == nullptr); - bool a (*altn); - - root.root_extra.reset ( - new scope::root_extra_type { - nullopt /* project */, - nullopt /* amalgamation */, - nullopt /* subprojects */, - 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_build_build_dir : std_build_build_dir, - a ? alt_bootstrap_file : std_bootstrap_file, - 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, - {}, /* meta_operations */ - {}, /* operations */ - {}, /* modules */ - {}, /* override_cache */ - {}, /* target_types */ - {}, /* environment */ - ""} /* environment_checksum */); + + context& ctx (root.ctx); + + root.root_extra.reset (new scope::root_extra_type (root, *altn)); // Enter built-in meta-operation and operation names. Loading of // modules (via the src bootstrap; see below) can result in @@ -621,9 +630,9 @@ namespace build2 root.insert_meta_operation (perform_id, mo_perform); root.insert_meta_operation (info_id, mo_info); - root.insert_operation (default_id, op_default); - root.insert_operation (update_id, op_update); - root.insert_operation (clean_id, op_clean); + root.insert_operation (default_id, op_default, nullptr); + root.insert_operation (update_id, op_update, ctx.var_update); + root.insert_operation (clean_id, op_clean, ctx.var_clean); } value& @@ -842,10 +851,26 @@ namespace build2 try { - for (const dir_entry& de: dir_iterator (d, true /* ignore_dangling */)) + // It's probably possible that a subproject can be a symlink with the + // link target, for example, being in a git submodule. Considering that, + // it makes sense to warn about dangling symlinks. + // + for (const dir_entry& de: + dir_iterator (d, dir_iterator::detect_dangling)) { if (de.type () != entry_type::directory) + { + if (de.type () == entry_type::unknown) + { + bool sl (de.ltype () == entry_type::symlink); + + warn << "skipping " + << (sl ? "dangling symlink" : "inaccessible entry") << ' ' + << d / de.path (); + } + continue; + } dir_path sd (d / path_cast<dir_path> (de.path ())); @@ -913,7 +938,9 @@ namespace build2 } void - bootstrap_src (scope& rs, optional<bool>& altn) + bootstrap_src (scope& rs, optional<bool>& altn, + optional<dir_path> aovr, + bool sovr) { tracer trace ("bootstrap_src"); @@ -943,13 +970,15 @@ namespace build2 rs.root_extra->project = nullptr; rs.root_extra->amalgamation = nullptr; rs.root_extra->subprojects = nullptr; + + assert (!aovr || aovr->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. // - else if (rs.buildfiles.insert (bf).second) + else if (rs.root_extra->insert_buildfile (bf)) { // Extract the project name and amalgamation variable value so that // we can make them available while loading bootstrap.build. @@ -985,7 +1014,13 @@ namespace build2 const project_name pn (cast<project_name> (move (*pv))); rs.root_extra->project = &pn; - if (av && (av->null || av->empty ())) + // @@ We will still have original values in the variables during + // bootstrap. Not sure what we can do about that. But it seems + // harmless. + // + if (aovr) + rs.root_extra->amalgamation = aovr->empty () ? nullptr : &*aovr; + else if (av && (av->null || av->empty ())) rs.root_extra->amalgamation = nullptr; { @@ -1005,6 +1040,13 @@ namespace build2 fail << "variable " << *ctx.var_amalgamation << " expected as a " << "second line in " << bf; } + + // Replace the value if overridden. + // + // Note that root_extra::amalgamation will be re-pointed below. + // + if (aovr) + rs.vars.assign (ctx.var_amalgamation) = move (*aovr); } else { @@ -1071,6 +1113,12 @@ namespace build2 // no been configured. In this case falling through is what we want. } } + else if (v) + { + if (cast<dir_path> (v).absolute ()) + fail << "absolute directory in variable " << *ctx.var_amalgamation + << " value"; + } // Do additional checks if the outer root could be our amalgamation. // @@ -1129,6 +1177,14 @@ namespace build2 auto rp (rs.vars.insert (*ctx.var_subprojects)); // Set NULL by default. value& v (rp.first); + if (!sovr) + { + if (rp.second) + rp.second = false; // Keep NULL. + else + v = nullptr; // Make NULL. + } + if (rp.second) { // No subprojects set so we need to figure out if there are any. @@ -1285,9 +1341,9 @@ namespace build2 // Call module's post-boot functions. // - for (size_t i (0); i != root.root_extra->modules.size (); ++i) + for (size_t i (0); i != root.root_extra->loaded_modules.size (); ++i) { - module_state& s (root.root_extra->modules[i]); + module_state& s (root.root_extra->loaded_modules[i]); if (s.boot_post != nullptr) boot_post_module (root, s); @@ -1328,7 +1384,7 @@ namespace build2 } void - create_bootstrap_outer (scope& root) + create_bootstrap_outer (scope& root, bool subp) { context& ctx (root.ctx); @@ -1376,7 +1432,7 @@ namespace build2 setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn)); bootstrap_pre (rs, altn); - bootstrap_src (rs, altn); + bootstrap_src (rs, altn, nullopt, subp); // bootstrap_post() delayed until after create_bootstrap_outer(). } else @@ -1387,7 +1443,7 @@ namespace build2 rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } - create_bootstrap_outer (rs); + create_bootstrap_outer (rs, subp); if (!bstrapped) bootstrap_post (rs); @@ -1475,22 +1531,19 @@ namespace build2 } void - load_root (scope& root) + load_root (scope& root, + const function<void (parser&)>& pre, + const function<void (parser&)>& post) { tracer trace ("load_root"); - context& ctx (root.ctx); - - 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 f (src_root / root.root_extra->root_file); - - if (root.buildfiles.find (f) != root.buildfiles.end ()) + if (root.root_extra->loaded) + { + assert (pre == nullptr && post == nullptr); return; + } + + context& ctx (root.ctx); if (ctx.no_external_modules) fail << "attempt to load project " << root << " after skipped loading " @@ -1499,18 +1552,19 @@ namespace build2 // First load outer roots, if any. // if (scope* rs = root.parent_scope ()->root_scope ()) - load_root (*rs); + if (!rs->root_extra->loaded) + load_root (*rs); // Finish off initializing bootstrapped modules (before mode). // // Note that init() can load additional modules invalidating iterators. // auto init_modules = - [&root, n = root.root_extra->modules.size ()] (module_boot_init v) + [&root, n = root.root_extra->loaded_modules.size ()] (module_boot_init v) { for (size_t i (0); i != n; ++i) { - module_state& s (root.root_extra->modules[i]); + module_state& s (root.root_extra->loaded_modules[i]); if (s.boot_init && *s.boot_init == v) init_module (root, root, s.name, s.loc); @@ -1530,6 +1584,11 @@ namespace build2 // Load hooks and root.build. // + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path f (src_root / root.root_extra->root_file); + // 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, @@ -1545,10 +1604,22 @@ namespace build2 // parser p (ctx, load_stage::root); + if (pre != nullptr) + { + pre (p); + p.reset (); + } + if (he) {source_hooks (p, root, hd, true /* pre */); p.reset ();} if (fe) {source_once (p, root, root, f, root);} if (he) {p.reset (); source_hooks (p, root, hd, false /* pre */);} + if (post != nullptr) + { + p.reset (); + post (p); + } + // Finish off initializing bootstrapped modules (after mode). // { @@ -1639,7 +1710,7 @@ namespace build2 if (const value& v = *l) { storage.clear (); - auto ns (reverse (v, storage)); + auto ns (reverse (v, storage, true /* reduce */)); if (f == "multiline") { @@ -1654,6 +1725,8 @@ namespace build2 dr << left << setw (static_cast<int> (pad)) << n << " [null]"; } } + + root.root_extra->loaded = true; } scope& @@ -1690,7 +1763,8 @@ namespace build2 if (load) { - load_root (rs); + if (!rs.root_extra->loaded) + load_root (rs); setup_base (i, out_root, src_root); // Setup as base. } @@ -1745,8 +1819,10 @@ namespace build2 } // Extract metadata for an executable target by executing it with the - // --build2-metadata option. In case of an error, issue diagnostics and fail - // if opt is false and return nullopt if it's true. + // --build2-metadata option. Key is the target name (and not necessarily the + // same as metadata variable prefix in export.metadata; e.g., openbsd-m4 and + // openbsd_m4). In case of an error, issue diagnostics and fail if opt is + // false and return nullopt if it's true. // // Note that loading of the metadata is split into two steps, extraction and // parsing, because extraction also serves as validation that the executable @@ -1810,7 +1886,9 @@ namespace build2 try { // Note: not using run_*() functions since need to be able to suppress - // all errors, including inability to exec. + // all errors, including abnormal, inability to exec, etc., in case of + // optional import. Also, no need to buffer diagnostics since in the + // serial load. // if (verb >= 3) print_process (args); @@ -1870,10 +1948,19 @@ namespace build2 return r; if (!opt) - error (loc) << "invalid metadata signature in " << args[0] - << " output" << + { + diag_record dr; + dr << error (loc) << "invalid metadata signature in " << args[0] + << " output" << info << "expected '" << s << "'"; + if (verb >= 1 && verb <= 2) + { + dr << info << "command line: "; + print_process (dr, args); + } + } + goto fail; } @@ -1889,16 +1976,27 @@ namespace build2 if (pr.wait ()) { if (!opt) - error (loc) << "unable to read metadata from " << args[0]; + error (loc) << "io error reading metadata from " << args[0]; } else { // The child process presumably issued diagnostics but if it didn't, - // the result will be very confusing. So let's issue something - // generic for good measure. + // the result will be very confusing. So let's issue something generic + // for good measure. But also make it consistent with diagnostics + // issued by run_finish(). // if (!opt) - error (loc) << "unable to extract metadata from " << args[0]; + { + diag_record dr; + dr << error (loc) << "unable to extract metadata from " << args[0] << + info << "process " << args[0] << " " << *pr.exit; + + if (verb >= 1 && verb <= 2) + { + dr << info << "command line: "; + print_process (dr, args); + } + } } goto fail; @@ -1914,8 +2012,7 @@ namespace build2 goto fail; } - fail: - + fail: if (opt) { metadata_cache.insert (pp.effect_string (), true); @@ -1950,8 +2047,9 @@ namespace build2 static void import_suggest (const diag_record& dr, const project_name& pn, - const target_type& tt, + const target_type* tt, const string& tn, + bool rule_hint, const char* qual = nullptr) { string pv (pn.variable ()); @@ -1964,15 +2062,19 @@ namespace build2 // Suggest ad hoc import but only if it's a path-based target (doing it // for lib{} is very confusing). // - if (tt.is_a<path_target> ()) + if (tt != nullptr && tt->is_a<path_target> ()) { - string v (tt.is_a<exe> () && (pv == tn || pn == tn) + string v (tt->is_a<exe> () && (pv == tn || pn == tn) ? "config." + pv - : "config.import." + pv + '.' + tn + '.' + tt.name); + : "config.import." + pv + '.' + tn + '.' + tt->name); dr << info << "or use " << v << " configuration variable to specify " << "its " << (qual != nullptr ? qual : "") << "path"; } + + if (rule_hint) + dr << info << "or use rule_hint attribute to specify a rule that can " + << "find this target"; } // Return the processed target name as well as the project directory, if @@ -1987,6 +2089,9 @@ namespace build2 // Return empty name if an ad hoc import resulted in a NULL target (only // allowed if optional is true). // + // Note that this function has a side effect of potentially marking some + // config.import.* variables as used. + // pair<name, optional<dir_path>> import_search (bool& new_value, scope& ibase, @@ -2018,6 +2123,9 @@ namespace build2 // // 4. Normal import. // + // @@ PERF: in quite a few places (local, subproject) we could have + // returned the scope and save on bootstrap in import_load(). + // if (tgt.unqualified ()) { if (tgt.directory () && tgt.relative ()) @@ -2025,6 +2133,8 @@ namespace build2 if (tgt.absolute ()) { + // Ad hoc import. + // // Actualize the directory to be analogous to the config.import.<proj> // case (which is of abs_dir_path type). // @@ -2041,7 +2151,7 @@ namespace build2 fail (loc) << "project-local importation of target " << tgt << " from an unnamed project"; - tgt.proj = pn; + tgt.proj = pn; // Reduce to normal import. return make_pair (move (tgt), optional<dir_path> (iroot.out_path ())); } @@ -2073,7 +2183,9 @@ namespace build2 // over anything that we may discover. In particular, we will prefer it // over any bundled subprojects. // - auto& vp (iroot.var_pool ()); + // Note: go straight for the public variable pool. + // + auto& vp (iroot.var_pool (true /* public */)); using config::lookup_config; @@ -2243,7 +2355,8 @@ namespace build2 auto df = make_diag_frame ( [&proj, tt, &on] (const diag_record& dr) { - import_suggest (dr, proj, *tt, on, "alternative "); + import_suggest ( + dr, proj, tt, on, false, "alternative "); }); md = extract_metadata (e->process_path (), @@ -2366,6 +2479,8 @@ namespace build2 { tracer trace ("import_load"); + uint64_t metav (meta ? 1 : 0); // Metadata version. + // We end up here in two cases: Ad hoc import, in which case name is // unqualified and absolute and path is a base, not necessarily root. And // normal import, in which case name must be project-qualified and path is @@ -2428,14 +2543,51 @@ namespace build2 } } + // First check the cache. + // + using import_key = context::import_key; + + auto cache_find = [&ctx, &tgt, metav] (dir_path& out_root) -> + const pair<names, const scope&>* + { + import_key k {move (out_root), move (tgt), metav}; + + auto i (ctx.import_cache.find (k)); + if (i != ctx.import_cache.end ()) + return &i->second; + + out_root = move (k.out_root); + tgt = move (k.target); + + return nullptr; + }; + + if (proj) + { + if (const auto* r = cache_find (out_root)) + return *r; + } + + dir_path cache_out_root; + // Clear current project's environment. // auto_project_env penv (nullptr); + // Note: this loop does at most two iterations. + // for (const scope* proot (nullptr); ; proot = root) { bool top (proot == nullptr); + // Check the cache for the subproject. + // + if (!top && proj) + { + if (const auto* r = cache_find (out_root)) + return *r; + } + root = create_root (ctx, out_root, src_root)->second.front (); bool bstrapped (bootstrapped (*root)); @@ -2514,6 +2666,8 @@ namespace build2 if (i != ps->end ()) { + cache_out_root = move (out_root); + const dir_path& d ((*i).second); altn = nullopt; out_root = root->out_path () / d; @@ -2525,9 +2679,69 @@ namespace build2 fail (loc) << out_root << " is not out_root for " << *proj; } + // Buildfile importation is quite different so handle it separately. + // + // Note that we don't need to load the project in this case. + // + // @@ For now we don't out-qualify the resulting target to be able to + // re-import it ad hoc (there is currently no support for out-qualified + // ad hoc import). Feels like this should be harmless since it's just a + // glorified path to a static file that nobody is actually going to use + // as a target (e.g., to depend upon). + // + if (tgt.type == "buildfile") + { + auto add_ext = [&altn] (string& n) + { + if (path_traits::find_extension (n) == string::npos) + { + if (n != (*altn ? alt_buildfile_file : std_buildfile_file).string ()) + { + n += "."; + n += *altn ? alt_build_ext : std_build_ext; + } + } + }; + + if (proj) + { + name n; + + n.dir = move (src_root); + n.dir /= *altn ? alt_export_dir : std_export_dir; + if (!tgt.dir.empty ()) + { + n.dir /= tgt.dir; + n.dir.normalize (); + } + + n.type = tgt.type; + n.value = tgt.value; + add_ext (n.value); + + pair<names, const scope&> r (names {move (n)}, *root); + + // Cache. + // + if (cache_out_root.empty ()) + cache_out_root = move (out_root); + + ctx.import_cache.emplace ( + import_key {move (cache_out_root), move (tgt), metav}, r); + + return r; + } + else + { + add_ext (tgt.value); + return pair<names, const scope&> (names {move (tgt)}, *root); + } + } + // Load the imported root scope. // - load_root (*root); + if (!root->root_extra->loaded) + load_root (*root); // If this is a normal import, then we go through the export stub. // @@ -2542,6 +2756,9 @@ namespace build2 // "Pass" the imported project's roots to the stub. // + if (cache_out_root.empty ()) + cache_out_root = out_root; + ts.assign (ctx.var_out_root) = move (out_root); ts.assign (ctx.var_src_root) = move (src_root); @@ -2557,7 +2774,7 @@ namespace build2 // Pass the metadata compatibility version in import.metadata. // if (meta) - ts.assign (ctx.var_import_metadata) = uint64_t (1); + ts.assign (ctx.var_import_metadata) = metav; // Load the export stub. Note that it is loaded in the context of the // importing project, not the imported one. The export stub will @@ -2583,7 +2800,7 @@ namespace build2 }); parser p (ctx); - v = p.parse_export_stub (ifs, path_name (es), gs, ts); + v = p.parse_export_stub (ifs, path_name (es), *root, gs, ts); } // If there were no export directive executed in an export stub, @@ -2593,7 +2810,14 @@ namespace build2 fail (loc) << "target " << tgt << " is not exported by project " << *proj; - return pair<names, const scope&> (move (v), *root); + pair<names, const scope&> r (move (v), *root); + + // Cache. + // + ctx.import_cache.emplace ( + import_key {move (cache_out_root), move (tgt), metav}, r); + + return r; } catch (const io_error& e) { @@ -2650,10 +2874,13 @@ namespace build2 } } + static names + import2_buildfile (context&, names&&, bool, const location&); + pair<names, import_kind> import (scope& base, name tgt, - bool ph2, + const optional<string>& ph2, bool opt, bool metadata, const location& loc) @@ -2677,11 +2904,10 @@ namespace build2 // if (metadata) { - pair<const target*, import_kind> r ( + import_result<target> r ( import_direct (base, move (tgt), ph2, opt, metadata, loc)); - return make_pair (r.first != nullptr ? r.first->as_name () : names {}, - r.second); + return make_pair (move (r.name), r.kind); } pair<name, optional<dir_path>> r ( @@ -2712,18 +2938,30 @@ namespace build2 // if (ns.back ().qualified ()) { - if (ph2) + if (ns.back ().type == "buildfile") + { + assert (ph2); + ns = import2_buildfile (ctx, move (ns), opt && !r.second, loc); + } + else if (ph2) { // This is tricky: we only want the optional semantics for the // fallback case. // - if (const target* t = import (ctx, - base.find_prerequisite_key (ns, loc), - opt && !r.second /* optional */, - nullopt /* metadata */, - false /* existing */, - loc)) + if (const target* t = import2 (ctx, + base.find_prerequisite_key (ns, loc), + *ph2, + opt && !r.second /* optional */, + nullopt /* metadata */, + false /* existing */, + loc)) + { + // Note that here r.first was still project-qualified and we + // have no choice but to call as_name(). This shouldn't cause + // any problems since the import() call assigns the extension. + // ns = t->as_name (); + } else ns.clear (); // NULL } @@ -2747,21 +2985,82 @@ namespace build2 } const target* - import (context& ctx, - const prerequisite_key& pk, - bool opt, - const optional<string>& meta, - bool exist, - const location& loc) + import2 (context& ctx, + const prerequisite_key& pk, + const string& hint, + bool opt, + const optional<string>& meta, + bool exist, + const location& loc) { - tracer trace ("import"); + tracer trace ("import2"); - assert (!meta || !exist); + // Neither hint nor metadata can be requested for existing. + // + assert (!exist || (!meta && hint.empty ())); assert (pk.proj); const project_name& proj (*pk.proj); - // Target type-specific search. + // Note that if this function returns a target, it should have the + // extension assigned (like the find/insert_target() functions) so that + // as_name() returns a stable name. + + // Rule-specific resolution. + // + if (!hint.empty ()) + { + assert (pk.scope != nullptr); + + // Note: similar to/inspired by match_rule(). + // + // Search scopes outwards, stopping at the project root. + // + for (const scope* s (pk.scope); + s != nullptr; + s = s->root () ? nullptr : s->parent_scope ()) + { + // We only look for rules that are registered for perform(update). + // + if (const operation_rule_map* om = s->rules[perform_id]) + { + if (const target_type_rule_map* ttm = (*om)[update_id]) + { + // Ignore the target type the rules are registered for (this is + // about prerequisite types, not target). + // + // @@ Note that the same rule could be registered for several + // types which means we will keep calling it repeatedly. + // + for (const auto& p: *ttm) + { + const name_rule_map& nm (p.second); + + // Filter against the hint. + // + for (auto p (nm.find_sub (hint)); p.first != p.second; ++p.first) + { + const string& n (p.first->first); + const rule& r (p.first->second); + + auto df = make_diag_frame ( + [&pk, &n](const diag_record& dr) + { + if (verb != 0) + dr << info << "while importing " << pk << " using rule " + << n; + }); + + if (const target* t = r.import (pk, meta, loc)) + return t; + } + } + } + } + } + } + + // Builtin resolution for certain target types. // const target_key& tk (pk.tk); const target_type& tt (*tk.type); @@ -2814,8 +3113,7 @@ namespace build2 auto df = make_diag_frame ( [&proj, &tt, &tk] (const diag_record& dr) { - import_suggest ( - dr, proj, tt, *tk.name, "alternative "); + import_suggest (dr, proj, &tt, *tk.name, false, "alternative "); }); if (!(md = extract_metadata (pp, *meta, opt, loc))) @@ -2824,6 +3122,9 @@ namespace build2 if (!t || *t == nullptr) { + // Note: we need the lock because process_path() call below is not + // MT-safe. + // pair<target&, ulock> r (insert_target (trace, ctx, tt, p)); t = &r.first; @@ -2855,29 +3156,108 @@ namespace build2 dr << info << "consider adding its installation location" << info << "or explicitly specify its project name"; else - import_suggest (dr, proj, tt, *tk.name); + // Use metadata as proxy for immediate import. + // + import_suggest (dr, proj, &tt, *tk.name, meta && hint.empty ()); dr << endf; } - pair<const target*, import_kind> + static names + import2_buildfile (context&, names&& ns, bool opt, const location& loc) + { + tracer trace ("import2_buildfile"); + + assert (ns.size () == 1); + name n (move (ns.front ())); + + // Our approach doesn't work for targets without a project so let's fail + // hard, even if optional. + // + if (!n.proj || n.proj->empty ()) + fail (loc) << "unable to import target " << n << " without project name"; + + while (!build_install_buildfile.empty ()) // Breakout loop. + { + path f (build_install_buildfile / + dir_path (n.proj->string ()) / + n.dir / + n.value); + + // See if we need to try with extensions. + // + bool ext (path_traits::find_extension (n.value) == string::npos && + n.value != std_buildfile_file.string () && + n.value != alt_buildfile_file.string ()); + + if (ext) + { + f += '.'; + f += std_build_ext; + } + + if (!exists (f)) + { + l6 ([&]{trace << "tried " << f;}); + + if (ext) + { + f.make_base (); + f += '.'; + f += alt_build_ext; + + if (!exists (f)) + { + l6 ([&]{trace << "tried " << f;}); + break; + } + } + else + break; + } + + // Split the path into the target. + // + ns = {name (f.directory (), move (n.type), f.leaf ().string ())}; + return move (ns); + } + + if (opt) + return names {}; + + diag_record dr; + dr << fail (loc) << "unable to import target " << n; + + import_suggest (dr, *n.proj, nullptr /* tt */, n.value, false); + + if (build_install_buildfile.empty ()) + dr << info << "no exported buildfile installation location is " + << "configured in build2"; + else + dr << info << "exported buildfile installation location is " + << build_install_buildfile; + + dr << endf; + } + + import_result<target> import_direct (bool& new_value, scope& base, name tgt, - bool ph2, + const optional<string>& ph2, bool opt, bool metadata, const location& loc, const char* what) { - // This is like normal import() except we return the target rather than + // This is like normal import() except we return the target in addition to // its name. // tracer trace ("import_direct"); l5 ([&]{trace << tgt << " from " << base << " for " << what;}); - assert ((!opt || ph2) && (!metadata || ph2)); + assert ((!opt || ph2) && (!metadata || ph2) && tgt.type != "buildfile"); context& ctx (base.ctx); assert (ctx.phase == run_phase::load); @@ -2886,7 +3266,7 @@ namespace build2 // auto meta (metadata ? optional<string> (tgt.value) : nullopt); - names ns; + names ns, rns; import_kind k; const target* pt (nullptr); @@ -2909,7 +3289,7 @@ namespace build2 if (r.first.empty ()) { assert (opt); - return make_pair (pt, k); // NULL + return import_result<target> {nullptr, {}, k}; // NULL } else if (r.first.qualified ()) { @@ -2920,31 +3300,44 @@ namespace build2 // This is tricky: we only want the optional semantics for the // fallback case. // - pt = import (ctx, - base.find_prerequisite_key (ns, loc), - opt && !r.second, - meta, - false /* existing */, - loc); + pt = import2 (ctx, + base.find_prerequisite_key (ns, loc), + *ph2, + opt && !r.second, + meta, + false /* existing */, + loc); } if (pt == nullptr) - return make_pair (pt, k); // NULL + return import_result<target> {nullptr, {}, k}; // NULL + + // Note that here r.first was still project-qualified and we have no + // choice but to call as_name() (below). This shouldn't cause any + // problems since the import() call assigns the extension. - // Otherwise fall through. + // Fall through. } else + { + // It's a bit fuzzy in which cases we end up here. So for now we keep + // the original if it's absolute and call as_name() otherwise. + // + if (r.first.absolute ()) + rns.push_back (r.first); + ns.push_back (move (r.first)); // And fall through. + } } else { k = r.first.absolute () ? import_kind::adhoc : import_kind::normal; - ns = import_load (base.ctx, move (r), metadata, loc).first; + rns = ns = import_load (base.ctx, move (r), metadata, loc).first; } if (pt == nullptr) { - // Similar logic to perform's search(). + // Similar logic to perform's search(). Note: modifies ns. // target_key tk (base.find_target_key (ns, loc)); pt = ctx.targets.find (tk, trace); @@ -2952,6 +3345,9 @@ namespace build2 fail (loc) << "unknown imported target " << tk; } + if (rns.empty ()) + rns = pt->as_name (); + target& t (pt->rw ()); // Load phase. // Note that if metadata is requested via any of the import*() functions, @@ -2963,7 +3359,10 @@ namespace build2 // The export.metadata value should start with the version followed by // the metadata variable prefix. // - lookup l (t.vars[ctx.var_export_metadata]); + // Note: lookup on target, not target::vars since it could come from + // the group (think lib{} metadata). + // + lookup l (t[ctx.var_export_metadata]); if (l && !l->empty ()) { const names& ns (cast<names> (l)); @@ -2996,13 +3395,15 @@ namespace build2 const string& pfx (ns[1].value); - auto& vp (ctx.var_pool.rw ()); // Load phase. - // See if we have the stable program name in the <var-prefix>.name // variable. If its missing, set it to the metadata key (i.e., target // name as imported) by default. // { + // Note: go straight for the public variable pool. + // + auto& vp (ctx.var_pool.rw ()); // Load phase. + value& nv (t.assign (vp.insert (pfx + ".name"))); if (!nv) nv = *meta; @@ -3023,18 +3424,43 @@ namespace build2 fail (loc) << "no metadata for imported target " << t; } - return make_pair (pt, k); + return import_result<target> {pt, move (rns), k}; + } + + path + import_buildfile (scope& bs, name n, bool opt, const location& loc) + { + names r (import (bs, + move (n), + string () /* phase2 */, + opt, + false /* metadata */, + loc).first); + + path p; + if (!r.empty ()) // Optional not found. + { + // Note: see also parse_import(). + // + assert (r.size () == 1); // See import_load() for details. + name& n (r.front ()); + p = n.dir / n.value; // Should already include extension. + } + else + assert (opt); + + return p; } ostream& - operator<< (ostream& o, const pair<const exe*, import_kind>& p) + operator<< (ostream& o, const import_result<exe>& r) { - assert (p.first != nullptr); + assert (r.target != nullptr); - if (p.second == import_kind::normal) - o << *p.first; + if (r.kind == import_kind::normal) + o << *r.target; else - o << p.first->process_path (); + o << r.target->process_path (); return o; } @@ -3074,13 +3500,23 @@ namespace build2 // mkdir (d / std_build_dir, verbosity); + auto diag = [verbosity] (const path& f) + { + if (verb >= verbosity) + { + if (verb >= 2) + text << "cat >" << f; + else if (verb) + print_diag ("save", f); + } + }; + // Write build/bootstrap.build. // { path f (d / std_bootstrap_file); - if (verb >= verbosity) - text << (verb >= 2 ? "cat >" : "save ") << f; + diag (f); try { @@ -3126,8 +3562,7 @@ namespace build2 { path f (d / std_root_file); - if (verb >= verbosity) - text << (verb >= 2 ? "cat >" : "save ") << f; + diag (f); try { @@ -3175,8 +3610,7 @@ namespace build2 { path f (d / std_build_dir / "config.build"); // std_config_file - if (verb >= verbosity) - text << (verb >= 2 ? "cat >" : "save ") << f; + diag (f); try { @@ -3201,8 +3635,7 @@ namespace build2 { path f (d / std_buildfile_file); - if (verb >= verbosity) - text << (verb >= 2 ? "cat >" : "save ") << f; + diag (f); try { |