diff options
Diffstat (limited to 'libbuild2/config')
-rw-r--r-- | libbuild2/config/init.cxx | 108 | ||||
-rw-r--r-- | libbuild2/config/module.cxx | 2 | ||||
-rw-r--r-- | libbuild2/config/operation.cxx | 264 |
3 files changed, 244 insertions, 130 deletions
diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index 6998017..4e1890a 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -6,6 +6,7 @@ #include <libbuild2/file.hxx> #include <libbuild2/rule.hxx> +#include <libbuild2/lexer.hxx> #include <libbuild2/scope.hxx> #include <libbuild2/context.hxx> #include <libbuild2/filesystem.hxx> // exists() @@ -32,9 +33,18 @@ namespace build2 const string& mname (rs.ctx.current_mname); const string& oname (rs.ctx.current_oname); - // Only create the module if we are configuring or creating. This is a - // bit tricky since the build2 core may not yet know if this is the - // case. But we know. + // While config.import (see below) could theoretically be specified in a + // buildfile, config.export is expected to always be specified as a + // command line override. + // + // Note: must be entered during bootstrap since we need it in + // configure_execute(). + // + vp.insert<path> ("config.export", true /* ovr */); + + // Only create the module if we are configuring or creating or if it was + // forced with config.module (useful if we need to call $config.export() + // during other meta-operations). // if (( mname == "configure" || mname == "create") || (mname.empty () && (oname == "configure" || oname == "create"))) @@ -80,42 +90,80 @@ namespace build2 assert (config_hints.empty ()); // We don't known any hints. + // Note that the config.<name>* variables belong to the module <name>. + // So the only "special" variables we can allocate in config.* are + // config.config.*, names that have been "gifted" to us by other modules + // (like config.version) as well as names that we have reserved to not + // be valid module names (build, import, export). + // auto& vp (rs.ctx.var_pool.rw (rs)); - // Load config.build if one exists (we don't need to worry about - // disfigure since we will never be init'ed). + auto& c_v (vp.insert<uint64_t> ("config.version", false /*ovr*/)); + auto& c_i (vp.insert<paths> ("config.import", true /* ovr */)); + + // Load config.build if one exists followed by extra files specified in + // config.import (we don't need to worry about disfigure since we will + // never be init'ed). // - const variable& c_v (vp.insert<uint64_t> ("config.version", false)); + auto load_config = [&rs, &c_v] (const path& f, const location& l) + { + // Check the config version. We assume that old versions cannot + // understand new configs and new versions are incompatible with old + // configs. + // + // We extract the value manually instead of loading and then checking + // in order to be able to fixup/migrate the file which we may want to + // do in the future. + // + + // This is tricky for stdin since we cannot reopen it (or put more + // than one character back). So what we are going to do is continue + // reading after extracting the variable. One side effect of this is + // that we won't have the config.version variable entered in the scope + // but that is harmless (we could do it manually if necessary). + // + ifdstream ifs; + lexer lex (open_file_or_stdin (f, ifs), f); + + // Assume missing version is 0. + // + auto p (extract_variable (rs.ctx, lex, c_v)); + uint64_t v (p.second ? cast<uint64_t> (p.first) : 0); + + if (v != module::version) + fail (l) << "incompatible config file " << f << + info << "config file version " << v + << (p.second ? "" : " (missing)") << + info << "config module version " << module::version << + info << "consider reconfiguring " << project (rs) << '@' + << rs.out_path (); + + source (rs, rs, lex); + }; { path f (config_file (rs)); if (exists (f)) + load_config (f, l); + } + + if (lookup l = rs[c_i]) + { + // Only load files that were specified on our root scope as well as + // global overrides. This way we can use our override "positioning" + // machinery (i.e., where the override applies) to decide where the + // extra config is loaded. The resulting semantics feels quite natural + // and consistent with command line variable overrides: + // + // b config.import=.../config.build # outermost amalgamation + // b ./config.import=.../config.build # this project + // b !config.import=.../config.build # every project + // + if (l.belongs (rs) || l.belongs (rs.ctx.global_scope)) { - // Check the config version. We assume that old versions cannot - // understand new configs and new versions are incompatible with old - // configs. - // - // We extract the value manually instead of loading and then - // checking in order to be able to fixup/migrate the file which we - // may want to do in the future. - // - { - // Assume missing version is 0. - // - auto p (extract_variable (rs.ctx, f, c_v)); - uint64_t v (p.second ? cast<uint64_t> (p.first) : 0); - - if (v != module::version) - fail (l) << "incompatible config file " << f << - info << "config file version " << v - << (p.second ? "" : " (missing)") << - info << "config module version " << module::version << - info << "consider reconfiguring " << project (rs) << '@' - << out_root; - } - - source (rs, rs, f); + for (const path& f: cast<paths> (l)) + load_config (f, location (&f)); } } diff --git a/libbuild2/config/module.cxx b/libbuild2/config/module.cxx index 7e9b765..b43f1d9 100644 --- a/libbuild2/config/module.cxx +++ b/libbuild2/config/module.cxx @@ -30,7 +30,7 @@ namespace build2 i = sm.insert (string (n, 0, n.find ('.', 7))); } - // Don't insert duplicates. The config.import vars are particularly + // Don't insert duplicates. The config.import.* vars are particularly // susceptible to duplication. // saved_variables& sv (i->second); diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 535018e..9050645 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -87,35 +87,45 @@ namespace build2 using project_set = set<const scope*>; // Use pointers to get comparison. + // If inherit is false, then don't rely on inheritance from outer scopes + // (used for config.export). + // static void - save_config (const scope& rs, const project_set& projects) + save_config (const scope& rs, + const path& f, + bool inherit, + const project_set& projects) { context& ctx (rs.ctx); - path f (config_file (rs)); + const module& mod (*rs.lookup_module<const module> (module::name)); - if (verb) - text << (verb >= 2 ? "cat >" : "save ") << f; + const string& df (f.string () != "-" ? f.string () : "<stdout>"); - const module& mod (*rs.lookup_module<const module> (module::name)); + if (verb) + text << (verb >= 2 ? "cat >" : "save ") << df; try { - ofdstream ofs (f); + ofdstream ofs; + ostream& os (open_file_or_stdout (f, ofs)); - ofs << "# Created automatically by the config module, but feel " << + os << "# Created automatically by the config module, but feel " << "free to edit." << endl - << "#" << endl; + << "#" << endl; - ofs << "config.version = " << module::version << endl; + os << "config.version = " << module::version << endl; - if (auto l = rs.vars[ctx.var_amalgamation]) + if (inherit) { - const dir_path& d (cast<dir_path> (l)); + if (auto l = rs.vars[ctx.var_amalgamation]) + { + const dir_path& d (cast<dir_path> (l)); - ofs << endl - << "# Base configuration inherited from " << d << endl - << "#" << endl; + os << endl + << "# Base configuration inherited from " << d << endl + << "#" << endl; + } } // Save config variables. @@ -146,6 +156,11 @@ namespace build2 if (!l.defined ()) continue; + // Handle inherited from outer scope values. + // + // Note that we keep this logic (with warnings and all) even if + // inherit is false to make things easier to reason about. + // if (!(l.belongs (rs) || l.belongs (ctx.global_scope))) { // This is presumably an inherited value. But it could also be @@ -205,42 +220,50 @@ namespace build2 } } - if (found) // Inherited. - continue; - - location loc (&f); - - // If this value is not defined in a project's root scope, then - // something is broken. - // - if (r == nullptr) - fail (loc) << "inherited variable " << var << " value " - << "is not from a root scope"; - - // If none of the outer project's configurations use this value, - // then we warn and save as our own. One special case where we - // don't want to warn the user is if the variable is overriden. - // - if (org.first == ovr.first) + if (found) { - diag_record dr; - dr << warn (loc) << "saving previously inherited variable " - << var; - - dr << info (loc) << "because project " << *r - << " no longer uses it in its configuration"; - - if (verb >= 2) + // Inherited. + // + if (inherit) + continue; + } + else + { + location loc (&f); + + // If this value is not defined in a project's root scope, + // then something is broken. + // + if (r == nullptr) + fail (loc) << "inherited variable " << var << " value " + << "is not from a root scope"; + + // If none of the outer project's configurations use this + // value, then we warn and save as our own. One special case + // where we don't want to warn the user is if the variable is + // overriden. + // + if (org.first == ovr.first) { - dr << info (loc) << "variable value: "; + diag_record dr; + dr << warn (loc) << "saving previously inherited variable " + << var; - if (*l) + dr << info (loc) << "because project " << *r + << " no longer uses it in its configuration"; + + if (verb >= 2) { - storage.clear (); - dr << "'" << reverse (*l, storage) << "'"; + dr << info (loc) << "variable value: "; + + if (*l) + { + storage.clear (); + dr << "'" << reverse (*l, storage) << "'"; + } + else + dr << "[null]"; } - else - dr << "[null]"; } } } @@ -264,7 +287,7 @@ namespace build2 // if (first) { - ofs << endl; + os << endl; first = false; } @@ -274,7 +297,7 @@ namespace build2 org.first == ovr.first && // Not overriden. (sv.flags & save_commented) == save_commented) { - ofs << '#' << n << " =" << endl; + os << '#' << n << " =" << endl; continue; } @@ -283,20 +306,20 @@ namespace build2 storage.clear (); names_view ns (reverse (v, storage)); - ofs << n; + os << n; if (ns.empty ()) - ofs << " ="; + os << " ="; else { - ofs << " = "; - to_stream (ofs, ns, true, '@'); // Quote. + os << " = "; + to_stream (os, ns, true, '@'); // Quote. } - ofs << endl; + os << endl; } else - ofs << n << " = [null]" << endl; + os << n << " = [null]" << endl; } } @@ -304,12 +327,15 @@ namespace build2 } catch (const io_error& e) { - fail << "unable to write " << f << ": " << e; + fail << "unable to write " << df << ": " << e; } } static void - configure_project (action a, const scope& rs, project_set& projects) + configure_project (action a, + const scope& rs, + const variable* c_e, // config.export + project_set& projects) { tracer trace ("configure_project"); @@ -332,8 +358,7 @@ namespace build2 mkdir (out_root / rs.root_extra->bootstrap_dir, 2); } - // We distinguish between a complete configure and operation- - // specific. + // We distinguish between a complete configure and operation-specific. // if (a.operation () == default_id) { @@ -341,15 +366,48 @@ namespace build2 // Save src-root.build unless out_root is the same as src. // - if (out_root != src_root) + if (c_e == nullptr && out_root != src_root) save_src_root (rs); - // Save config.build. + // Save config.build unless an alternative is specified with + // config.export. Similar to config.import we will only save to that + // file if it is specified on our root scope or as a global override + // (the latter is a bit iffy but let's allow it, for example, to dump + // everything to stdout). Note that to save a subproject's config we + // will have to use a scope-specific override (since the default will + // apply to the amalgamation): // - save_config (rs, projects); + // b configure: subproj/ subproj/config.export=.../config.build + // + // Could be confusing but then normally it will be the amalgamation + // whose configuration we want to export. + // + // Note also that if config.export is specified we do not rewrite + // config.build files (say, of subprojects) as well as src-root.build + // above. Failed that, if we are running in a disfigured project, we + // may end up leaving it in partially configured state. + // + if (c_e == nullptr) + save_config (rs, config_file (rs), true /* inherit */, projects); + else + { + lookup l (rs[*c_e]); + if (l && (l.belongs (rs) || l.belongs (ctx.global_scope))) + { + // 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 + // of course just copy the relevant config.build files, but we may + // still want to support this mode somehow in the future (maybe + // using `+` as a modifier, say config.export=+.../config.build). + // + save_config (rs, cast<path> (l), false /* inherit */, projects); + } + } } else { + fail << "operation-specific configuration not yet supported"; } // Configure subprojects that have been loaded. @@ -368,7 +426,7 @@ namespace build2 if (nrs.out_path () != out_nroot) // This subproject not loaded. continue; - configure_project (a, nrs, projects); + configure_project (a, nrs, c_e, projects); } } } @@ -513,6 +571,15 @@ namespace build2 { bool fwd (forward (params)); + context& ctx (fwd ? ts[0].as<scope> ().ctx : ts[0].as<target> ().ctx); + + const variable* c_e (ctx.var_pool.find ("config.export")); + + if (c_e->overrides == nullptr) + c_e = nullptr; + else if (fwd) + fail << "config.export specified for forward configuration"; + project_set projects; for (const action_target& at: ts) @@ -521,53 +588,52 @@ namespace build2 { // Forward configuration. // - const scope& rs (*static_cast<const scope*> (at.target)); + const scope& rs (at.as<scope> ()); configure_forward (rs, projects); - continue; } + else + { + // Normal configuration. + // + // Match rules to configure every operation supported by each + // project. Note that we are not calling operation_pre/post() + // callbacks here since the meta operation is configure and we know + // what we are doing. + // + // Note that we cannot do this in parallel. We cannot parallelize + // the outer loop because we should match for a single action at a + // time. And we cannot swap the loops because the list of operations + // is target-specific. However, inside match(), things can proceed + // in parallel. + // + const target& t (at.as<target> ()); + const scope* rs (t.base_scope ().root_scope ()); - // Normal configuration. - // - // Match rules to configure every operation supported by each project. - // Note that we are not calling operation_pre/post() callbacks here - // since the meta operation is configure and we know what we are - // doing. - // - // Note that we cannot do this in parallel. We cannot parallelize the - // outer loop because we should match for a single action at a time. - // And we cannot swap the loops because the list of operations is - // target-specific. However, inside match(), things can proceed in - // parallel. - // - const target& t (at.as_target ()); - const scope* rs (t.base_scope ().root_scope ()); - - if (rs == nullptr) - fail << "out of project target " << t; - - context& ctx (t.ctx); + if (rs == nullptr) + fail << "out of project target " << t; - const operations& ops (rs->root_extra->operations); + const operations& ops (rs->root_extra->operations); - for (operation_id id (default_id + 1); // Skip default_id. - id < ops.size (); - ++id) - { - if (const operation_info* oif = ops[id]) + for (operation_id id (default_id + 1); // Skip default_id. + id < ops.size (); + ++id) { - // Skip aliases (e.g., update-for-install). - // - if (oif->id != id) - continue; + if (const operation_info* oif = ops[id]) + { + // Skip aliases (e.g., update-for-install). + // + if (oif->id != id) + continue; - ctx.current_operation (*oif); + ctx.current_operation (*oif); - phase_lock pl (ctx, run_phase::match); - match (action (configure_id, id), t); + phase_lock pl (ctx, run_phase::match); + match (action (configure_id, id), t); + } } - } - configure_project (a, *rs, projects); + configure_project (a, *rs, c_e, projects); + } } } @@ -802,7 +868,7 @@ namespace build2 // for (const action_target& at: ts) { - const scope& rs (*static_cast<const scope*> (at.target)); + const scope& rs (at.as<scope> ()); if (!(fwd ? disfigure_forward ( rs, projects) |