diff options
Diffstat (limited to 'libbuild2/config/operation.cxx')
-rw-r--r-- | libbuild2/config/operation.cxx | 522 |
1 files changed, 366 insertions, 156 deletions
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index b9856be..150bf1a 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -42,7 +42,7 @@ namespace build2 ofs << "# Created automatically by the config module." << endl << "#" << endl << "src_root = "; - to_stream (ofs, name (src_root), true /* quote */, '@'); + to_stream (ofs, name (src_root), quote_mode::normal, '@'); ofs << endl; ofs.close (); @@ -61,8 +61,10 @@ namespace build2 path f (src_root / rs.root_extra->out_root_file); - if (verb) - text << (verb >= 2 ? "cat >" : "save ") << f; + if (verb >= 2) + text << "cat >" << f; + else if (verb) + print_diag ("save", f); try { @@ -71,7 +73,7 @@ namespace build2 ofs << "# Created automatically by the config module." << endl << "#" << endl << "out_root = "; - to_stream (ofs, name (out_root), true /* quote */, '@'); + to_stream (ofs, name (out_root), quote_mode::normal, '@'); ofs << endl; ofs.close (); @@ -82,8 +84,6 @@ namespace build2 } } - using project_set = set<const scope*>; // Use pointers to get comparison. - // Return (first) whether an unused/inherited variable should be saved // according to the config.config.persist value and (second) whether the // user should be warned about it. @@ -134,7 +134,8 @@ namespace build2 bool r; if (c.compare (p, 4 , "save") == 0) r = true; else if (c.compare (p, 4 , "drop") == 0) r = false; - else fail << "invalid config.config.persist action '" << c << "'"; + else fail << "invalid config.config.persist action '" << c << "'" + << endf; bool w (false); if ((p += 4) != c.size ()) @@ -159,24 +160,26 @@ namespace build2 // If inherit is false, then don't rely on inheritance from outer scopes. // + // @@ We are modifying the module (marking additional variables as saved) + // and this function can be called from a buildfile (probably only + // during serial execution but still). + // + // We could also be configuring multiple projects (including from + // pkg_configure() in bpkg) but feels like we should be ok since we + // only modify this project's root scope data which should not affect + // any other project. + // + // See also save_environment() for a similar issue. + // void save_config (const scope& rs, ostream& os, const path_name& on, bool inherit, + const module& mod, const project_set& projects) { context& ctx (rs.ctx); - // @@ We are modifying the module (marking additional variables as - // saved) and this function can be called from a buildfile (probably - // only during serial execution but still). - // - module* mod (rs.find_module<module> (module::name)); - - if (mod == nullptr) - fail (on) << "no configuration information available during this " - << "meta-operation"; - names storage; auto info_value = [&storage] (diag_record& dr, const value& v) mutable @@ -186,7 +189,7 @@ namespace build2 if (v) { storage.clear (); - dr << "'" << reverse (v, storage) << "'"; + dr << "'" << reverse (v, storage, true /* reduce */) << "'"; } else dr << "[null]"; @@ -214,9 +217,11 @@ namespace build2 // saved according to config.config.persist potentially warning if the // variable would otherwise be dropped. // + // Note: go straight for the public variable pool. + // auto& vp (ctx.var_pool); - for (auto p (rs.vars.lookup_namespace (*vp.find ("config"))); + for (auto p (rs.vars.lookup_namespace ("config")); p.first != p.second; ++p.first) { @@ -228,26 +233,59 @@ namespace build2 if (size_t n = var->override ()) var = vp.find (string (var->name, 0, n)); + const string& name (var->name); + // Skip special variables. // - if (var->name == "config.booted" || - var->name == "config.loaded" || - var->name == "config.configured" || - var->name.compare (0, 14, "config.config.") == 0) + if (name == "config.booted" || + name == "config.loaded" || + name == "config.configured" || + name.compare (0, 14, "config.config.") == 0) continue; - if (mod->find_variable (*var)) // Saved or unsaved. + if (mod.find_variable (*var)) // Saved or unsaved. continue; + // Skip config.**.develop variables (see parser::parse_config() for + // details). + // + // In a sense, this variable is always "available" but if the + // package does not distinguish between development and consumption, + // then specifying config.*.develop=true should be noop. + // + { + size_t p (name.rfind ('.')); + if (p != 6 && name.compare (p + 1, string::npos, "develop") == 0) + continue; + } + + // A common reason behind an unused config.import.* value is an + // unused dependency. That is, there is depends in manifest but no + // import in buildfile (or import could be conditional in which case + // depends should also be conditional). So let's suggest this + // possibility. Note that the project name may have been sanitized + // to a variable name. Oh, well, better than nothing. + // + auto info_import = [] (diag_record& dr, const string& var) + { + if (var.compare (0, 14, "config.import.") == 0) + { + size_t p (var.find ('.', 14)); + + dr << info << "potentially unused dependency on " + << string (var, 14, p == string::npos ? p : p - 14); + } + }; + const value& v (p.first->second); pair<bool, bool> r (save_config_variable (*var, - mod->persist, + mod.persist, false /* inherited */, true /* unused */)); if (r.first) // save { - mod->save_variable (*var, 0); + const_cast<module&> (mod).save_variable (*var, 0); if (r.second) // warn { @@ -266,6 +304,7 @@ namespace build2 diag_record dr; dr << warn (on) << "saving no longer used variable " << *var; + info_import (dr, var->name); if (verb >= 2) info_value (dr, v); } @@ -276,6 +315,7 @@ namespace build2 { diag_record dr; dr << warn (on) << "dropping no longer used variable " << *var; + info_import (dr, var->name); info_value (dr, v); } } @@ -283,12 +323,23 @@ namespace build2 // Save config variables. // - for (auto p: mod->saved_modules.order) + for (auto p: mod.saved_modules.order) { const string& sname (p.second->first); const saved_variables& svars (p.second->second); - bool first (true); // Separate modules with a blank line. + // Separate modules with a blank line. + // + auto first = [v = true] () mutable + { + if (v) + { + v = false; + return "\n"; + } + return ""; + }; + for (const saved_variable& sv: svars) { if (!sv.flags) // unsaved @@ -308,7 +359,13 @@ namespace build2 // inherited. We might also not have any value at all (see // unconfigured()). // - if (!l.defined () || (l->null && flags & save_null_omitted)) + // Note that we must check for null() before attempting any + // further tests. + // + if (!l.defined () || + (l->null ? flags & save_null_omitted : + l->empty () ? flags & save_empty_omitted : + (flags & save_false_omitted) != 0 && !cast<bool> (*l))) continue; // Handle inherited from outer scope values. @@ -317,90 +374,97 @@ namespace build2 // we save the inherited values regardless of whether they are // used or not. // - if (inherit && !(l.belongs (rs) || l.belongs (ctx.global_scope))) + const value* base (nullptr); + if (inherit) { - // This is presumably an inherited value. But it could also be - // some left-over garbage. For example, an amalgamation could - // have used a module but then dropped it while its config - // values are still lingering in config.build. They are probably - // still valid and we should probably continue using them but we - // definitely want to move them to our config.build since they - // will be dropped from the amalgamation's config.build on the - // next reconfigure. Let's also warn the user just in case, - // unless there is no module and thus we couldn't really check - // (the latter could happen when calling $config.save() during - // other meta-operations, though it passes false for inherit). + // Return true if the specified value can be inherited from. // - // There is also another case that falls under this now that - // overrides are by default amalgamation-wide rather than just - // "project and subprojects": we may be (re-)configuring a - // subproject but the override is now set on the outer project's - // root. - // - bool found (false), checked (true); - const scope* r (&rs); - while ((r = r->parent_scope ()->root_scope ()) != nullptr) + auto find_inherited = [&on, &projects, + &info_value, + &sname, &rs, &var] (const lookup& org, + const lookup& ovr) { - if (l.belongs (*r)) + const lookup& l (ovr); + + // This is presumably an inherited value. But it could also be + // some left-over garbage. For example, an amalgamation could + // have used a module but then dropped it while its config + // values are still lingering in config.build. They are + // probably still valid and we should probably continue using + // them but we definitely want to move them to our + // config.build since they will be dropped from the + // amalgamation's config.build on the next reconfigure. Let's + // also warn the user just in case, unless there is no module + // and thus we couldn't really check (the latter could happen + // when calling $config.save() during other meta-operations, + // though it passes false for inherit). + // + // There is also another case that falls under this now that + // overrides are by default amalgamation-wide rather than just + // "project and subprojects": we may be (re-)configuring a + // subproject but the override is now set on the outer + // project's root. + // + bool found (false), checked (true); + const scope* r (&rs); + while ((r = r->parent_scope ()->root_scope ()) != nullptr) { - // Find the config module (might not be there). - // - if (auto* m = r->find_module<const module> (module::name)) + if (l.belongs (*r)) { - // Find the corresponding saved module. + // Find the config module (might not be there). // - auto i (m->saved_modules.find (sname)); - - if (i != m->saved_modules.end ()) + if (auto* m = r->find_module<const module> (module::name)) { - // Find the variable. + // Find the corresponding saved module. // - const saved_variables& sv (i->second); - found = sv.find (var) != sv.end (); + auto i (m->saved_modules.find (sname)); - // If not marked as saved, check whether overriden via - // config.config.persist. - // - if (!found && m->persist != nullptr) + if (i != m->saved_modules.end ()) { - found = save_config_variable ( - var, - m->persist, - false /* inherited */, - true /* unused */).first; + // Find the variable. + // + const saved_variables& sv (i->second); + found = sv.find (var) != sv.end (); + + // If not marked as saved, check whether overriden via + // config.config.persist. + // + if (!found && m->persist != nullptr) + { + found = save_config_variable ( + var, + m->persist, + false /* inherited */, + true /* unused */).first; + } + + // Handle that other case: if this is an override but + // the outer project itself is not being configured, + // then we need to save this override. + // + // One problem with using the already configured + // project set is that the outer project may be + // configured only after us in which case both + // projects will save the value. But perhaps this is a + // feature, not a bug since this is how project-local + // (%) override behaves. + // + if (found && + org != ovr && + projects.find (r) == projects.end ()) + found = false; } - - // Handle that other case: if this is an override but - // the outer project itself is not being configured, - // then we need to save this override. - // - // One problem with using the already configured project - // set is that the outer project may be configured only - // after us in which case both projects will save the - // value. But perhaps this is a feature, not a bug since - // this is how project-local (%) override behaves. - // - if (found && - org.first != ovr.first && - projects.find (r) == projects.end ()) - found = false; } - } - else - checked = false; + else + checked = false; - break; + break; + } } - } - if (found) - { - // Inherited. - // - continue; - } - else - { + if (found) + return true; + // If this value is not defined in a project's root scope, // then something is broken. // @@ -413,7 +477,7 @@ namespace build2 // our own. One special case where we don't want to warn the // user is if the variable is overriden. // - if (checked && org.first == ovr.first) + if (checked && org == ovr) { diag_record dr; dr << warn (on) << "saving previously inherited variable " @@ -425,6 +489,39 @@ namespace build2 if (verb >= 2) info_value (dr, *l); } + + return false; + }; + + // Inherit as-is. + // + if (!l.belongs (rs) && + !l.belongs (ctx.global_scope) && + find_inherited (org.first, ovr.first)) + continue; + else if (flags & save_base) + { + // See if we can base our value on inherited. + // + if (const scope* ors = rs.parent_scope ()->root_scope ()) + { + pair<lookup, size_t> org (ors->lookup_original (var)); + pair<lookup, size_t> ovr (var.overrides == nullptr + ? org + : ors->lookup_override (var, org)); + const lookup& l (ovr.first); + + // We cannot base anything on an empty value. + // + if (l && !l->empty ()) + { + // @@ It's not clear we want the checks/diagnostics in + // this case. + // + if (find_inherited (org.first, ovr.first)) + base = l.value; + } + } } } @@ -442,44 +539,42 @@ namespace build2 continue; } - // If we got here then we are saving this variable. Handle the - // blank line. - // - if (first) - { - os << endl; - first = false; - } - // Handle the save_default_commented flag. // - if ((org.first.defined () && org.first->extra) && // Default value. - org.first == ovr.first && // Not overriden. + if (org.first.defined () && org.first->extra == 1 && // Default. + org.first == ovr.first && // No override. (flags & save_default_commented) != 0) { - os << '#' << n << " =" << endl; + os << first () << '#' << n << " =" << endl; continue; } - if (v) + if (v.null) { - storage.clear (); - names_view ns (reverse (v, storage)); + os << first () << n << " = [null]" << endl; + continue; + } - os << n; + storage.clear (); + pair<names_view, const char*> p ( + sv.save != nullptr + ? sv.save (v, base, storage) + : make_pair (reverse (v, storage, true /* reduce */), "=")); - if (ns.empty ()) - os << " ="; - else - { - os << " = "; - to_stream (os, ns, true /* quote */, '@'); - } + // Might becomes empty after a custom save function had at it. + // + if (p.first.empty () && (flags & save_empty_omitted)) + continue; + + os << first () << n << ' ' << p.second; - os << endl; + if (!p.first.empty ()) + { + os << ' '; + to_stream (os, p.first, quote_mode::normal, '@'); } - else - os << n << " = [null]" << endl; + + os << endl; } } } @@ -493,6 +588,7 @@ namespace build2 save_config (const scope& rs, const path& f, bool inherit, + const module& mod, const project_set& projects) { path_name fn (f); @@ -500,13 +596,16 @@ namespace build2 if (f.string () == "-") fn.name = "<stdout>"; - if (verb) - text << (verb >= 2 ? "cat >" : "save ") << fn; + if (verb >= 2) + text << "cat >" << fn; + else if (verb) + print_diag ("save", fn); try { ofdstream ofs; - save_config (rs, open_file_or_stdout (fn, ofs), fn, inherit, projects); + save_config ( + rs, open_file_or_stdout (fn, ofs), fn, inherit, mod, projects); ofs.close (); } catch (const io_error& e) @@ -515,10 +614,86 @@ namespace build2 } } + // Update config.config.environment value for a hermetic configuration. + // + // @@ We are modifying the module. See also save_config() for a similar + // issue. + // + static void + save_environment (scope& rs, module& mod) + { + // Here we have two parts: (1) get the list of environment variables we + // need to save and (2) save their values in config.config.environment. + // + // The saved_environment list should by now contain all the project- + // specific environment variables. To that we add builtin defaults and + // then filter the result against config.config.hermetic.environment + // inclusions/exclusions. + // + auto& vars (mod.saved_environment); + + vars.insert ("PATH"); + +#if defined(_WIN32) +#elif defined(__APPLE__) + vars.insert ("DYLD_LIBRARY_PATH"); +#else // Linux, FreeBSD, NetBSD, OpenBSD + vars.insert ("LD_LIBRARY_PATH"); +#endif + + for (const pair<string, optional<bool>>& p: + cast_empty<hermetic_environment> ( + rs["config.config.hermetic.environment"])) + { + if (!p.second || *p.second) + vars.insert (p.first); + else + vars.erase (p.first); + } + + // Get the values. + // + strings vals; + { + // Set the project environment before querying the values. Note that + // the logic in init() makes sure that this is all we need to do to + // handle reload (in which case we should still be using + // config.config.environment from amalgamation, if any). + // + auto_project_env penv (rs); + + for (const string& var: vars) + { + if (optional<string> val = getenv (var)) + { + vals.push_back (var + '=' + *val); + } + else + vals.push_back (var); // Unset. + } + } + + // Note: go straight for the public variable pool. + // + value& v (rs.assign (*rs.ctx.var_pool.find ("config.config.environment"))); + + // Note that setting new config.config.environment value invalidates the + // project's environment (scope::root_extra::environment) which could be + // queried in the post-configuration hook. We could re-initialize it but + // the c.c.e value from amalgamation could be referenced by subprojects. + // So instead it seems easier to just save the old value in the module. + // + if (v) + mod.old_environment = move (v.as<strings> ()); + + v = move (vals); + } + static void configure_project (action a, const scope& rs, const variable* c_s, // config.config.save + const module& mod, project_set& projects) { tracer trace ("configure_project"); @@ -538,7 +713,7 @@ namespace build2 // if (out_root != src_root) { - mkdir_p (out_root / rs.root_extra->build_dir); + mkdir_p (out_root / rs.root_extra->build_dir, 1); mkdir (out_root / rs.root_extra->bootstrap_dir, 2); } @@ -548,6 +723,12 @@ namespace build2 { l5 ([&]{trace << "completely configuring " << out_root;}); + // Save the environment if this configuration is hermetic (see init() + // for the other half of this logic). + // + if (cast_false<bool> (rs["config.config.hermetic"])) + save_environment (const_cast<scope&> (rs), const_cast<module&> (mod)); + // Save src-root.build unless out_root is the same as src. // if (c_s == nullptr && out_root != src_root) @@ -572,12 +753,17 @@ namespace build2 // may end up leaving it in partially configured state. // if (c_s == nullptr) - save_config (rs, config_file (rs), true /* inherit */, projects); + save_config (rs, config_file (rs), true /* inherit */, mod, projects); else { lookup l (rs[*c_s]); if (l && (l.belongs (rs) || l.belongs (ctx.global_scope))) { + const path& f (cast<path> (l)); + + if (f.empty ()) + fail << "empty path in " << *c_s; + // While writing the complete configuration seems like a natural // default, there might be a desire to take inheritance into // account (if, say, we are exporting at multiple levels). One can @@ -585,7 +771,7 @@ namespace build2 // still want to support this mode somehow in the future (it seems // like an override of config.config.persist should do the trick). // - save_config (rs, cast<path> (l), false /* inherit */, projects); + save_config (rs, f, false /* inherit */, mod, projects); } } } @@ -596,11 +782,8 @@ namespace build2 if (c_s == nullptr) { - if (module* m = rs.find_module<module> (module::name)) - { - for (auto hook: m->configure_post_) - hook (a, rs); - } + for (auto hook: mod.configure_post_) + hook (a, rs); } // Configure subprojects that have been loaded. @@ -611,15 +794,19 @@ namespace build2 { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); - const scope& nrs (ctx.scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find_out (out_nroot)); - // @@ Strictly speaking we need to check whether the config module - // was loaded for this subproject. + // Skip this subproject if it is not loaded or doesn't use the + // config module. // - if (nrs.out_path () != out_nroot) // This subproject not loaded. - continue; + if (nrs.out_path () == out_nroot) + { + if (const module* m = nrs.find_module<module> (module::name)) + { + configure_project (a, nrs, c_s, *m, projects); + } + } - configure_project (a, nrs, c_s, projects); } } } @@ -651,7 +838,7 @@ namespace build2 for (auto p: *ps) { dir_path out_nroot (out_root / p.second); - const scope& nrs (ctx.scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find_out (out_nroot)); assert (nrs.out_path () == out_nroot); configure_forward (nrs, projects); @@ -662,11 +849,13 @@ namespace build2 operation_id (*pre) (const values&, meta_operation_id, const location&); static operation_id - configure_operation_pre (const values&, operation_id o) + configure_operation_pre (context&, const values&, operation_id o) { // Don't translate default to update. In our case unspecified // means configure everything. // + // Note: see pkg_configure() in bpkg if changing anything here. + // return o; } @@ -701,8 +890,10 @@ namespace build2 } static void - configure_pre (const values& params, const location& l) + configure_pre (context&, const values& params, const location& l) { + // Note: see pkg_configure() in bpkg if changing anything here. + // forward (params, "configure", l); // Validate. } @@ -722,11 +913,13 @@ namespace build2 // create_bootstrap_inner (rs); - if (rs.out_path () == rs.src_path ()) + if (rs.out_eq_src ()) fail (l) << "forwarding to source directory " << rs.src_path (); } else - load (params, rs, buildfile, out_base, src_base, l); // Normal load. + // Normal load. + // + perform_load (params, rs, buildfile, out_base, src_base, l); } static void @@ -746,7 +939,7 @@ namespace build2 ts.push_back (&rs); } else - search (params, rs, bs, bf, tk, l, ts); // Normal search. + perform_search (params, rs, bs, bf, tk, l, ts); // Normal search. } static void @@ -766,6 +959,8 @@ namespace build2 context& ctx (fwd ? ts[0].as<scope> ().ctx : ts[0].as<target> ().ctx); + // Note: go straight for the public variable pool. + // const variable* c_s (ctx.var_pool.find ("config.config.save")); if (c_s->overrides == nullptr) @@ -820,16 +1015,28 @@ namespace build2 ctx.current_operation (*oif); + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, {}, true /* inner */, location ()); + phase_lock pl (ctx, run_phase::match); - match (action (configure_id, id), t); + match_sync (action (configure_id, id), t); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, {}, true /* inner */); } } - configure_project (a, *rs, c_s, projects); + configure_project (a, + *rs, + c_s, + *rs->find_module<module> (module::name), + projects); } } } + // NOTE: see pkg_configure() in bpkg if changing anything here. + // const meta_operation_info mo_configure { configure_id, "configure", @@ -879,7 +1086,7 @@ namespace build2 { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); - const scope& nrs (ctx.scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find_out (out_nroot)); assert (nrs.out_path () == out_nroot); // See disfigure_load(). r = disfigure_project (a, nrs, projects) || r; @@ -905,7 +1112,7 @@ namespace build2 } } - if (module* m = rs.find_module<module> (module::name)) + if (const module* m = rs.find_module<module> (module::name)) { for (auto hook: m->disfigure_pre_) r = hook (a, rs) || r; @@ -987,7 +1194,7 @@ namespace build2 for (auto p: *ps) { dir_path out_nroot (out_root / p.second); - const scope& nrs (ctx.scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find_out (out_nroot)); assert (nrs.out_path () == out_nroot); r = disfigure_forward (nrs, projects) || r; @@ -1004,13 +1211,13 @@ namespace build2 } static void - disfigure_pre (const values& params, const location& l) + disfigure_pre (context&, const values& params, const location& l) { forward (params, "disfigure", l); // Validate. } static operation_id - disfigure_operation_pre (const values&, operation_id o) + disfigure_operation_pre (context&, const values&, operation_id o) { // Don't translate default to update. In our case unspecified // means disfigure everything. @@ -1128,6 +1335,8 @@ namespace build2 // Add the default config.config.persist value unless there is a custom // one (specified as a command line override). // + // Note: go straight for the public variable pool. + // const variable& var (*ctx.var_pool.find ("config.config.persist")); if (!rs[var].defined ()) @@ -1244,7 +1453,8 @@ namespace build2 string ("config"), /* config_module */ nullopt, /* config_file */ true, /* buildfile */ - "the create meta-operation"); + "the create meta-operation", + 1 /* verbosity */); save_config (ctx, d); } |