From 2e98d3ec3aa57c7b1776d3bf5e7e219a9a3cb3af Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 9 Mar 2015 08:43:58 +0200 Subject: Build according to buildspec At this stage operations are still ignored. --- build/b.cxx | 305 +++++++++++++++++++++++++++++++++++++++---------------- build/name.cxx | 19 ++-- build/parser.cxx | 136 ++++++------------------- build/path | 2 + build/search.cxx | 2 +- build/spec | 10 +- build/spec.cxx | 4 +- build/target | 36 +++++-- build/target.cxx | 140 +++++++++++++++++++------ 9 files changed, 406 insertions(+), 248 deletions(-) (limited to 'build') diff --git a/build/b.cxx b/build/b.cxx index de76071..0a1d8d2 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -171,83 +172,146 @@ main (int argc, char* argv[]) } } - if (verb >= 4) - trace << "buildspec: " << bspec; + level4 ([&]{trace << "buildspec: " << bspec;}); - // Figure out {src,out}_{root,base}. Note that all the paths must be - // normalized. - // - //@@ Must be normalized. + // Load all the buildfiles. // - path out_base (work); - path src_base (out_base); //@@ TMP + if (bspec.empty ()) + bspec.push_back (metaopspec ()); // Default meta-operation. - path src_root; - path out_root; - - // The project's root directory is the one that contains the build/ - // sub-directory which contains the pre.build file. - // - for (path d (src_base); !d.root () && d != home; d = d.directory ()) + for (metaopspec& ms: bspec) { - if (path_mtime (d / path ("build/pre.build")) != timestamp_nonexistent) + if (ms.empty ()) + ms.push_back (opspec ()); // Default operation. + + for (opspec& os: ms) { - src_root = d; - break; + if (os.empty ()) + // Default target: dir{}. + // + os.push_back (targetspec (name ("dir", path (), string ()))); + + for (targetspec& ts: os) + { + name& tn (ts.target); + + // First figure out the out_base of this target. The logic + // is as follows: if a directory was specified in any form, + // then that's the out_base. Otherwise, we check if the name + // value has a directory prefix. This has a good balance of + // control and the expected result in most cases. + // + path out_base (tn.dir); + if (out_base.empty ()) + { + // See if there is a directory part in value. We cannot + // assume it is a valid filesystem name so we will have + // to do the splitting manually. + // + path::size_type i (path::traits::rfind_separator (tn.value)); + + if (i != string::npos) + out_base = path (tn.value, i != 0 ? i : 1); // Special case: "/". + } + + if (out_base.relative ()) + out_base = work / out_base; + + out_base.normalize (); + + path& src_base (ts.src_base); + if (src_base.empty ()) + { + //@@ TODO: Configured case: find out_root (looking for + // "build/bootstrap.build" or some such), then src_root + // (stored in this file). Need to also detect the in-tree + // build. + // + + // If that doesn't work out (e.g., the first build), then + // default to the working directory as src_base. + // + src_base = work; + } + + if (src_base.relative ()) + src_base = work / src_base; + + src_base.normalize (); + + path src_root; + path out_root; + + // The project's root directory is the one that contains the build/ + // sub-directory which contains the pre.build file. + // + for (path d (src_base), f ("build/pre.build"); + !d.root () && d != home; + d = d.directory ()) + { + if (path_mtime (d / f) != timestamp_nonexistent) + { + src_root = d; + break; + } + } + + // If there is no such sub-directory, assume this is a simple + // project with src_root being the same as src_base. + // + if (src_root.empty ()) + { + src_root = src_base; + out_root = out_base; + } + else + out_root = out_base.directory (src_base.leaf (src_root)); + + if (verb >= 4) + { + trace << tn; + trace << " out_base: " << out_base.string (); + trace << " src_base: " << src_base.string (); + trace << " out_root: " << out_root.string (); + trace << " src_root: " << src_root.string (); + } + + // Create project root and base scopes, set the corresponding + // variables. Note that we might already have all of this set + // up as a result of one of the preceding target processing. + // + scope& proot_scope (scopes[out_root]); + scope& pbase_scope (scopes[out_base]); + + proot_scope.variables["out_root"] = move (out_root); + proot_scope.variables["src_root"] = move (src_root); + + pbase_scope.variables["out_base"] = out_base; + pbase_scope.variables["src_base"] = src_base; + + // Parse the buildfile. + // + path bf (src_base / path ("buildfile")); + + ifstream ifs (bf.string ()); + if (!ifs.is_open ()) + fail << "unable to open " << bf; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + parser p; + + try + { + p.parse_buildfile (ifs, bf, pbase_scope); + } + catch (const std::ios_base::failure&) + { + fail << "failed to read from " << bf; + } + } } } - // If there is no such sub-directory, assume this is a simple project - // with src_root being the same as src_base. - // - if (src_root.empty ()) - { - src_root = src_base; - out_root = out_base; - } - else - out_root = out_base.directory (src_base.leaf (src_root)); - - if (verb >= 4) - { - trace << "out_base: " << out_base.string (); - trace << "src_base: " << src_base.string (); - trace << "out_root: " << out_root.string (); - trace << "src_root: " << src_root.string (); - } - - // Create project root and base scopes, set the corresponding - // variables. - // - scope& proot_scope (scopes[out_root]); - scope& pbase_scope (scopes[out_base]); - - proot_scope.variables["out_root"] = move (out_root); - proot_scope.variables["src_root"] = move (src_root); - - pbase_scope.variables["out_base"] = out_base; - pbase_scope.variables["src_base"] = src_base; - - // Parse buildfile. - // - path bf ("buildfile"); - - ifstream ifs (bf.string ()); - if (!ifs.is_open ()) - fail << "unable to open " << bf; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - parser p; - - try - { - p.parse_buildfile (ifs, bf, pbase_scope); - } - catch (const std::ios_base::failure&) - { - fail << "failed to read from " << bf; - } - dump_scopes (); dump (); @@ -268,30 +332,93 @@ main (int argc, char* argv[]) path_rule path_r; rules[typeid (path_target)].emplace ("path", path_r); - // Build. + // Do the operations. We do meta-operations and operations sequentially + // (no parallelism). // - auto i (targets.find (dir::static_type.id, out_base, "", nullptr, trace)); - if (i == targets.end ()) - fail << "no targets in " << bf; - - target& t (**i); - - match (t); - - dump (); - - switch (update (t)) + for (metaopspec& ms: bspec) { - case target_state::uptodate: + for (opspec& os: ms) { - info << "target " << t << " is up to date"; - break; + // But multiple targets in the same operation can be done in + // parallel. + // + vector> tgs; + tgs.reserve (os.size ()); + + // First resolve and match all the targets. We don't want to + // start building before we know how for all the targets in + // this operation. + // + for (targetspec& ts: os) + { + name& tn (ts.target); + const location l ("", 1, 0); //@@ TODO + + const string* e; + const target_type* ti (target_types.find (tn, e)); + + if (ti == nullptr) + fail (l) << "unknown target type " << tn.type; + + // If the directory is relative, assume it is relative to work + // (must be consistent with how we derive out_base). + // + path& d (tn.dir); + + if (d.relative ()) + d = work / d; + + d.normalize (); + + target_set::key tk {ti, &d, &tn.value, &e}; + auto i (targets.find (tk, trace)); + if (i == targets.end ()) + fail (l) << "unknown target " << tk; + + target& t (**i); + + if (!t.recipe ()) + { + level4 ([&]{trace << "matching target " << t;}); + match (t); + } + + tgs.push_back (t); + } + + dump (); + + // Now build. + // + for (target& t: tgs) + { + // The target might have already been updated indirectly. We + // still want to inform the user about its status since they + // requested its update explicitly. + // + target_state s (t.state ()); + if (s == target_state::unknown) + { + level4 ([&]{trace << "updating target " << t;}); + s = update (t); + } + + switch (s) + { + case target_state::uptodate: + { + info << "target " << t << " is up to date"; + break; + } + case target_state::updated: + break; + case target_state::failed: + //@@ This could probably happen in a parallel build. + case target_state::unknown: + assert (false); + } + } } - case target_state::updated: - break; - case target_state::failed: - case target_state::unknown: - assert (false); } } catch (const failed&) diff --git a/build/name.cxx b/build/name.cxx index 7f300f4..46e2440 100644 --- a/build/name.cxx +++ b/build/name.cxx @@ -19,8 +19,9 @@ namespace build bool hv (!n.value.empty ()); bool hd (false); - // Print the directory before type. - // + if (ht) + os << n.type << '{'; + if (!n.dir.empty ()) { string s (diag_relative_work (n.dir)); @@ -31,24 +32,20 @@ namespace build { os << s; - // Add the directory separator unless it is already there. + // Add the directory separator unless it is already there + // or we have type but no value. The idea is to print foo/ + // or dir{foo}. // - if (s.back () != path::traits::directory_separator) + if (s.back () != path::traits::directory_separator && (hv || !ht)) os << path::traits::directory_separator; hd = true; } } - if (ht) - os << n.type; - - if (ht || (hd && hv)) - os << '{'; - os << n.value; - if (ht || (hd && hv)) + if (ht) os << '}'; if (!ht && !hv && !hd) diff --git a/build/parser.cxx b/build/parser.cxx index 5cdd627..2c629d6 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -36,84 +36,6 @@ namespace build typedef token_type type; - // Given a target or prerequisite name, figure out its type, taking - // into account extensions, special names (e.g., '.' and '..'), or - // anything else that might be relevant. Also process the name (in - // place) by extracting the extension, adjusting dir/value, etc. - // - const target_type& - find_target_type (name& n, const location& l, const string*& ext) - { - string& v (n.value); - - // First determine the target type. - // - const char* tt; - if (n.type.empty ()) - { - // Empty name or '.' and '..' signify a directory. - // - if (v.empty () || v == "." || v == "..") - tt = "dir"; - else - //@@ TODO: derive type from extension. - // - tt = "file"; - } - else - tt = n.type.c_str (); - - auto i (target_types.find (tt)); - if (i == target_types.end ()) - fail (l) << "unknown target type " << tt; - - const target_type& ti (i->second); - - ext = nullptr; - - // Directories require special name processing. If we find that more - // targets deviate, then we should make this target-type-specific. - // - if (ti.id == dir::static_type.id || ti.id == fsdir::static_type.id) - { - // The canonical representation of a directory name is with empty - // value. - // - if (!v.empty ()) - { - n.dir /= path (v); // Move name value to dir. - v.clear (); - } - } - else - { - // Split the path into its directory part (if any) the name part, - // and the extension (if any). We cannot assume the name part is - // a valid filesystem name so we will have to do the splitting - // manually. - // - path::size_type i (path::traits::rfind_separator (v)); - - if (i != string::npos) - { - n.dir /= path (v, i != 0 ? i : 1); // Special case: "/". - v = string (v, i + 1, string::npos); - } - - // Extract the extension. - // - string::size_type j (path::traits::find_extension (v)); - - if (j != string::npos) - { - ext = &extension_pool.find (v.c_str () + j); - v.resize (j - 1); - } - } - - return ti; - } - void parser:: parse_buildfile (istream& is, const path& p, scope& s) { @@ -299,7 +221,10 @@ namespace build for (auto& pn: pns) { const string* e; - const target_type& ti (find_target_type (pn, ploc, e)); + const target_type* ti (target_types.find (pn, e)); + + if (ti == nullptr) + fail (ploc) << "unknown target type " << pn.type; pn.dir.normalize (); @@ -307,7 +232,7 @@ namespace build // prerequisite& p ( scope_->prerequisites.insert ( - ti, move (pn.dir), move (pn.value), e, *scope_, trace).first); + *ti, move (pn.dir), move (pn.value), e, *scope_, trace).first); ps.push_back (p); } @@ -315,7 +240,11 @@ namespace build for (auto& tn: ns) { const string* e; - const target_type& ti (find_target_type (tn, nloc, e)); + const target_type* ti (target_types.find (tn, e)); + + if (ti == nullptr) + fail (nloc) << "unknown target type " << tn.type; + path& d (tn.dir); if (d.empty ()) @@ -332,7 +261,7 @@ namespace build // target& t ( targets.insert ( - ti, move (tn.dir), move (tn.value), e, trace).first); + *ti, move (tn.dir), move (tn.value), e, trace).first); t.prerequisites = ps; //@@ OPT: move if last target. @@ -764,7 +693,8 @@ namespace build // Note that at this stage we don't treat '.' and '..' as special // (unless they are specified with a directory separator) because // then we would have ended up treating '.: ...' as a directory - // scope. Instead, this is handled higher up, in find_target_type(). + // scope. Instead, this is handled higher up the processing chain, + // in target_types::find(). // // @@ TODO: and not quoted // @@ -1097,36 +1027,36 @@ namespace build if (bs.empty () || !bs.back ().meta_operation.empty ()) bs.push_back (metaopspec ()); // Empty (default) meta operation. - metaopspec& mo (bs.back ()); + metaopspec& ms (bs.back ()); for (auto i (ns.begin ()), e (i + targets); i != e; ++i) { if (opname (*i)) - mo.push_back (opspec (move (i->value))); + ms.push_back (opspec (move (i->value))); else { - // Do we have the src_root? + // Do we have the src_base? // - path src_root; + path src_base; if (i->pair) { if (!i->type.empty ()) - fail (l) << "expected target src_root instead of " << *i; + fail (l) << "expected target src_base instead of " << *i; - src_root = move (i->dir); + src_base = move (i->dir); if (!i->value.empty ()) - src_root /= path (move (i->value)); + src_base /= path (move (i->value)); ++i; assert (i != e); } - if (mo.empty () || !mo.back ().operation.empty ()) - mo.push_back (opspec ()); // Empty (default) operation. + if (ms.empty () || !ms.back ().operation.empty ()) + ms.push_back (opspec ()); // Empty (default) operation. - opspec& os (mo.back ()); - os.emplace_back (move (src_root), move (*i)); + opspec& os (ms.back ()); + os.emplace_back (move (src_base), move (*i)); } } } @@ -1146,16 +1076,16 @@ namespace build // checks. // bool meta (false); - for (const metaopspec& mo: nbs) + for (const metaopspec& nms: nbs) { - if (!mo.meta_operation.empty ()) - fail (l) << "nested meta-operation " << mo.meta_operation; + if (!nms.meta_operation.empty ()) + fail (l) << "nested meta-operation " << nms.meta_operation; if (!meta) { - for (const opspec& o: mo) + for (const opspec& nos: nms) { - if (!o.operation.empty ()) + if (!nos.operation.empty ()) { meta = true; break; @@ -1181,13 +1111,13 @@ namespace build // should be just a bunch of targets. // assert (nmo.size () == 1); - opspec& no (nmo.back ()); + opspec& nos (nmo.back ()); if (bs.empty () || !bs.back ().meta_operation.empty ()) bs.push_back (metaopspec ()); // Empty (default) meta operation. - no.operation = move (ns.back ().value); - bs.back ().push_back (move (no)); + nos.operation = move (ns.back ().value); + bs.back ().push_back (move (nos)); } next (t, tt); // Done with ')'. @@ -1210,7 +1140,7 @@ namespace build // then we don't do anything. // if (default_target_ == nullptr || // No targets in this buildfile. - targets.find (dir::static_type.id, // Explicit current dir target. + targets.find (dir::static_type, // Explicit current dir target. scope_->path (), "", nullptr, diff --git a/build/path b/build/path index 9b68485..550af80 100644 --- a/build/path +++ b/build/path @@ -74,6 +74,8 @@ namespace build return string_type::npos; } + // Return the position of '.' or npos if there is no extension. + // static size_type find_extension (string_type const& s) { diff --git a/build/search.cxx b/build/search.cxx index e80f7e7..5f21c64 100644 --- a/build/search.cxx +++ b/build/search.cxx @@ -41,7 +41,7 @@ namespace build } } - auto i (targets.find (p.type.id, d, p.name, p.ext, trace)); + auto i (targets.find (p.type, d, p.name, p.ext, trace)); if (i == targets.end ()) return 0; diff --git a/build/spec b/build/spec index 9ca8c4d..fda4186 100644 --- a/build/spec +++ b/build/spec @@ -17,11 +17,13 @@ namespace build { struct targetspec { - targetspec (path sr, name t) - : src_root (std::move (sr)), target (std::move (t)) {} + explicit + targetspec (name t): target (std::move (t)) {} + targetspec (path sb, name t) + : src_base (std::move (sb)), target (std::move (t)) {} - path src_root; - name target; // target.dir is out_root. + path src_base; + name target; }; struct opspec: std::vector diff --git a/build/spec.cxx b/build/spec.cxx index fb83b31..31bd4f1 100644 --- a/build/spec.cxx +++ b/build/spec.cxx @@ -15,9 +15,9 @@ namespace build ostream& operator<< (ostream& os, const targetspec& s) { - if (!s.src_root.empty ()) + if (!s.src_base.empty ()) { - string d (diag_relative_work (s.src_root)); + string d (diag_relative_work (s.src_base)); if (d != ".") { diff --git a/build/target b/build/target index 31a590d..9c4640e 100644 --- a/build/target +++ b/build/target @@ -18,6 +18,7 @@ #include #include #include +#include #include #include // compare_*, extension_pool @@ -102,14 +103,17 @@ namespace build { struct key { - mutable const std::type_index* type; + mutable const target_type* type; mutable const path* dir; mutable const std::string* name; - mutable const std::string** ext; + mutable const std::string* const* ext; friend bool operator< (const key& x, const key& y) { + const std::type_index& xt (x.type->id); + const std::type_index& yt (y.type->id); + //@@ TODO: use compare() to compare once. // Unspecified and specified extension are assumed equal. The @@ -117,10 +121,10 @@ namespace build // pointers. // return - (*x.type < *y.type) || - (*x.type == *y.type && *x.name < *y.name) || - (*x.type == *y.type && *x.name == *y.name && *x.dir < *y.dir) || - (*x.type == *y.type && *x.name == *y.name && *x.dir == *y.dir && + (xt < yt) || + (xt == yt && *x.name < *y.name) || + (xt == yt && *x.name == *y.name && *x.dir < *y.dir) || + (xt == yt && *x.name == *y.name && *x.dir == *y.dir && *x.ext != nullptr && *y.ext != nullptr && **x.ext < **y.ext); } }; @@ -132,7 +136,7 @@ namespace build find (const key& k, tracer& trace) const; iterator - find (const std::type_index& type, + find (const target_type& type, const path& dir, const std::string& name, const std::string* ext, @@ -156,6 +160,9 @@ namespace build map map_; }; + std::ostream& + operator<< (std::ostream&, const target_set::key&); + extern target_set targets; class target_type_map: public std::map< @@ -164,8 +171,23 @@ namespace build compare_c_string> { public: + typedef std::map, + compare_c_string> base; + void insert (const target_type& tt) {emplace (tt.name, tt);} + + using base::find; + + // Given a name, figure out its type, taking into account extensions, + // special names (e.g., '.' and '..'), or anything else that might be + // relevant. Also process the name (in place) by extracting the + // extension, adjusting dir/value, etc (note that the dir is not + // necessarily normalized). Return NULL if not found. + // + const target_type* + find (name&, const std::string*& ext) const; }; extern target_type_map target_types; diff --git a/build/target.cxx b/build/target.cxx index 6fdfb7b..f09a858 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -13,39 +13,12 @@ using namespace std; namespace build { - // target_type - // - const target_type* - resolve_target_type (const std::string name); - // target // ostream& operator<< (ostream& os, const target& t) { - os << t.type ().name << '{'; - - if (!t.dir.empty ()) - { - string s (diag_relative_work (t.dir)); - - if (s != ".") - { - os << s; - - if (!t.name.empty () && s.back () != path::traits::directory_separator) - os << path::traits::directory_separator; - } - } - - os << t.name; - - if (t.ext != nullptr && !t.ext->empty ()) - os << '.' << *t.ext; - - os << '}'; - - return os; + return os << target_set::key {&t.type (), &t.dir, &t.name, &t.ext}; } static target* @@ -59,6 +32,8 @@ namespace build // target_set // + target_set targets; + auto target_set:: find (const key& k, tracer& trace) const -> iterator { @@ -99,22 +74,125 @@ namespace build const std::string* ext, tracer& trace) { - iterator i (find (key {&tt.id, &dir, &name, &ext}, trace)); + iterator i (find (key {&tt, &dir, &name, &ext}, trace)); if (i != end ()) return pair (**i, false); unique_ptr t (tt.factory (move (dir), move (name), ext)); i = map_.emplace ( - make_pair (key {&tt.id, &t->dir, &t->name, &t->ext}, + make_pair (key {&tt, &t->dir, &t->name, &t->ext}, move (t))).first; return pair (**i, true); } - target_set targets; + ostream& + operator<< (ostream& os, const target_set::key& k) + { + os << k.type->name << '{'; + + if (!k.dir->empty ()) + { + string s (diag_relative_work (*k.dir)); + + if (s != ".") + { + os << s; + + if (!k.name->empty () && + s.back () != path::traits::directory_separator) + os << path::traits::directory_separator; + } + } + + os << *k.name; + + if (*k.ext != nullptr && !(*k.ext)->empty ()) + os << '.' << **k.ext; + + os << '}'; + + return os; + } + + // + // target_type_map target_types; + const target_type* target_type_map:: + find (name& n, const string*& ext) const + { + ext = nullptr; + + string& v (n.value); + + // First determine the target type. + // + const char* tt; + if (n.type.empty ()) + { + // Empty name or '.' and '..' signify a directory. + // + if (v.empty () || v == "." || v == "..") + tt = "dir"; + else + //@@ TODO: derive type from extension. + // + tt = "file"; + } + else + tt = n.type.c_str (); + + auto i (find (tt)); + if (i == end ()) + return nullptr; + + const target_type& ti (i->second); + + // Directories require special name processing. If we find that more + // targets deviate, then we should make this target-type-specific. + // + if (ti.id == dir::static_type.id || ti.id == fsdir::static_type.id) + { + // The canonical representation of a directory name is with empty + // value. + // + if (!v.empty ()) + { + n.dir /= path (v); // Move name value to dir. + v.clear (); + } + } + else + { + // Split the path into its directory part (if any) the name part, + // and the extension (if any). We cannot assume the name part is + // a valid filesystem name so we will have to do the splitting + // manually. + // + path::size_type i (path::traits::rfind_separator (v)); + + if (i != string::npos) + { + n.dir /= path (v, i != 0 ? i : 1); // Special case: "/". + v = string (v, i + 1, string::npos); + } + + // Extract the extension. + // + string::size_type j (path::traits::find_extension (v)); + + if (j != string::npos) + { + ext = &extension_pool.find (v.c_str () + j + 1); + v.resize (j); + } + } + + return &ti; + } + // path_target // timestamp path_target:: -- cgit v1.1