From 534f9d8db025d58c9ce23f3b81a37e8c34386a27 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 13 Apr 2015 10:29:25 +0200 Subject: Add initial import support --- build/b.cxx | 54 +------------ build/diagnostics | 10 +-- build/dump.cxx | 55 ++++++------- build/file | 32 +++++++- build/file.cxx | 169 +++++++++++++++++++++++++++++++++++++++- build/name | 10 +++ build/operation.cxx | 2 +- build/parser | 17 +++-- build/parser.cxx | 216 ++++++++++++++++++++++++++++++++-------------------- build/path-io | 22 ++++++ build/scope | 16 ++++ 11 files changed, 425 insertions(+), 178 deletions(-) create mode 100644 build/path-io diff --git a/build/b.cxx b/build/b.cxx index 49fb027..8d2ca47 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -43,19 +43,6 @@ using namespace std; namespace build { - inline bool - is_src_root (const path& d) - { - return file_exists (d / path ("build/bootstrap.build")) || - file_exists (d / path ("build/root.build")); - } - - inline bool - is_out_root (const path& d) - { - return file_exists (d / path ("build/bootstrap/src-root.build")); - } - // Given an src_base directory, look for the project's src_root // based on the presence of known special files. Return empty // path if not found. @@ -88,41 +75,6 @@ namespace build src = false; return path (); } - - // Create and bootstrap outer root scopes, if any. Loading is - // done by root_pre(). - // - static void - create_outer_roots (scope& root) - { - auto v (root.ro_variables ()["amalgamation"]); - - if (!v) - return; - - const path& d (v.as ()); - path out_root (root.path () / d); - path src_root (root.src_path () / d); - out_root.normalize (); - src_root.normalize (); - - scope& rs (create_root (out_root, src_root)); - - bootstrap_out (rs); - - // Check if the bootstrap process changed src_root. - // - const path& p (rs.variables["src_root"].as ()); - - if (src_root != p) - fail << "bootstrapped src_root " << p << " does not match " - << "amalgamated " << src_root; - - rs.src_path_ = &p; - - bootstrap_src (rs); - create_outer_roots (rs); - } } #include @@ -588,10 +540,10 @@ main (int argc, char* argv[]) } // Create and bootstrap outer roots if any. Loading is done - // by root_pre() (that would normally be called by the meta- - // operation's load() callback below). + // by load_root_pre() (that would normally be called by the + // meta-operation's load() callback below). // - create_outer_roots (rs); + create_bootstrap_outer (rs); // The src bootstrap should have loaded all the modules that // may add new meta/operations. So at this stage they should diff --git a/build/diagnostics b/build/diagnostics index 64a0107..3ec80a3 100644 --- a/build/diagnostics +++ b/build/diagnostics @@ -14,7 +14,7 @@ #include #include -#include +#include // included at the end. namespace build { @@ -30,12 +30,6 @@ namespace build std::string diag_relative (const path&); - inline std::ostream& - operator<< (std::ostream& os, const path& p) - { - return os << diag_relative (p); - } - // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}", // and "updating exe{foo} already configured". // @@ -363,4 +357,6 @@ namespace build extern const fail_mark fail; } +#include + #endif // BUILD_DIAGNOSTICS diff --git a/build/dump.cxx b/build/dump.cxx index 6c1cdcf..05ec679 100644 --- a/build/dump.cxx +++ b/build/dump.cxx @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -20,29 +19,30 @@ using namespace std; namespace build { static void - dump_target (const target& t) + dump_target (ostream& os, const target& t) { - cerr << t << ':'; + os << t << ':'; for (const prerequisite& p: t.prerequisites) { - cerr << ' ' << p; + os << ' ' << p; } } static void - dump_scope (scope& p, + dump_scope (ostream& os, + scope& p, scope_map::iterator& i, string& ind, set& rts) { - string d (diag_relative (p.path ())); + string d (relative (p.path ()).string ()); if (d.back () != path::traits::directory_separator) d += '/'; - cerr << ind << d << ":" << endl - << ind << '{'; + os << ind << d << ":" << endl + << ind << '{'; const path* orb (relative_base); relative_base = &p.path (); @@ -58,16 +58,16 @@ namespace build const variable& var (e.first); const value_ptr& val (e.second); - cerr << endl - << ind << var.name << " = "; + os << endl + << ind << var.name << " = "; if (val == nullptr) - cerr << "[null]"; + os << "[null]"; else { //@@ TODO: assuming it is a list. // - cerr << dynamic_cast (*val); + os << dynamic_cast (*val); } vb = true; @@ -79,16 +79,16 @@ namespace build { if (vb) { - cerr << endl; + os << endl; vb = false; } if (sb) - cerr << endl; // Extra newline between scope blocks. + os << endl; // Extra newline between scope blocks. - cerr << endl; + os << endl; scope& s (i->second); - dump_scope (s, ++i, ind, rts); + dump_scope (os, s, ++i, ind, rts); sb = true; } @@ -129,31 +129,34 @@ namespace build if (vb || sb) { - cerr << endl; + os << endl; vb = sb = false; } - cerr << endl - << ind; - dump_target (t); + os << endl + << ind; + dump_target (os, t); } ind.resize (ind.size () - 2); relative_base = orb; - cerr << endl - << ind << '}'; + os << endl + << ind << '}'; } void dump () { - string ind; - set rts; auto i (scopes.begin ()); scope& g (i->second); // Global scope. assert (&g == global_scope); - dump_scope (g, ++i, ind, rts); - cerr << endl; + + string ind; + set rts; + + ostream& os (*diag_stream); + dump_scope (os, g, ++i, ind, rts); + os << endl; } } diff --git a/build/file b/build/file index 9975e06..32cac48 100644 --- a/build/file +++ b/build/file @@ -6,10 +6,18 @@ #define BUILD_FILE #include +#include namespace build { class scope; + struct location; + + bool + is_src_root (const path&); + + bool + is_out_root (const path&); void source (const path& buildfile, scope& root, scope& base); @@ -25,7 +33,7 @@ namespace build void source_once (const path& buildfile, scope& root, scope& base, scope& once); - // Create project's root scope. Only set the src_root variable is the + // Create project's root scope. Only set the src_root variable if the // passed src_root value is not empty. // scope& @@ -42,10 +50,28 @@ namespace build bool bootstrap_src (scope& root); - // Load project's root[-pre].build unless already loaded. + // Create and bootstrap outer root scopes, if any. Loading is + // done by load_root_pre() below. + // + void + create_bootstrap_outer (scope& root); + + // Create and bootstrap inner root scopes between root and base, + // if any. Return the innermost created root scope or root if + // none were created. Loading is done by load_root_pre() below. + // + scope& + create_bootstrap_inner (scope& root, const path& out_base); + + // Load project's root[-pre].build unless already loaded. Also + // make sure all outer root scopes are loaded prior to loading + // this root scope. // void - root_pre (scope& root); + load_root_pre (scope& root); + + void + import (scope& base, const name&, const location&); } #include diff --git a/build/file.cxx b/build/file.cxx index 0b0220d..70a5ed8 100644 --- a/build/file.cxx +++ b/build/file.cxx @@ -15,6 +15,19 @@ using namespace std; namespace build { + bool + is_src_root (const path& d) + { + return file_exists (d / path ("build/bootstrap.build")) || + file_exists (d / path ("build/root.build")); + } + + bool + is_out_root (const path& d) + { + return file_exists (d / path ("build/bootstrap/src-root.build")); + } + void source (const path& bf, scope& root, scope& base) { @@ -146,18 +159,170 @@ namespace build } void - root_pre (scope& root) + create_bootstrap_outer (scope& root) + { + auto v (root.ro_variables ()["amalgamation"]); + + if (!v) + return; + + const path& d (v.as ()); + path out_root (root.path () / d); + path src_root (root.src_path () / d); + out_root.normalize (); + src_root.normalize (); + + scope& rs (create_root (out_root, src_root)); + + bootstrap_out (rs); + + // Check if the bootstrap process changed src_root. + // + const path& p (rs.variables["src_root"].as ()); + + if (src_root != p) + fail << "bootstrapped src_root " << p << " does not match " + << "amalgamated " << src_root; + + rs.src_path_ = &p; + + bootstrap_src (rs); + create_bootstrap_outer (rs); + } + + scope& + create_bootstrap_inner (scope& root, const path& out_base) + { + if (auto v = root.ro_variables ()["subprojects"]) + { + for (const name& n: v.as ()) + { + // Should be a list of directories. + // + if (!n.type.empty () || !n.value.empty () || n.dir.empty ()) + fail << "expected directory in subprojects variable " + << "instead of " << n; + + path out_root (root.path () / n.dir); + + if (!out_base.sub (out_root)) + continue; + + path src_root (root.src_path () / n.dir); + scope& rs (create_root (out_root, src_root)); + + bootstrap_out (rs); + + // Check if the bootstrap process changed src_root. + // + const path& p (rs.variables["src_root"].as ()); + + if (src_root != p) + fail << "bootstrapped src_root " << p << " does not match " + << "subproject " << src_root; + + rs.src_path_ = &p; + + bootstrap_src (rs); + + // See if there are more inner roots. + // + return create_bootstrap_inner (rs, out_base); + } + } + + return root; + } + + void + load_root_pre (scope& root) { tracer trace ("root_pre"); // First load outer roots, if any. // if (scope* rs = root.parent_scope ()->root_scope ()) - root_pre (*rs); + load_root_pre (*rs); path bf (root.src_path () / path ("build/root.build")); if (file_exists (bf)) source_once (bf, root, root); } + + void + import (scope& ibase, const name& n, const location& l) + { + scope& iroot (*ibase.root_scope ()); + + // Figure out this project's out_root. + // + const variable& var (variable_pool.find ("config." + n.value)); + auto val (iroot[var]); + + if (val) + { + if (val.scope == global_scope) + iroot.variables[var] = val; // Copy into root scope. + } + else + fail (l) << "unable to find out_root for imported " << n << + info << "consider explicitly configuring its out_root via the " + << var.name << " command line variable"; + + const path& out_root (val.as ()); + + // Bootstrap the imported root scope. This is pretty similar to + // what we do in main() except that here we don't try to guess + // src_root. + // + path src_root (is_src_root (out_root) ? out_root : path ()); + scope& root (create_root (out_root, src_root)); + + bootstrap_out (root); + + // Check that the bootstrap process set src_root. + // + if (auto v = root.ro_variables ()["src_root"]) + { + const path& p (v.as ()); + + if (!src_root.empty () && p != src_root) + fail << "bootstrapped src_root " << p << " does not match " + << "discovered " << src_root; + + root.src_path_ = &p; + } + else + fail (l) << "unable to determine src_root for imported " << n << + info << "consider configuring " << out_root; + + bootstrap_src (root); + + // Bootstrap outer roots if any. Loading will be done by + // load_root_pre() below. + // + create_bootstrap_outer (root); + + // Load the imported root scope. + // + load_root_pre (root); + + // Create a temporary scope so that the export stub does not mess + // up any of our variables. + // + temp_scope ts (ibase); + + // "Pass" the imported project's roots to the stub. + // + ts.variables["out_root"] = out_root; + ts.variables["src_root"] = src_root; + + // 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); + } } diff --git a/build/name b/build/name index f45930e..cf7427b 100644 --- a/build/name +++ b/build/name @@ -33,6 +33,16 @@ namespace build name (std::string t, path d, std::string v) : type (std::move (t)), dir (std::move (d)), value (std::move (v)) {} + bool + empty () const {type.empty () && dir.empty () && value.empty ();} + + bool + simple () const {return type.empty () && dir.empty ();} + + bool + directory () const + {return type.empty () && !dir.empty () && value.empty ();} + std::string type; path dir; std::string value; diff --git a/build/operation.cxx b/build/operation.cxx index 0d6bfcd..a8fdfb1 100644 --- a/build/operation.cxx +++ b/build/operation.cxx @@ -41,7 +41,7 @@ namespace build { // Load project's root[-pre].build. // - root_pre (root); + load_root_pre (root); // Create the base scope. Note that its existence doesn't // mean it was already processed as a base scope; it can diff --git a/build/parser b/build/parser index 1597432..53ce2d0 100644 --- a/build/parser +++ b/build/parser @@ -54,6 +54,12 @@ namespace build include (token&, token_type&); void + import (token&, token_type&); + + void + export_ (token&, token_type&); + + void using_ (token&, token_type&); void @@ -80,11 +86,13 @@ namespace build // Utilities. // private: - void - process_default_target (token&); + // Switch to new root scope and return the previous one. + // + scope* + switch_root (scope*); void - create_inner_roots (const path&); + process_default_target (token&); // Lexer. // @@ -112,10 +120,9 @@ namespace build lexer* lexer_; scope* scope_; // Current base scope (out_base). scope* root_; // Current root scope (out_root). - target* default_target_; - const path* out_root_; const path* src_root_; + target* default_target_; token peek_ {token_type::eos, false, 0, 0}; bool peeked_ {false}; diff --git a/build/parser.cxx b/build/parser.cxx index 4d5cbbe..d3abd46 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -42,18 +42,10 @@ namespace build lexer l (is, rw); lexer_ = &l; scope_ = &base; - root_ = &root; + root_ = nullptr; + switch_root (&root); default_target_ = nullptr; - out_root_ = &root.ro_variables ()["out_root"].as (); - - // During bootstrap we may not know src_root yet. - // - { - auto v (root.ro_variables ()["src_root"]); - src_root_ = v ? &v.as () : nullptr; - } - token t (type::eos, false, 0, 0); type tt; next (t, tt); @@ -123,6 +115,18 @@ namespace build include (t, tt); continue; } + else if (n == "import") + { + next (t, tt); + import (t, tt); + continue; + } + else if (n == "export") + { + next (t, tt); + export_ (t, tt); + continue; + } else if (n == "using") { next (t, tt); @@ -186,6 +190,8 @@ namespace build if (dir) { + // Directory scope. + // scope& prev (*scope_); path p (move (ns[0].dir)); // Steal. @@ -196,10 +202,19 @@ namespace build p.normalize (); scope_ = &scopes[p]; + // If this is a known project root scope, switch the + // parser state to use it. + // + scope* ors (switch_root (scope_->root () ? scope_ : root_)); + + if (ors != root_) + level4 ([&]{trace (nloc) << "switching to root scope " << p;}); + // A directory scope can contain anything that a top level can. // clause (t, tt); + switch_root (ors); scope_ = &prev; } else @@ -425,7 +440,7 @@ namespace build // if (src_root_ == nullptr) { - auto v ((*root_)["src_root"]); + auto v (root_->ro_variables ()["src_root"]); src_root_ = v ? &v.as () : nullptr; } } @@ -436,65 +451,6 @@ namespace build fail (t) << "expected newline instead of " << t; } - // Create, bootstrap, and load outer root scopes, if any. Also - // update the parser state to point to the innermost root scope. - // - void parser:: - create_inner_roots (const path& out_base) - { - auto v (root_->ro_variables ()["subprojects"]); - - if (!v) - return; - - for (const name& n: v.as ()) - { - // Should be a list of directories. - // - if (!n.type.empty () || !n.value.empty () || n.dir.empty ()) - fail << "expected directory in subprojects variable " - << "instead of " << n; - - path out_root (*out_root_ / n.dir); - - if (!out_base.sub (out_root)) - continue; - - path src_root (*src_root_ / n.dir); - scope& rs (create_root (out_root, src_root)); - - bootstrap_out (rs); - - // Check if the bootstrap process changed src_root. - // - const path& p (rs.variables["src_root"].as ()); - - if (src_root != p) - fail << "bootstrapped src_root " << p << " does not match " - << "subproject " << src_root; - - rs.src_path_ = &p; - - bootstrap_src (rs); - - // Load the root scope. - // - root_pre (rs); - - // Update parser state. - // - root_ = &rs; - out_root_ = &rs.variables["out_root"].as (); - src_root_ = &p; - - // See if there are more inner roots. - // - create_inner_roots (out_base); - - break; // Can only be in one sub-project. - } - } - void parser:: include (token& t, token_type& tt) { @@ -570,11 +526,14 @@ namespace build out_base = out_src (src_base, *out_root_, *src_root_); } - // Create, bootstrap and load root scope(s) of subproject(s) that - // this out_base belongs to. + // Create and bootstrap root scope(s) of subproject(s) that + // this out_base belongs to. If any were created, load them + // and update parser state. // - scope* ors (root_); - create_inner_roots (out_base); + scope* ors (switch_root (&create_bootstrap_inner (*root_, out_base))); + + if (root_ != ors) + load_root_pre (*root_); // Loads outer roots recursively. ifstream ifs (p.string ()); @@ -620,12 +579,79 @@ namespace build lexer_ = ol; path_ = op; - if (root_ != ors) - { - root_ = ors; - out_root_ = &root_->ro_variables ()["out_root"].as (); - src_root_ = &root_->ro_variables ()["src_root"].as (); - } + switch_root (ors); + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + import (token& t, token_type& tt) + { + tracer trace ("parser::import", &path_); + + if (src_root_ == nullptr) + fail (t) << "import during bootstrap"; + + // The rest should be a list of projects and/or targets. Parse + // them as names to get variable expansion and directory prefixes. + // + location l (get_location (t, &path_)); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + for (name& n: ns) + { + // For now we only support project names. + // + if (!n.simple ()) + fail (l) << "project name expected instead of " << n; + + + build::import (*scope_, n, l); + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + export_ (token& t, token_type& tt) + { + tracer trace ("parser::export", &path_); + + scope* ps (scope_->parent_scope ()); + + // This should be temp_scope. + // + 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. + // + 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_->ro_variables ()[var]) + ps->variables[var] = val; //@@ Move? + else + fail (l) << "undefined exported variable " << var.name; } if (tt == type::newline) @@ -672,10 +698,11 @@ namespace build void parser:: print (token& t, token_type& tt) { - for (; tt != type::newline && tt != type::eos; next (t, tt)) - cout << t; + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); - cout << endl; + cout << ns << endl; if (tt != type::eos) next (t, tt); // Swallow newline. @@ -1303,6 +1330,29 @@ namespace build return bs; } + scope* parser:: + switch_root (scope* nr) + { + scope* r (root_); + + if (nr != root_) + { + root_ = nr; + + // During bootstrap we may not know src_root yet. We are also + // not using the scopes's path() and src_path() since pointers + // to their return values are not guaranteed to be stable (and, + // in fact, path()'s is not). + // + out_root_ = &root_->ro_variables ()["out_root"].as (); + + auto v (root_->ro_variables ()["src_root"]); + src_root_ = v ? &v.as () : nullptr; + } + + return r; + } + void parser:: process_default_target (token& t) { diff --git a/build/path-io b/build/path-io new file mode 100644 index 0000000..a9deea9 --- /dev/null +++ b/build/path-io @@ -0,0 +1,22 @@ +// file : build/path-io -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_PATH_IO +#define BUILD_PATH_IO + +#include + +#include +#include + +namespace build +{ + inline std::ostream& + operator<< (std::ostream& os, const path& p) + { + return os << (&os == diag_stream ? diag_relative (p) : p.string ()); + } +} + +#endif // BUILD_PATH_IO diff --git a/build/scope b/build/scope index 2b621e7..2eb8ac7 100644 --- a/build/scope +++ b/build/scope @@ -88,6 +88,8 @@ namespace build private: friend class scope_map; + friend class temp_scope; + typedef path_map::const_iterator iterator; scope (): variables (*this) {} @@ -97,6 +99,20 @@ namespace build scope* root_; }; + // Temporary scope. The idea is to be able to create a temporary + // scope in order not to change the variables in the current scope. + // Such a scope is not entered in to the scope map. As a result it + // can only be used as a temporary set of variables. In particular, + // defining targets/prerequsites directly in such a scope will surely + // end up badly. Defining any nested scopes will be as if defining + // such a scope in the parent (since path() returns parent's path). + // + class temp_scope: public scope + { + public: + temp_scope (scope& p) {i_ = p.i_; parent_ = &p; root_ = p.root_;} + }; + class scope_map: public path_map { public: -- cgit v1.1