From 02d902cb8e5e69b123fcdf170e5eeb9ca5605304 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 15 May 2020 12:11:30 +0200 Subject: Amalgamation cutoff support Now a project that disables amalgamation will not logically "see" an outer project even if it's physically inside its scope. --- libbuild2/config/operation.cxx | 6 +- libbuild2/dump.cxx | 5 ++ libbuild2/file.cxx | 150 +++++++++++++++++++++++++++++------------ libbuild2/file.hxx | 17 +++-- libbuild2/operation.cxx | 2 +- libbuild2/scope.hxx | 26 +++++-- libbuild2/scope.ixx | 56 ++++++++++++++- 7 files changed, 198 insertions(+), 64 deletions(-) (limited to 'libbuild2') diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 37ff1a3..17eb99a 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -202,12 +202,10 @@ namespace build2 if (inherit) { - if (auto l = rs.vars[ctx.var_amalgamation]) + if (const dir_path* a = *rs.root_extra->amalgamation) { - const dir_path& d (cast (l)); - os << endl - << "# Base configuration inherited from " << d << endl + << "# Base configuration inherited from " << *a << endl << "#" << endl; } } diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index e253988..8ee68b7 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -400,6 +400,11 @@ namespace build2 // Nested scopes of which we are an immediate parent. // + // Note that because we use the logical (rather than physical) parent, we + // will be printing the logical scope hierarchy (i.e., a project with + // disabled amalgamation will be printed directly inside the global + // scope). + // for (auto e (p.ctx.scopes.end ()); i != e && i->second.parent_scope () == &p; ) { diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 3e0d1c1..0bcb198 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -494,6 +494,7 @@ namespace build2 root.root_extra.reset ( new scope::root_extra_type { + nullopt /* amalgamation */, a, a ? alt_build_ext : std_build_ext, a ? alt_build_dir : std_build_dir, @@ -567,15 +568,29 @@ namespace build2 } pair - extract_variable (context& ctx, lexer& l, const variable& var) + extract_variable (context& ctx, lexer& l, const variable& var, size_t n) { const path_name& fn (l.name ()); try { token t (l.next ()); - token_type tt; + // Skip the requested number of lines. + // + while (--n != 0) + { + for (; t.type != token_type::eos; t = l.next ()) + { + if (t.type == token_type::newline) + { + t = l.next (); + break; + } + } + } + + token_type tt; if (t.type != token_type::word || t.value != var.name || ((tt = l.next ().type) != token_type::assign && tt != token_type::prepend && @@ -603,22 +618,23 @@ namespace build2 pair extract_variable (context& ctx, - istream& is, - const path& bf, - const variable& var) + istream& is, const path& bf, + const variable& var, size_t n) { path_name in (bf); lexer l (is, in); - return extract_variable (ctx, l, var); + return extract_variable (ctx, l, var, n); } pair - extract_variable (context& ctx, const path& bf, const variable& var) + extract_variable (context& ctx, + const path& bf, + const variable& var, size_t n) { try { ifdstream ifs (bf); - return extract_variable (ctx, ifs, bf, var); + return extract_variable (ctx, ifs, bf, var, n); } catch (const io_error& e) { @@ -715,8 +731,8 @@ namespace build2 auto p (extract_variable (ctx, f, *ctx.var_project)); if (!p.second) - fail << "variable " << ctx.var_project->name << " expected " - << "as a first line in " << f; + fail << "variable " << *ctx.var_project << " expected as a first " + << "line in " << f; name = cast (move (p.first)); } @@ -817,44 +833,68 @@ namespace build2 context& ctx (rs.ctx); - bool r (false); - const dir_path& out_root (rs.out_path ()); const dir_path& src_root (rs.src_path ()); + path bf (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn)); + + if (rs.root_extra == nullptr) { - path f (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn)); + // If nothing so far has indicated the naming, assume standard. + // + if (!altn) + altn = false; - if (rs.root_extra == nullptr) - { - // If nothing so far has indicated the naming, assume standard. - // - if (!altn) - altn = false; + setup_root_extra (rs, altn); + } - setup_root_extra (rs, altn); - } + bool r (true); + if (bf.empty ()) + { + r = false; + } + // 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) + { + // Deal with the empty amalgamation variable (which indicates that + // amalgamating this project is disabled). We go through all this + // trouble of extracting its value manually (and thus requiring its + // assignment, if any, to be the second line in bootstrap.build, after + // project assignment) in order to have the logical amalgamation view + // during bootstrap (note that the bootstrap pre hooks will still see + // physical amalgamation). + // + auto ap (extract_variable (ctx, bf, *ctx.var_amalgamation, 2)); + + if (ap.second && (ap.first.null || ap.first.empty ())) + rs.root_extra->amalgamation = nullptr; - 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 (rs.buildfiles.insert (f).second) - { - parser p (rs.ctx, load_stage::boot); - source (p, rs, rs, f); - } - else - l5 ([&]{trace << "skipping already sourced " << f;}); + parser p (rs.ctx, load_stage::boot); + source (p, rs, rs, bf); + } - r = true; + // Detect and diagnose the case where the amalgamation variable is not + // the second line. + // + if (!ap.second && rs.vars[ctx.var_amalgamation].defined ()) + { + fail << "variable " << *ctx.var_amalgamation << " expected as a " + << "second line in " << bf; } } + else + { + // Here we assume amalgamation has been dealt with. + // + l5 ([&]{trace << "skipping already sourced " << bf;}); + } - // See if we are a part of an amalgamation. There are two key players: the + // Finish dealing with the amalgamation. There are two key players: the // outer root scope which may already be present (i.e., we were loaded as // part of an amalgamation) and the amalgamation variable that may or may // not be set by the user (in bootstrap.build) or by an earlier call to @@ -877,17 +917,20 @@ namespace build2 const dir_path& ad (ars->out_path ()); dir_path rd (ad.relative (out_root)); - // If we already have the amalgamation variable set, verify - // that aroot matches its value. + // If we already have the amalgamation variable set, verify that aroot + // matches its value. // if (!rp.second) { + /* @@ TMP if (!v) { fail << out_root << " cannot be amalgamated" << info << "amalgamated by " << ad; } else + */ + if (v) { const dir_path& vd (cast (v)); @@ -909,10 +952,9 @@ namespace build2 } else if (rp.second) { - // If there is no outer root and the amalgamation variable - // hasn't been set, then we need to check if any of the - // outer directories is a project's out_root. If so, then - // that's our amalgamation. + // If there is no outer root and the amalgamation variable hasn't been + // set, then we need to check if any of the outer directories is a + // project's out_root. If so, then that's our amalgamation. // optional altn; const dir_path& ad (find_out_root (out_root.directory (), altn).first); @@ -923,7 +965,18 @@ namespace build2 l5 ([&]{trace << out_root << " amalgamated as " << rd;}); v = move (rd); } + //@@ else: the value will be NULL and amalgamation will be disabled. + // We could omit setting it in root_extra... But maybe this is + // correct: we don't want to load half of the project as + // amalgamated and the other half as not, would we now? + } + // @@ else if (v): shouldn't we try to bootstrap a project in the + // user-specified directory? Though this case is not + // used outside of some controlled cases (like module + // sidebuilds). + + rs.root_extra->amalgamation = cast_null (v); } // See if we have any subprojects. In a sense, this is the other @@ -1198,6 +1251,8 @@ namespace build2 // Check if we are strongly amalgamated by this outer root scope. // + // Note that we won't end up here if we are not amalgamatable. + // if (root.src_path ().sub (rs.src_path ())) root.strong_ = rs.strong_scope (); // Itself or some outer scope. } @@ -1248,10 +1303,17 @@ namespace build2 rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } + //@@ TODO: what if subproject has amalgamation disabled? Can we have a + // subproject that disables our attempt to amalgamate it (see + // amalgamatable() call below). + // Check if we strongly amalgamated this inner root scope. // - if (rs.src_path ().sub (root.src_path ())) - rs.strong_ = root.strong_scope (); // Itself or some outer scope. + if (rs.amalgamatable ()) + { + if (rs.src_path ().sub (root.src_path ())) + rs.strong_ = root.strong_scope (); // Itself or some outer scope. + } // See if there are more inner roots. // diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx index 8af408e..0123591 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -213,24 +213,27 @@ namespace build2 load_root (scope& root); // Extract the specified variable value from a buildfile. It is expected to - // be the first non-comment line and not to rely on any variable expansion - // other than those from the global scope or any variable overrides. Return - // an indication of whether the variable was found. + // be the n'th non-blank/comment line and not to rely on any variable + // expansions other than those from the global scope or any variable + // overrides. Return in second an indication of whether the variable was + // found. // LIBBUILD2_SYMEXPORT pair - extract_variable (context&, const path&, const variable&); + extract_variable (context&, const path&, const variable&, size_t n = 1); - // As above, but extract from a stream. The name argument is used for + // As above, but extract from a stream. The path argument is used for // diagnostics. // LIBBUILD2_SYMEXPORT pair - extract_variable (context&, istream&, const path& name, const variable&); + extract_variable (context&, + istream&, const path&, + const variable&, size_t = 1); // As above, but extract from a lexer (this could be useful for extracting // from stdin). // LIBBUILD2_SYMEXPORT pair - extract_variable (context&, lexer&, const variable&); + extract_variable (context&, lexer&, const variable&, size_t = 1); // Import has two phases: the first is triggered by the import directive in // the buildfile. It will try to find and load the project. Failed that, it diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx index 5b549a4..d56e416 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -549,7 +549,7 @@ namespace build2 << "url: " << cast_empty (rs[ctx.var_project_url]) << endl << "src_root: " << cast (rs[ctx.var_src_root]) << endl << "out_root: " << cast (rs[ctx.var_out_root]) << endl - << "amalgamation: " << cast_empty (rs[ctx.var_amalgamation]) << endl + << "amalgamation: " << (*rs.root_extra->amalgamation != nullptr ? **rs.root_extra->amalgamation : empty_dir_path) << endl << "subprojects: " << cast_empty (rs[ctx.var_subprojects]) << endl << "operations:"; print_ops (rs.root_extra->operations, ctx.operation_table); cout << endl << "meta-operations:"; print_ops (rs.root_extra->meta_operations, ctx.meta_operation_table); cout << endl; diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index e7c2db7..473dde8 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -46,18 +46,21 @@ namespace build2 const dir_path* src_path_ = nullptr; bool - root () const {return root_ == this;} + root () const; - scope* parent_scope () {return parent_;} - const scope* parent_scope () const {return parent_;} + // Note that the *_scope() functions reaturn "logical" parent/root/etc + // scopes, taking into account the project's var_amalgamation value. + + scope* parent_scope (); + const scope* parent_scope () const; // Root scope of this scope or NULL if this scope is not (yet) in any // (known) project. Note that if the scope itself is root, then this // function return this. To get to the outer root, query the root scope of // the parent. // - scope* root_scope () {return root_;} - const scope* root_scope () const {return root_;} + scope* root_scope (); + const scope* root_scope () const; // Root scope of the outermost "strong" (source-based) amalgamation of // this scope or NULL if this scope is not (yet) in any (known) project. @@ -413,6 +416,11 @@ namespace build2 public: struct root_extra_type { + // This project's amalgamation (var_amalgamation value). Absent means it + // is not yet determined. NULL means amalgamation is disabled. + // + optional amalgamation; + bool altn; // True if using alternative build file/directory naming. // Build file/directory naming scheme used by this project. @@ -506,6 +514,14 @@ namespace build2 scope (context& c, bool global) : ctx (c), vars (c, global), target_vars (c, global) {} + // Return true if this root scope can be amalgamated. + // + bool + amalgamatable () const; + + // Note that these values represent "physical" scoping relationships not + // taking into account the project's var_amalgamation value. + // scope* parent_; scope* root_; scope* strong_ = nullptr; // Only set on root scopes. diff --git a/libbuild2/scope.ixx b/libbuild2/scope.ixx index 9aecd48..a3a417f 100644 --- a/libbuild2/scope.ixx +++ b/libbuild2/scope.ixx @@ -5,9 +5,52 @@ namespace build2 { // scope // + inline bool scope:: + root () const + { + return root_ == this; + } + + inline bool scope:: + amalgamatable () const + { + return (root_extra == nullptr || + !root_extra->amalgamation || + *root_extra->amalgamation != nullptr); + } + + inline scope* scope:: + parent_scope () + { + // If this is a root scope and amalgamation is disabled, "jump" straight + // to the global scope. + // + return root () && !amalgamatable () ? &global_scope () : parent_; + } + + inline const scope* scope:: + parent_scope () const + { + return root () && !amalgamatable () ? &global_scope () : parent_; + } + + inline scope* scope:: + root_scope () + { + return root_; + } + + inline const scope* scope:: + root_scope () const + { + return root_; + } + inline scope* scope:: strong_scope () { + // We naturally assume strong_ is not set for non-amalgamatable projects. + // return root_ != nullptr ? root_->strong_ != nullptr ? root_->strong_ : root_ : nullptr; @@ -26,7 +69,9 @@ namespace build2 { scope* r (root_); if (r != nullptr) - for (; r->parent_->root_ != nullptr; r = r->parent_->root_) ; + for (; + r->amalgamatable () && r->parent_->root_ != nullptr; + r = r->parent_->root_) ; return r; } @@ -35,7 +80,9 @@ namespace build2 { const scope* r (root_); if (r != nullptr) - for (; r->parent_->root_ != nullptr; r = r->parent_->root_) ; + for (; + r->amalgamatable () && r->parent_->root_ != nullptr; + r = r->parent_->root_) ; return r; } @@ -44,9 +91,12 @@ namespace build2 { // Scan the parent root scope chain looking for this scope. // - for (const scope* pr (&r); (pr = pr->parent_->root_) != nullptr; ) + for (const scope* pr (&r); + pr->amalgamatable () && (pr = pr->parent_->root_) != nullptr; ) + { if (pr == this) return true; + } return false; } -- cgit v1.1