From fc1e4124a533b7f628dc00d343b9061367634b27 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 28 Apr 2015 16:14:30 +0200 Subject: Second iteration over import/export support --- build/algorithm.cxx | 2 +- build/cxx/rule.cxx | 87 +++++++++++++++++++++++++++++++++----- build/dump.cxx | 32 +++++++------- build/file | 3 +- build/file.cxx | 117 ++++++++++++++++++++++++++++++++++++++++++++++------ build/name | 2 + build/parser | 11 +++++ build/parser.cxx | 56 ++++++++++++++----------- build/target | 6 +-- build/variable | 10 +++++ 10 files changed, 259 insertions(+), 67 deletions(-) (limited to 'build') diff --git a/build/algorithm.cxx b/build/algorithm.cxx index e6a71df..95724cf 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -175,7 +175,7 @@ namespace build { target& pt (search (p)); - if (p.target->dir.sub (d)) + if (pt.dir.sub (d)) { match (a, pt); t.prerequisite_targets.push_back (&pt); diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index ec2a0b9..a23e81e 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -68,6 +68,22 @@ namespace build } } + // Append library options from one of the cxx.export.* variables + // recursively, prerequisite libraries first. + // + static void + append_lib_options (vector& args, target& l, const char* var) + { + for (target* t: l.prerequisite_targets) + { + if (t != nullptr && + (t->is_a () || t->is_a () || t->is_a ())) + append_lib_options (args, *t, var); + } + + append_options (args, l, var); + } + // compile // void* compile:: @@ -121,12 +137,25 @@ namespace build // When cleaning, ignore prerequisites that are not in the same // or a subdirectory of ours. // - switch (a.operation ()) + for (prerequisite& p: group_prerequisites (t)) { - case default_id: - case update_id: search_and_match (a, t); break; - case clean_id: search_and_match (a, t, t.dir); break; - default: assert (false); + target& pt (search (p)); + + if (a.operation () == clean_id && !pt.dir.sub (t.dir)) + continue; + + build::match (a, pt); + + // A dependency on a library is there so that we can get its + // cxx.export.poptions. In particular, making sure it is + // executed before us will only restrict parallelism. But we + // do need to match it in order to get its prerequisite_targets + // populated; see append_lib_options() above. + // + if (pt.is_a () || pt.is_a () || pt.is_a ()) + continue; + + t.prerequisite_targets.push_back (&pt); } // Inject additional prerequisites. For now we only do it for @@ -205,6 +234,16 @@ namespace build vector args {cxx.c_str ()}; + // Add cxx.export.poptions from prerequisite libraries. + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + append_options (args, t, "cxx.poptions"); // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. @@ -369,6 +408,16 @@ namespace build vector args {cxx.c_str ()}; + // Add cxx.export.poptions from prerequisite libraries. + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + append_options (args, t, "cxx.poptions"); append_options (args, t, "cxx.coptions"); @@ -586,6 +635,7 @@ namespace build // We may need the project roots for rule chaining (see below). // We will resolve them lazily only if needed. // + scope* root (nullptr); const dir_path* out_root (nullptr); const dir_path* src_root (nullptr); @@ -673,16 +723,16 @@ namespace build continue; } - if (out_root == nullptr) + if (root == nullptr) { // Which scope shall we use to resolve the root? Unlikely, // but possible, the prerequisite is from a different project // altogether. So we are going to use the target's project. // - scope* rs (t.root_scope ()); - assert (rs != nullptr); // Shouldn't have matched. - out_root = &rs->path (); - src_root = &rs->src_path (); + root = t.root_scope (); + assert (root != nullptr); // Shouldn't have matched. + out_root = &root->path (); + src_root = &root->src_path (); } prerequisite& cp (p); @@ -817,6 +867,23 @@ namespace build // Note: add the source to the group, not the member. // pt->prerequisites.emplace_back (cp); + + // Add our imported lib*{} prerequisites to the object file (see + // cxx.export.poptions above for details). + // + for (prerequisite& p: group_prerequisites (t)) + { + if (p.is_a () || p.is_a () || p.is_a ()) + { + // Check that it is imported, that is its root scope differs + // from ours. + // + if (p.dir.absolute () && // Imported is always absolute. + scopes.find (p.dir).root_scope () != root) + pt->prerequisites.emplace_back (p); + } + } + build::match (a, *ot); } diff --git a/build/dump.cxx b/build/dump.cxx index 3f8eb15..3f3bd93 100644 --- a/build/dump.cxx +++ b/build/dump.cxx @@ -28,31 +28,31 @@ namespace build os << ':'; - // If the target has been matched to a rule, print resolved + for (const prerequisite& p: t.prerequisites) + { + os << ' '; + + // Print it as target if one has been cached. + // + if (p.target != nullptr) + os << *p.target; + else + os << p; + } + + // If the target has been matched to a rule, also print resolved // prerequisite targets. // if (t.recipe (a)) { + bool first (true); for (const target* pt: t.prerequisite_targets) { if (pt == nullptr) // Skipped. continue; - os << ' ' << *pt; - } - } - else - { - for (const prerequisite& p: t.prerequisites) - { - os << ' '; - - // Print it as target if one has been cached. - // - if (p.target != nullptr) - os << *p.target; - else - os << p; + os << (first ? " | " : " ") << *pt; + first = false; } } } diff --git a/build/file b/build/file index 937be1b..4dbe9fe 100644 --- a/build/file +++ b/build/file @@ -7,6 +7,7 @@ #include #include +#include // list_value namespace build { @@ -70,7 +71,7 @@ namespace build void load_root_pre (scope& root); - void + list_value import (scope& base, const name&, const location&); } diff --git a/build/file.cxx b/build/file.cxx index c130663..1e42079 100644 --- a/build/file.cxx +++ b/build/file.cxx @@ -7,12 +7,11 @@ #include #include +#include #include #include #include -#include - using namespace std; namespace build @@ -252,19 +251,85 @@ namespace build source_once (bf, root, root); } - void + list_value import (scope& ibase, const name& n, const location& l) { + tracer trace ("import"); + + // Split the name into the project and target. + // + string project; + name target; + + if (n.dir.empty ()) + { + if (!n.simple ()) + fail << "project name expected before imported target " << n; + + // Note that value can be foo/bar/baz; in this case probably + // means sub-projects? Or only to a certain point, then + // (untyped) target? Looks like I will need to scan anything + // that looks like a directory checking if this is a subproject. + // If not, then that's part of the target. + // + project = n.value; + } + else + { + //@@ This can be a path inside a sub-project. So, eventually, + // we should find the innermost sub-project and load the + // export stub from there (will probably still have to + // resolve root from the top-level project). For now we + // assume the project is always top-level. + // + project = *n.dir.begin (); + + target.dir = n.dir.leaf (dir_path (project)); + target.type = n.type; + target.value = n.value; + } + scope& iroot (*ibase.root_scope ()); // Figure out this project's out_root. // - string var ("config." + n.value); - const dir_path& out_root ( - config::required (iroot, var.c_str (), dir_path ()).first); + dir_path out_root; + string var ("config." + project); - if (out_root.empty ()) - fail (l) << "unable to find out_root for imported " << n << + if (auto v = iroot[var]) + { + if (!v.belongs (*global_scope)) // A value from (some) config.build. + out_root = v.as (); + else + { + // Process the path by making it absolute and normalized. Also, + // for usability's sake, treat a simple name that doesn't end + // with '/' as a directory. + // + const list_value& lv (v.as ()); + + if (lv.size () == 1) + { + const name& n (lv.front ()); + + if (n.directory ()) + out_root = n.dir; + else if (n.simple ()) + out_root = dir_path (n.value); + } + + if (out_root.empty ()) + fail (l) << "invalid " << var << " value " << lv; + + if (out_root.relative ()) + out_root = work / out_root; + + out_root.normalize (); + iroot.assign (var) = out_root; + } + } + else + fail (l) << "unable to find out_root for imported " << project << info << "consider explicitly configuring its out_root via the " << var << " command line variable"; @@ -290,7 +355,7 @@ namespace build root.src_path_ = &p; } else - fail (l) << "unable to determine src_root for imported " << n << + fail (l) << "unable to determine src_root for imported " << project << info << "consider configuring " << out_root; bootstrap_src (root); @@ -311,14 +376,42 @@ namespace build // "Pass" the imported project's roots to the stub. // - ts.assign ("out_root") = out_root; - ts.assign ("src_root") = src_root; + ts.assign ("out_root") = move (out_root); + ts.assign ("src_root") = move (src_root); + + // Also pass the target being imported. + // + { + auto v (ts.assign ("target")); + + if (!target.empty ()) // Otherwise leave NULL. + v = list_value {move (target)}; + } // Load the export stub. Note that it is loaded in the context // of the importing project, not the imported one. The export // stub will normally switch to the imported root scope at some // point. // - source (root.src_path () / path ("build/export.build"), iroot, ts); + path es (root.src_path () / path ("build/export.build")); + ifstream ifs (es.string ()); + if (!ifs.is_open ()) + fail << "unable to open " << es; + + level4 ([&]{trace << "importing " << es;}); + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + parser p; + + try + { + p.parse_buildfile (ifs, es, iroot, ts); + } + catch (const std::ios_base::failure&) + { + fail << "failed to read from " << es; + } + + return p.export_value (); } } diff --git a/build/name b/build/name index b0fd6cb..0af944a 100644 --- a/build/name +++ b/build/name @@ -24,6 +24,8 @@ namespace build // struct name { + name () = default; + explicit name (std::string v): value (std::move (v)) {} diff --git a/build/parser b/build/parser index 6168645..94bf7f3 100644 --- a/build/parser +++ b/build/parser @@ -7,11 +7,13 @@ #include #include +#include // move() #include #include #include #include +#include // list_value #include namespace build @@ -36,6 +38,14 @@ namespace build token parse_variable (lexer&, scope&, std::string name, token_type kind); + list_value + export_value () + { + list_value r (std::move (export_value_)); + export_value_.clear (); // Empty state. + return r; + } + // Recursive descent parser. // private: @@ -123,6 +133,7 @@ namespace build const dir_path* out_root_; const dir_path* src_root_; target* default_target_; + list_value export_value_; token peek_ {token_type::eos, false, 0, 0}; bool peeked_ {false}; diff --git a/build/parser.cxx b/build/parser.cxx index 4275411..443253c 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -605,6 +605,26 @@ namespace build if (src_root_ == nullptr) fail (t) << "import during bootstrap"; + // General import format: + // + // import [=](|/])+ + // + value_proxy val; + token_type at; // Assignment type. + if (tt == type::name) + { + at = peek (); + + if (at == token_type::equal || at == token_type::plus_equal) + { + val.rebind (at == token_type::equal + ? scope_->assign (t.name ()) + : scope_->append (t.name ())); + next (t, tt); // Consume =/+=. + next (t, tt); + } + } + // The rest should be a list of projects and/or targets. Parse // them as names to get variable expansion and directory prefixes. // @@ -615,13 +635,15 @@ namespace build for (name& n: ns) { - // For now we only support project names. - // - if (!n.simple ()) - fail (l) << "project name expected instead of " << n; + list_value r (build::import (*scope_, n, l)); - - build::import (*scope_, n, l); + if (val.defined ()) + { + if (at == token_type::equal) + val = move (r); + else + val += move (r); + } } if (tt == type::newline) @@ -642,26 +664,12 @@ namespace build if (ps == nullptr || ps->path () != scope_->path ()) fail (t) << "export outside export stub"; - // The rest should be a list of variables. Parse them as names - // to get variable expansion. + // The rest is a value. Parse it as names to get variable expansion. // location l (get_location (t, &path_)); - names_type ns (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - - for (name& n: ns) - { - if (!n.simple ()) - fail (l) << "variable name expected instead of " << n; - - const auto& var (variable_pool.find (n.value)); - - if (auto val = scope_->vars[var]) - ps->assign (var) = val; //@@ Move? - else - fail (l) << "undefined exported variable " << var.name; - } + export_value_ = (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); if (tt == type::newline) next (t, tt); diff --git a/build/target b/build/target index 0aa0227..7776a77 100644 --- a/build/target +++ b/build/target @@ -335,15 +335,15 @@ namespace build iterator operator-- (int) {iterator r (*this); return --r;} + reference operator* () const {return *i_;} + pointer operator-> () const {return i_.operator -> ();} + friend bool operator== (const iterator& x, const iterator& y) { return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_; } - reference operator* () const {return *i_;} - pointer operator-> () const {return i_.operator -> ();} - friend bool operator!= (const iterator& x, const iterator& y) {return !(x == y);} diff --git a/build/variable b/build/variable index 59db060..0ccd242 100644 --- a/build/variable +++ b/build/variable @@ -67,6 +67,8 @@ namespace build struct list_value: value, names { public: + using names::names; + list_value () = default; list_value (names d): names (std::move (d)) {} list_value (std::string d) {emplace_back (std::move (d));} @@ -87,6 +89,10 @@ namespace build // value_proxy // // A variable can be undefined, null, or contain some actual value. + // Note that once value_proxy is bound to a value, the only way to + // rebind it to a different value is by using explicit rebind(). In + // particular, assigning one value proxy to another will assing the + // values. // struct variable_map; @@ -148,8 +154,12 @@ namespace build // const variable_map* map; // Variable map to which this value belongs. + value_proxy (): map (nullptr), p (nullptr) {} value_proxy (value_ptr* p, const variable_map* m): map (m), p (p) {} + void + rebind (const value_proxy& x) {map = x.map; p = x.p;} + private: value_ptr* p; }; -- cgit v1.1