From 655121741560d62c1ae82c13a9d2aad18f130603 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 16 Nov 2018 14:00:07 +0200 Subject: Implement support for dependency chains Now instead of: ./: exe{foo} exe{foo}: cxx{*} We can write: ./: exe{foo}: cxx{*} Or even: ./: exe{foo}: libue{foo}: cxx{*} This can be combined with prerequisite-specific variables (which naturally only apply to the last set of prerequisites in the chain): ./: exe{foo}: libue{foo}: bin.whole = false --- build2/parser.cxx | 303 +++++++++++++----------- build2/parser.hxx | 9 +- tests/dependency/chain/buildfile | 5 + tests/dependency/chain/testscript | 39 +++ tests/variable/prerequisite-specific/testscript | 13 +- 5 files changed, 227 insertions(+), 142 deletions(-) create mode 100644 tests/dependency/chain/buildfile create mode 100644 tests/dependency/chain/testscript diff --git a/build2/parser.cxx b/build2/parser.cxx index 5348fbe..1658623 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -509,8 +509,9 @@ namespace build2 // prerequisites. Fall through. } - // Dependency declaration (including prerequisite-specific variable - // assignment) or target-specific variable assignment. + // Target-specific variable assignment or dependency declaration, + // including a dependency chain and/or prerequisite-specific variable + // assignment. // if (at.first) @@ -704,8 +705,8 @@ namespace build2 rg.play (); // Replay. } } - // Dependency declaration potentially followed by prerequisite- - // specific variable assignment). + // Dependency declaration potentially followed by a chain and/or + // a prerequisite-specific variable assignment. // else { @@ -714,138 +715,7 @@ namespace build2 else attributes_pop (); - // First enter all the targets (normally we will have just one). - // - small_vector, 1> tgs; - - for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i) - { - name& n (*i); - - if (n.qualified ()) - fail (nloc) << "project name in target " << n; - - name o (n.pair ? move (*++i) : name ()); - enter_target tg (*this, move (n), move (o), false, nloc, trace); - - if (default_target_ == nullptr) - default_target_ = target_; - - target_->prerequisites_state_.store (2, memory_order_relaxed); - target_->prerequisites_.reserve (pns.size ()); - tgs.push_back (*target_); - } - - // Now enter each prerequisite into each target. - // - for (auto& pn: pns) - { - auto rp (scope_->find_target_type (pn, ploc)); - const target_type* tt (rp.first); - optional& e (rp.second); - - if (tt == nullptr) - fail (ploc) << "unknown target type " << pn.type; - - // Current dir collapses to an empty one. - // - if (!pn.dir.empty ()) - pn.dir.normalize (false, true); - - // @@ OUT: for now we assume the prerequisite's out is - // undetermined. The only way to specify an src prerequisite - // will be with the explicit @-syntax. - // - // Perhaps use @file{foo} as a way to specify it is in the out - // tree, e.g., to suppress any src searches? The issue is what - // to use for such a special indicator. Also, one can easily and - // natually suppress any searches by specifying the absolute - // path. - // - prerequisite p (pn.proj, - *tt, - move (pn.dir), - dir_path (), - move (pn.value), - move (e), - *scope_); - - for (auto i (tgs.begin ()), e (tgs.end ()); i != e; ) - { - // Move last prerequisite (which will normally be the only - // one). - // - target& t (*i); - t.prerequisites_.push_back ( - ++i == e - ? move (p) - : prerequisite (p, memory_order_relaxed)); // Serial - } - } - - // Do we have prerequisite-specific variable assignment? - // - if (tt == type::colon) - { - // What should we do if there are no prerequisites (for example, - // because of an empty wildcard result)? We can fail or we can - // ignore. In most cases, however, this is probably an error - // (for example, forgetting to checkout a git submodule) so - // let's not confuse the user and fail (one can always handle - // the optional prerequisites case with a variable and an if). - // - if (pns.empty ()) - fail (ploc) << "no prerequisites in prerequisite-specific " - << "variable assignment"; - - // Set the variable in the last pns.size() prerequisites of each - // target. This code is similar to target-specific case above. - // - // @@ TODO prerequisite block (also target block above). - // - next (t, tt); - attributes_push (t, tt); - - // @@ PAT: currently we pattern-expand prerequisite-specific - // vars. - // - const location vloc (get_location (t)); - names vns (parse_names (t, tt, pattern_mode::expand)); - - if (tt != type::assign && - tt != type::prepend && - tt != type::append) - fail (t) << "expected assignment instead of " << t; - - type at (tt); - - const variable& var (parse_variable_name (move (vns), vloc)); - apply_variable_attributes (var); - - // We handle multiple targets and/or prerequisites by replaying - // the tokens (see the target-specific case above for details). - // - replay_guard rg (*this, tgs.size () > 1 || pns.size () > 1); - - for (auto ti (tgs.begin ()), te (tgs.end ()); ti != te; ) - { - target& tg (*ti); - enter_target tgg (*this, tg); - - for (size_t pn (tg.prerequisites_.size ()), - pi (pn - pns.size ()); pi != pn; ) - { - enter_prerequisite pg (*this, tg.prerequisites_[pi]); - parse_variable (t, tt, var, at); - - if (++pi != pn) - rg.play (); // Replay. - } - - if (++ti != te) - rg.play (); // Replay. - } - } + parse_dependency (t, tt, move (ns), nloc, move (pns), ploc); } if (tt == type::newline) @@ -998,6 +868,167 @@ namespace build2 } void parser:: + parse_dependency (token& t, token_type& tt, + names&& tns, const location& tloc, // Target names. + names&& pns, const location& ploc) // Prereq names. + { + tracer trace ("parser::parse_dependency", &path_); + + // First enter all the targets (normally we will have just one). + // + small_vector, 1> tgs; + + for (auto i (tns.begin ()), e (tns.end ()); i != e; ++i) + { + name& n (*i); + + if (n.qualified ()) + fail (tloc) << "project name in target " << n; + + name o (n.pair ? move (*++i) : name ()); + enter_target tg (*this, move (n), move (o), false, tloc, trace); + + if (default_target_ == nullptr) + default_target_ = target_; + + target_->prerequisites_state_.store (2, memory_order_relaxed); + target_->prerequisites_.reserve (pns.size ()); + tgs.push_back (*target_); + } + + // Now enter each prerequisite into each target. + // + for (auto& pn: pns) + { + auto rp (scope_->find_target_type (pn, ploc)); + const target_type* tt (rp.first); + optional& e (rp.second); + + if (tt == nullptr) + fail (ploc) << "unknown target type " << pn.type; + + // Current dir collapses to an empty one. + // + if (!pn.dir.empty ()) + pn.dir.normalize (false, true); + + // @@ OUT: for now we assume the prerequisite's out is undetermined. The + // only way to specify an src prerequisite will be with the explicit + // @-syntax. + // + // Perhaps use @file{foo} as a way to specify it is in the out tree, + // e.g., to suppress any src searches? The issue is what to use for such + // a special indicator. Also, one can easily and natually suppress any + // searches by specifying the absolute path. + // + // Note: we cannot move values out of pn since we may need to pass them + // as targets in case of a chain (see below). + // + prerequisite p (pn.proj, + *tt, + pn.dir, + dir_path (), + pn.value, + move (e), + *scope_); + + for (auto i (tgs.begin ()), e (tgs.end ()); i != e; ) + { + // Move last prerequisite (which will normally be the only one). + // + target& t (*i); + t.prerequisites_.push_back (++i == e + ? move (p) + : prerequisite (p, memory_order_relaxed)); + } + } + + // Do we have a dependency chain and/or prerequisite-specific variable + // assignment? + // + if (tt != type::colon) + return; + + // What should we do if there are no prerequisites (for example, because + // of an empty wildcard result)? We can fail or we can ignore. In most + // cases, however, this is probably an error (for example, forgetting to + // checkout a git submodule) so let's not confuse the user and fail (one + // can always handle the optional prerequisites case with a variable and + // an if). + // + if (pns.empty ()) + fail (ploc) << "no prerequisites in dependency chain or prerequisite-" + << "specific variable assignment"; + + next (t, tt); + auto at (attributes_push (t, tt)); + + // @@ PAT: currently we pattern-expand prerequisite-specific vars. + // + const location loc (get_location (t)); + names ns (tt != type::newline && tt != type::eos + ? parse_names (t, tt, pattern_mode::expand) + : names ()); + + // Prerequisite-specific variable assignment. + // + if (tt == type::assign || tt == type::prepend || tt == type::append) + { + // Set the variable in the last pns.size() prerequisites of each target. + // This code is similar to the target-specific variable case. + // + // @@ TODO prerequisite block (also target block above). + // + type at (tt); + + const variable& var (parse_variable_name (move (ns), loc)); + apply_variable_attributes (var); + + // We handle multiple targets and/or prerequisites by replaying the + // tokens (see the target-specific case above for details). + // + replay_guard rg (*this, tgs.size () > 1 || pns.size () > 1); + + for (auto ti (tgs.begin ()), te (tgs.end ()); ti != te; ) + { + target& tg (*ti); + enter_target tgg (*this, tg); + + for (size_t pn (tg.prerequisites_.size ()), pi (pn - pns.size ()); + pi != pn; ) + { + enter_prerequisite pg (*this, tg.prerequisites_[pi]); + parse_variable (t, tt, var, at); + + if (++pi != pn) + rg.play (); // Replay. + } + + if (++ti != te) + rg.play (); // Replay. + } + } + // + // Dependency chain. + // + else + { + if (at.first) + fail (at.second) << "attributes before prerequisites"; + else + attributes_pop (); + + // Note that we could have "pre-resolved" these prerequisites to actual + // targets or, at least, made they directories absolute. We don't do it + // for easy of documentation: with the current semantics we can just say + // that the dependency chain is equivalent to specifying each dependency + // separately. + // + parse_dependency (t, tt, move (pns), ploc, move (ns), loc); + } + } + + void parser:: source (istream& is, const path& p, const location& loc, diff --git a/build2/parser.hxx b/build2/parser.hxx index dfb4c6b..582f925 100644 --- a/build2/parser.hxx +++ b/build2/parser.hxx @@ -67,15 +67,20 @@ namespace build2 // If one is true then parse a single (logical) line (logical means it // can actually be several lines, e.g., an if-block). Return false if - // nothing has been parsed (i.e., we are on the same token). + // nothing has been parsed (i.e., we are still on the same token). // - // Note that after this function returns, the token is the first token on + // Note that after this function returns, the token is the first token of // the next line (or eos). // bool parse_clause (token&, token_type&, bool one = false); void + parse_dependency (token&, token_type&, + names&&, const location&, + names&&, const location&); + + void parse_assert (token&, token_type&); void diff --git a/tests/dependency/chain/buildfile b/tests/dependency/chain/buildfile new file mode 100644 index 0000000..a27681e --- /dev/null +++ b/tests/dependency/chain/buildfile @@ -0,0 +1,5 @@ +# file : tests/dependency/chain/buildfile +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +./: testscript $b diff --git a/tests/dependency/chain/testscript b/tests/dependency/chain/testscript new file mode 100644 index 0000000..09ea4a6 --- /dev/null +++ b/tests/dependency/chain/testscript @@ -0,0 +1,39 @@ +# file : tests/dependency/chain/testscript +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include ../../common.testscript + +: basic +: +$* <>/~%EOE% +./: dir{x}: dir{a} +dump dir{x} +EOI +:2:1: dump: +% .+/dir\{x/\}: .+:dir\{a/\}% +EOE + +: long +: +$* <>/~%EOE% +./: dir{x}: dir{y}: dir{a} +dump dir{x} dir{y} +EOI +:2:1: dump: +% .+/dir\{x/\}: .+:dir\{y/\}% + +% .+/dir\{y/\}: .+:dir\{a/\}% +EOE + +: multiple +: +$* <>/~%EOE% +./: dir{x} dir{y}: dir{a} dir{b} +dump dir{x} dir{y} +EOI +:2:1: dump: +% .+/dir\{x/\}: .+:dir\{a/\} .+:dir\{b/\}% + +% .+/dir\{y/\}: .+:dir\{a/\} .+:dir\{b/\}% +EOE diff --git a/tests/variable/prerequisite-specific/testscript b/tests/variable/prerequisite-specific/testscript index 1c7e7bd..ef14cfc 100644 --- a/tests/variable/prerequisite-specific/testscript +++ b/tests/variable/prerequisite-specific/testscript @@ -62,10 +62,15 @@ EOI } EOE -: expect-assignment +: chain : -$* <>EOE != 0 -dir{x}: dir{a}: +$* <>/~%EOE% +dir{x}: dir{y}: dir{a}: foo = FOO +dump dir{y} EOI -:1:16: error: expected name instead of +:2:1: dump: +% .+/dir\{y/\}: .+:dir\{a/\}:% + { + foo = FOO + } EOE -- cgit v1.1