From 8f8ab1e8f6d85748547c0d0e9987eed4f3c3e17b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 17 Apr 2015 15:08:05 +0200 Subject: Add support for target groups, use to handle obj/obja/objso object targets --- build/algorithm | 27 ++++-- build/algorithm.cxx | 76 ++++++++++------ build/algorithm.ixx | 24 ++++- build/algorithm.txx | 23 +++-- build/b.cxx | 10 ++ build/cxx/rule | 2 +- build/cxx/rule.cxx | 243 ++++++++++++++++++++++++++++++------------------- build/dump.cxx | 8 +- build/native | 29 +++++- build/native.cxx | 41 ++++++++- build/parser.cxx | 4 +- build/prerequisite | 55 +++++++++-- build/prerequisite.cxx | 48 +--------- build/rule.cxx | 13 +-- build/search | 12 +-- build/search.cxx | 60 ++++++------ build/target | 144 +++++++++++++++++------------ build/target-key | 73 +++++++++++++++ build/target.cxx | 30 +++--- 19 files changed, 598 insertions(+), 324 deletions(-) create mode 100644 build/target-key (limited to 'build') diff --git a/build/algorithm b/build/algorithm index 7d2c226..81ad8d8 100644 --- a/build/algorithm +++ b/build/algorithm @@ -12,6 +12,7 @@ namespace build { class prerequisite; + class prerequisite_key; // The default prerequsite search implementation. It first calls the // target-type-specific search function. If that doesn't yeld anything, @@ -20,13 +21,22 @@ namespace build target& search (prerequisite&); + // As above but specify the prerequisite to search as a key. Useful + // for searching for target group members where we need to search + // for a different target type. + // + target& + search (const prerequisite_key&); + // Match a rule to the action/target with ambiguity detection. // void match (action, target&); // The default prerequisite search and match implementation. It calls - // search() and then match() for each prerequisite in a loop. + // search() and then match() for each prerequisite in a loop. If this + // target is a member of a group, then it first does this to the group's + // prerequisites. // void search_and_match (action, target&); @@ -51,11 +61,12 @@ namespace build execute (action, target&); // The default prerequisite execute implementation. It calls execute() - // on each non-ignored (non-NULL target) prerequisite in a loop. - // Returns target_state::changed if any of them were changed and - // target_state::unchanged otherwise. It treats targets with postponed - // execution the same as ignored. Note that this function can be - // used as a recipe. + // on each non-ignored (non-NULL target) prerequisite in a loop. If this + // target is a member of a group, then it first does this to the group's + // prerequisites. Returns target_state::changed if any of them were + // changed and target_state::unchanged otherwise. It treats targets + // with postponed execution the same as ignored. Note that this + // function can be used as a recipe. // target_state execute_prerequisites (action, target&); @@ -74,7 +85,9 @@ namespace build // Another version of the above that does two extra things for the // caller: it determines whether the action needs to be executed on // the target based on the passed timestamp and, if so, finds a - // prerequisite of the specified type (e.g., a source file). + // prerequisite of the specified type (e.g., a source file). If + // there are multiple prerequisites of this type, then the last + // one returned. // template T* diff --git a/build/algorithm.cxx b/build/algorithm.cxx index 7675b0c..82286f5 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -22,14 +22,12 @@ using namespace std; namespace build { target& - search_impl (prerequisite& p) + search (const prerequisite_key& pk) { - assert (p.target == nullptr); - - if (target* t = p.type.search (p)) + if (target* t = pk.tk.type->search (pk)) return *t; - return create_new_target (p); + return create_new_target (pk); } void @@ -150,19 +148,28 @@ namespace build void search_and_match (action a, target& t) { - for (prerequisite& p: t.prerequisites) - match (a, search (p)); + if (t.group != nullptr) + search_and_match (a, *t.group); + + for (prerequisite_target& p: t.prerequisites) + { + p.target = &search (p); + match (a, *p.target); + } } void search_and_match (action a, target& t, const dir_path& d) { - for (prerequisite& p: t.prerequisites) + if (t.group != nullptr) + search_and_match (a, *t.group, d); + + for (prerequisite_target& p: t.prerequisites) { - target& pt (search (p)); + p.target = &search (p); - if (pt.dir.sub (d)) - match (a, pt); + if (p.target->dir.sub (d)) + match (a, *p.target); else p.target = nullptr; // Ignore. } @@ -188,7 +195,7 @@ namespace build { level5 ([&]{trace << "injecting prerequisite for " << t;}); - prerequisite& pp ( + prerequisite& p ( s.prerequisites.insert ( fsdir::static_type, d, @@ -197,8 +204,11 @@ namespace build s, trace).first); - t.prerequisites.push_back (pp); - match (a, search (pp)); + target& pt (search (p)); + + t.prerequisites.emplace_back (p, pt); + + match (a, pt); } } } @@ -244,14 +254,15 @@ namespace build { target_state ts (target_state::unchanged); - for (const prerequisite& p: t.prerequisites) + if (t.group != nullptr) + ts = execute_prerequisites (a, *t.group); + + for (target* pt: t.prerequisites) { - if (p.target == nullptr) // Skip ignored. + if (pt == nullptr) // Skip ignored. continue; - target& pt (*p.target); - - if (execute (a, pt) == target_state::changed) + if (execute (a, *pt) == target_state::changed) ts = target_state::changed; } @@ -263,14 +274,18 @@ namespace build { target_state ts (target_state::unchanged); - for (const prerequisite& p: reverse_iterate (t.prerequisites)) + for (target* pt: reverse_iterate (t.prerequisites)) { - if (p.target == nullptr) // Skip ignored. + if (pt == nullptr) // Skip ignored. continue; - target& pt (*p.target); + if (execute (a, *pt) == target_state::changed) + ts = target_state::changed; + } - if (execute (a, pt) == target_state::changed) + if (t.group != nullptr) + { + if (reverse_execute_prerequisites (a, *t.group) == target_state::changed) ts = target_state::changed; } @@ -282,19 +297,24 @@ namespace build { bool e (mt == timestamp_nonexistent); - for (const prerequisite& p: t.prerequisites) + if (t.group != nullptr) + { + if (execute_prerequisites (a, *t.group, mt)) + e = true; + } + + for (target* pt: t.prerequisites) { - if (p.target == nullptr) // Skip ignored. + if (pt == nullptr) // Skip ignored. continue; - target& pt (*p.target); - target_state ts (execute (a, pt)); + target_state ts (execute (a, *pt)); if (!e) { // If this is an mtime-based target, then compare timestamps. // - if (auto mpt = dynamic_cast (&pt)) + if (auto mpt = dynamic_cast (pt)) { timestamp mp (mpt->mtime ()); diff --git a/build/algorithm.ixx b/build/algorithm.ixx index 3bc4632..5d87338 100644 --- a/build/algorithm.ixx +++ b/build/algorithm.ixx @@ -2,17 +2,19 @@ // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#include #include namespace build { - target& - search_impl (prerequisite&); - inline target& search (prerequisite& p) { - return p.target != nullptr ? *p.target : search_impl (p); + if (p.target == nullptr) + p.target = &search ( + prerequisite_key {&p.type, &p.dir, &p.name, &p.ext, &p.scope}); + + return *p.target; } void @@ -50,4 +52,18 @@ namespace build } } } + + template + T* + execute_prerequisites (action, target&, const timestamp&, bool&); + + template + inline T* + execute_prerequisites (action a, target& t, const timestamp& mt) + { + bool e (mt == timestamp_nonexistent); + T* r (execute_prerequisites (a, t, mt, e)); + assert (r != nullptr); + return e ? r : nullptr; + } } diff --git a/build/algorithm.txx b/build/algorithm.txx index 1e22be1..2592b5e 100644 --- a/build/algorithm.txx +++ b/build/algorithm.txx @@ -6,29 +6,29 @@ namespace build { template T* - execute_prerequisites (action a, target& t, const timestamp& mt) + execute_prerequisites (action a, target& t, const timestamp& mt, bool& e) { //@@ Can factor the bulk of it into a non-template code. Can // either do a function template that will do dynamic_cast check // or can scan the target type info myself. I think latter. // - T* r (nullptr); - bool e (mt == timestamp_nonexistent); - for (const prerequisite& p: t.prerequisites) + if (t.group != nullptr) + r = execute_prerequisites (a, *t.group, mt, e); + + for (target* pt: t.prerequisites) { - if (p.target == nullptr) // Skip ignored. + if (pt == nullptr) // Skip ignored. continue; - target& pt (*p.target); - target_state ts (execute (a, pt)); + target_state ts (execute (a, *pt)); if (!e) { // If this is an mtime-based target, then compare timestamps. // - if (auto mpt = dynamic_cast (&pt)) + if (auto mpt = dynamic_cast (pt)) { timestamp mp (mpt->mtime ()); @@ -50,11 +50,10 @@ namespace build } } - if (r == nullptr) - r = dynamic_cast (&pt); + if (T* tmp = dynamic_cast (pt)) + r = tmp; } - assert (r != nullptr); - return e ? r : nullptr; + return r; } } diff --git a/build/b.cxx b/build/b.cxx index 2e36003..8ba4eb0 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -139,6 +139,8 @@ main (int argc, char* argv[]) target_types.insert (dir::static_type); target_types.insert (fsdir::static_type); + target_types.insert (obja::static_type); + target_types.insert (objso::static_type); target_types.insert (obj::static_type); target_types.insert (exe::static_type); target_types.insert (lib::static_type); @@ -163,6 +165,14 @@ main (int argc, char* argv[]) rules[clean_id][typeid (lib)].emplace ("cxx.gnu.link", cxx_link); cxx::compile cxx_compile; + rules[default_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); + rules[update_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); + rules[clean_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); + + rules[default_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); + rules[update_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); + rules[clean_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); + rules[default_id][typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); rules[update_id][typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); rules[clean_id][typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); diff --git a/build/cxx/rule b/build/cxx/rule index d2378d2..24879d4 100644 --- a/build/cxx/rule +++ b/build/cxx/rule @@ -33,7 +33,7 @@ namespace build private: void - inject_prerequisites (action, obj&, const cxx&, scope&) const; + inject_prerequisites (action, target&, const cxx&, scope&) const; }; class link: public rule diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index bc105cf..6f5606c 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -72,18 +72,25 @@ namespace build // - check prerequisites: the rest are headers (other ignorable?) // - if path already assigned, verify extension? // - // @@ Q: - // - // - Wouldn't it make sense to cache source file? Careful: unloading - // of dependency info. - // + + if (t.is_a ()) + fail << diag_doing (a, t) << " directly not supported"; // See if we have a C++ source file. // - for (prerequisite& p: t.prerequisites) + for (prerequisite_target& pe: t.prerequisites) { - if (p.type.id == typeid (cxx)) - return &p; + if (pe.prereq->type.id == typeid (cxx)) + return &pe; + } + + if (t.group != nullptr) + { + for (prerequisite_target& pe: t.group->prerequisites) + { + if (pe.prereq->type.id == typeid (cxx)) + return &pe; + } } level3 ([&]{trace << "no c++ source file for target " << t;}); @@ -91,14 +98,19 @@ namespace build } recipe compile:: - apply (action a, target& t, void* v) const + apply (action a, target& xt, void* v) const { - // Derive object file name from target name. - // - obj& o (dynamic_cast (t)); + path_target& t (static_cast (xt)); - if (o.path ().empty ()) - o.path (o.derived_path ("o")); + // Derive file name from target name. + // + if (t.path ().empty ()) + { + if (t.is_a ()) + t.path (t.derived_path ("o")); + else + t.path (t.derived_path ("o", nullptr, "-so")); + } // Search and match all the existing prerequisites. The injection // code (below) takes care of the ones it is adding. @@ -114,22 +126,22 @@ namespace build default: assert (false); } - // Inject dependency on the output directory. - // - inject_parent_fsdir (a, t); - // Inject additional prerequisites. For now we only do it for // update and default. // if (a.operation () == update_id || a.operation () == default_id) { - auto& sp (*static_cast (v)); - auto& st (dynamic_cast (*sp.target)); + prerequisite_target& spe (*static_cast (v)); + cxx& st (dynamic_cast (*spe.target)); if (st.mtime () != timestamp_nonexistent) - inject_prerequisites (a, o, st, sp.scope); + inject_prerequisites (a, t, st, spe.prereq->scope); } + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + switch (a) { case perform_update_id: return &perform_update; @@ -181,17 +193,17 @@ namespace build } void compile:: - inject_prerequisites (action a, obj& o, const cxx& s, scope& ds) const + inject_prerequisites (action a, target& t, const cxx& s, scope& ds) const { tracer trace ("cxx::compile::inject_prerequisites"); - scope& rs (*o.root_scope ()); // Shouldn't have matched if nullptr. + scope& rs (*t.root_scope ()); // Shouldn't have matched if nullptr. const string& cxx (rs["config.cxx"].as ()); vector args {cxx.c_str ()}; append_options (args, rs, "config.cxx.poptions"); - append_options (args, o, "cxx.poptions"); + append_options (args, t, "cxx.poptions"); // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. // Or maybe they are not C++ options? Common options? @@ -199,9 +211,12 @@ namespace build append_options (args, rs, "config.cxx.coptions"); string std; // Storage. - append_std (args, o, std); + append_std (args, t, std); - append_options (args, o, "cxx.coptions"); + append_options (args, t, "cxx.coptions"); + + if (t.is_a ()) + args.push_back ("-fPIC"); args.push_back ("-MM"); // @@ Change to -M args.push_back ("-MG"); // Treat missing headers as generated. @@ -222,7 +237,7 @@ namespace build if (verb >= 2) print_process (args); - level5 ([&]{trace << "target: " << o;}); + level5 ([&]{trace << "target: " << t;}); try { @@ -287,20 +302,22 @@ namespace build ds.prerequisites.insert ( hxx::static_type, move (d), move (n), e, ds, trace).first); - o.prerequisites.push_back (p); - // Resolve to target. // - path_target& t (dynamic_cast (search (p))); + path_target& pt (dynamic_cast (search (p))); + + // Add to prerequisites list. + // + t.prerequisites.emplace_back (p, pt); // Assign path. // - if (t.path ().empty ()) - t.path (move (f)); + if (pt.path ().empty ()) + pt.path (move (f)); // Match to a rule. // - build::match (a, t); + build::match (a, pt); } } @@ -325,10 +342,10 @@ namespace build } target_state compile:: - perform_update (action a, target& t) + perform_update (action a, target& xt) { - obj& o (dynamic_cast (t)); - cxx* s (execute_prerequisites (a, o, o.mtime ())); + path_target& t (static_cast (xt)); + cxx* s (execute_prerequisites (a, t, t.mtime ())); if (s == nullptr) return target_state::unchanged; @@ -336,23 +353,26 @@ namespace build // Translate paths to relative (to working directory) ones. This // results in easier to read diagnostics. // - path relo (relative (o.path ())); + path relo (relative (t.path ())); path rels (relative (s->path ())); - scope& rs (*o.root_scope ()); // Shouldn't have matched if nullptr. + scope& rs (*t.root_scope ()); // Shouldn't have matched if nullptr. const string& cxx (rs["config.cxx"].as ()); vector args {cxx.c_str ()}; append_options (args, rs, "config.cxx.poptions"); - append_options (args, o, "cxx.poptions"); + append_options (args, t, "cxx.poptions"); append_options (args, rs, "config.cxx.coptions"); string std; // Storage. - append_std (args, o, std); + append_std (args, t, std); + + append_options (args, t, "cxx.coptions"); - append_options (args, o, "cxx.coptions"); + if (t.is_a ()) + args.push_back ("-fPIC"); args.push_back ("-o"); args.push_back (relo.string ().c_str ()); @@ -379,7 +399,7 @@ namespace build // current clock time. It has the advantage of having the // subseconds precision. // - o.mtime (system_clock::now ()); + t.mtime (system_clock::now ()); return target_state::changed; } catch (const process_error& e) @@ -417,6 +437,8 @@ namespace build // (i.e., a utility library). // + bool so (t.is_a ()); + // Scan prerequisites and see if we can work with what we've got. // bool seen_cxx (false), seen_c (false), seen_obj (false); @@ -425,18 +447,23 @@ namespace build { if (p.type.id == typeid (cxx)) // @@ Should use is_a (add to p.type). { - if (!seen_cxx) - seen_cxx = true; + seen_cxx = seen_cxx || true; } else if (p.type.id == typeid (c)) { - if (!seen_c) - seen_c = true; + seen_c = seen_c || true; + } + else if (p.type.id == typeid (obja)) + { + if (so) + fail << "shared library " << t << " prerequisite " << p + << " is static object"; + + seen_obj = seen_obj || true; } - else if (p.type.id == typeid (obj)) + else if (p.type.id == typeid (objso) || p.type.id == typeid (obj)) { - if (!seen_obj) - seen_obj = true; + seen_obj = seen_obj || true; } else if (p.type.id != typeid (fsdir)) { @@ -458,27 +485,23 @@ namespace build } recipe link:: - apply (action a, target& t, void*) const + apply (action a, target& xt, void*) const { tracer trace ("cxx::link::apply"); + path_target& t (static_cast (xt)); + + bool so (t.is_a ()); + // Derive file name from target name. // - bool so (false); - if (exe* e = dynamic_cast (&t)) - { - if (e->path ().empty ()) - e->path (e->derived_path ()); - } - else if (lib* l = dynamic_cast (&t)) + if (t.path ().empty ()) { - if (l->path ().empty ()) - l->path (l->derived_path ("so", "lib")); - - so = true; + if (so) + t.path (t.derived_path ("so", "lib")); + else + t.path (t.derived_path ()); // exe } - else - assert (false); // We may need the project roots for rule chaining (see below). // We will resolve them lazily only if needed. @@ -489,21 +512,40 @@ namespace build // Process prerequisites: do rule chaining for C and C++ source // files as well as search and match. // - for (auto& pr: t.prerequisites) + for (prerequisite_target& pe: t.prerequisites) { - prerequisite& p (pr); + prerequisite& p (pe); - if (p.type.id != typeid (c) && p.type.id != typeid (cxx)) + if (!p.is_a () && !p.is_a ()) { - // The same logic as in search_and_match(). + // The same basic logic as in search_and_match(). // - target& pt (search (p)); + pe.target = &search (p); - if (a.operation () == clean_id && !pt.dir.sub (t.dir)) - p.target = nullptr; // Ignore. - else - build::match (a, pt); + if (a.operation () == clean_id && !pe.target->dir.sub (t.dir)) + { + pe.target = nullptr; // Ignore. + continue; + } + + // If this is the obj{} target group, then pick the appropriate + // member and make sure it is searched and matched. + // + if (obj* o = pe.target->is_a ()) + { + pe.target = so ? static_cast (o->so) : o->a; + + if (pe.target == nullptr) + { + const target_type& type ( + so ? objso::static_type : obja::static_type); + + pe.target = &search ( + prerequisite_key {&type, &p.dir, &p.name, &p.ext, &p.scope}); + } + } + build::match (a, *pe.target); continue; } @@ -520,8 +562,10 @@ namespace build } prerequisite& cp (p); + const target_type& o_type ( + so ? objso::static_type : obja::static_type); - // Come up with the obj{} prerequisite. The c(xx){} prerequisite + // Come up with the obj*{} prerequisite. The c(xx){} prerequisite // directory can be relative (to the scope) or absolute. If it is // relative, then use it as is. If it is absolute, then translate // it to the corresponding directory under out_root. While the @@ -535,14 +579,15 @@ namespace build { if (!cp.dir.sub (*src_root)) fail << "out of project prerequisite " << cp << - info << "specify corresponding obj{} target explicitly"; + info << "specify corresponding " << o_type.name << "{} " + << "target explicitly"; d = *out_root / cp.dir.leaf (*src_root); } prerequisite& op ( cp.scope.prerequisites.insert ( - obj::static_type, + o_type, move (d), cp.name, nullptr, @@ -569,15 +614,10 @@ namespace build // be in the same directory as obj{} and if not, well, go // and find yourself another build system). // - p.target = nullptr; // Skip. + pe.target = nullptr; // Skip. continue; } - // Set the -fPIC option if we are building a shared object. - // - if (so) - ot.append ("cxx.coptions") += "-fPIC"; - // If this target already exists, then it needs to be "compatible" // with what we are doing here. // @@ -624,17 +664,19 @@ namespace build << " would be incompatible with existing target " << ot << info << "existing prerequsite " << *cp1 << " does not " << "match " << cp << - info << "specify corresponding obj{} target explicitly"; + info << "specify corresponding " << o_type.name << "{} " + << "target explicitly"; } else { - ot.prerequisites.push_back (cp); + ot.prerequisites.emplace_back (cp); build::match (a, ot); } - // Change the exe{} target's prerequsite from cxx{} to obj{}. + // Change the exe{} target's prerequsite ref from cxx{} to obj*{}. // - pr = op; + pe.prereq = &op; + pe.target = &ot; } // Inject dependency on the output directory. @@ -658,7 +700,7 @@ namespace build // path_target& t (static_cast (xt)); - //exe& e (dynamic_cast (t)); + bool so (t.is_a ()); if (!execute_prerequisites (a, t, t.mtime ())) return target_state::unchanged; @@ -674,9 +716,6 @@ namespace build vector args {cxx.c_str ()}; - if (dynamic_cast (&t) != nullptr) - args.push_back ("-shared"); - append_options (args, rs, "config.cxx.coptions"); string std; // Storage. @@ -684,19 +723,33 @@ namespace build append_options (args, t, "cxx.coptions"); + if (so) + args.push_back ("-shared"); + args.push_back ("-o"); args.push_back (relt.string ().c_str ()); append_options (args, rs, "config.cxx.loptions"); append_options (args, t, "cxx.loptions"); - for (const prerequisite& p: t.prerequisites) + for (target* pt: t.prerequisites) { - if (const obj* o = dynamic_cast (p.target)) - { - relo.push_back (relative (o->path ())); - args.push_back (relo.back ().string ().c_str ()); - } + if (pt == nullptr) + continue; // Skipped. + + path_target* ppt; + + if (obj* o = pt->is_a ()) + ppt = so ? static_cast (o->so) : o->a; + else if ((ppt = pt->is_a ())) + ; + else if ((ppt = pt->is_a ())) + ; + else + continue; + + relo.push_back (relative (ppt->path ())); + args.push_back (relo.back ().string ().c_str ()); } append_options (args, rs, "config.cxx.libs"); diff --git a/build/dump.cxx b/build/dump.cxx index dbd9c19..75de2be 100644 --- a/build/dump.cxx +++ b/build/dump.cxx @@ -23,14 +23,14 @@ namespace build { os << t << ':'; - for (const prerequisite& p: t.prerequisites) + for (const prerequisite_target& pe: t.prerequisites) { os << ' '; - if (p.target != nullptr) - os << *p.target; + if (pe.target != nullptr) + os << *pe.target; else - os << p; + os << *pe.prereq; } } diff --git a/build/native b/build/native index 88f340d..3c1e6f2 100644 --- a/build/native +++ b/build/native @@ -9,7 +9,9 @@ namespace build { - class obj: public file + // The obj{} target group members. + // + class obja: public file { public: using file::file; @@ -19,6 +21,31 @@ namespace build static const target_type static_type; }; + class objso: public file + { + public: + using file::file; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + + // Target group. + // + class obj: public target + { + public: + using target::target; + + obja* a {nullptr}; + objso* so {nullptr}; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + class exe: public file { public: diff --git a/build/native.cxx b/build/native.cxx index d8b0149..42b6cf3 100644 --- a/build/native.cxx +++ b/build/native.cxx @@ -8,13 +8,48 @@ using namespace std; namespace build { + const target_type obja::static_type + { + typeid (obja), + "obja", + &file::static_type, + &member_target_factory, + file::static_type.search + }; + + const target_type objso::static_type + { + typeid (objso), + "objso", + &file::static_type, + &member_target_factory, + file::static_type.search + }; + + static target* + obj_factory (dir_path d, string n, const string* e) + { + target* a (targets.find (obja::static_type, d, n)); + target* so (targets.find (objso::static_type, d, n)); + + obj* t (new obj (move (d), move (n), e)); + + if ((t->a = static_cast (a))) + a->group = t; + + if ((t->so = static_cast (so))) + so->group = t; + + return t; + } + const target_type obj::static_type { typeid (obj), "obj", - &file::static_type, - &target_factory, - file::static_type.search + &target::static_type, + &obj_factory, + target::static_type.search }; const target_type exe::static_type diff --git a/build/parser.cxx b/build/parser.cxx index e055bdb..06e489a 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -274,7 +274,7 @@ namespace build scope_->prerequisites.insert ( *ti, move (pn.dir), move (pn.value), e, *scope_, trace).first); - ps.push_back (p); + ps.emplace_back (p); } for (auto& tn: ns) @@ -1391,7 +1391,7 @@ namespace build trace).first); p.target = &dt; - ct.prerequisites.push_back (p); + ct.prerequisites.emplace_back (p); } token_type parser:: diff --git a/build/prerequisite b/build/prerequisite index 77e04c2..b845f03 100644 --- a/build/prerequisite +++ b/build/prerequisite @@ -9,9 +9,12 @@ #include #include #include // move +#include #include +#include // reference_wrapper #include +#include #include // extension_pool #include @@ -19,7 +22,28 @@ namespace build { class scope; class target; - class target_type; + + // Light-weight (by being shallow-pointing) prerequsite key, similar + // to (and based on) target key. + // + class prerequisite_key + { + public: + typedef build::scope scope_type; + + target_key tk; + mutable scope_type* scope; + }; + + inline bool + operator< (const prerequisite_key& x, const prerequisite_key& y) + { + assert (x.scope == y.scope); + return x.tk < y.tk; + } + + std::ostream& + operator<< (std::ostream&, const prerequisite_key&); class prerequisite { @@ -34,7 +58,7 @@ namespace build const std::string* e, scope_type& s) : type (t), dir (std::move (d)), name (std::move (n)), ext (e), - scope (s), target (0) {} + scope (s), target (nullptr) {} public: const target_type_type& type; @@ -42,15 +66,32 @@ namespace build const std::string name; const std::string* ext; // NULL if unspecified. scope_type& scope; - target_type* target; // NULL if not yet resolved. + target_type* target; // NULL if not yet resolved. Note that this should + // always be the "primary target", not a member of + // a target group. + public: + // Prerequisite (target) type. + // + template + bool + is_a () const {return type.id == typeid (T);} }; - std::ostream& - operator<< (std::ostream&, const prerequisite&); + inline bool + operator< (const prerequisite& x, const prerequisite& y) + { + return prerequisite_key {&x.type, &x.dir, &x.name, &x.ext, &x.scope} < + prerequisite_key {&y.type, &y.dir, &y.name, &y.ext, &y.scope}; + } - bool - operator< (const prerequisite&, const prerequisite&); + inline std::ostream& + operator<< (std::ostream& os, const prerequisite& p) + { + return os << prerequisite_key {&p.type, &p.dir, &p.name, &p.ext, &p.scope}; + } + // Set of prerequisites in a scope. + // struct prerequisite_set: std::set { std::pair diff --git a/build/prerequisite.cxx b/build/prerequisite.cxx index 5d3235c..da6b8ba 100644 --- a/build/prerequisite.cxx +++ b/build/prerequisite.cxx @@ -16,59 +16,19 @@ using namespace std; namespace build { ostream& - operator<< (ostream& os, const prerequisite& p) + operator<< (ostream& os, const prerequisite_key& pk) { // Print scope unless the prerequisite's directory is absolute. // - if (!p.dir.absolute ()) + if (!pk.tk.dir->absolute ()) { - string s (diag_relative (p.scope.path (), false)); + string s (diag_relative (pk.scope->path (), false)); if (!s.empty ()) os << s << ':'; } - // If the name is empty, then we want to print the directory - // inside {}, e.g., dir{bar/}, not bar/dir{}. - // - bool n (!p.name.empty ()); - string d (diag_relative (p.dir, false)); - - if (n) - os << d; - - os << p.type.name << '{'; - - if (n) - { - os << p.name; - - if (p.ext != nullptr && !p.ext->empty ()) - os << '.' << *p.ext; - } - else - os << d; - - os << '}'; - - return os; - } - - bool - operator< (const prerequisite& x, const prerequisite& y) - { - //@@ TODO: use compare() to compare once. - - // Unspecified and specified extension are assumed equal. The - // extension strings are from the pool, so we can just compare - // pointers. - // - return - (x.type.id < y.type.id) || - (x.type.id == y.type.id && x.name < y.name) || - (x.type.id == y.type.id && x.name == y.name && x.dir < y.dir) || - (x.type.id == y.type.id && x.name == y.name && x.dir == y.dir && - x.ext != nullptr && y.ext != nullptr && x.ext < y.ext); + return os << pk.tk; } // prerequisite_set diff --git a/build/rule.cxx b/build/rule.cxx index 884b28d..a9afe1b 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -102,20 +102,21 @@ namespace build // timestamp mt (dynamic_cast (t).mtime ()); - for (const prerequisite& p: t.prerequisites) + for (target* pt: t.prerequisites) { - target& pt (*p.target); - target_state ts (execute (a, pt)); + assert (pt != nullptr); // We don't skip anything. + + target_state ts (execute (a, *pt)); // If this is an mtime-based target, then compare timestamps. // - if (auto mpt = dynamic_cast (&pt)) + if (auto mpt = dynamic_cast (pt)) { timestamp mp (mpt->mtime ()); if (mt < mp) fail << "no recipe to " << diag_do (a, t) << - info << "prerequisite " << pt << " is ahead of " << t + info << "prerequisite " << *pt << " is ahead of " << t << " by " << (mp - mt); } else @@ -124,7 +125,7 @@ namespace build // if (ts == target_state::changed) fail << "no recipe to " << diag_do (a, t) << - info << "prerequisite " << pt << " is ahead of " << t + info << "prerequisite " << *pt << " is ahead of " << t << " because it was updated"; } } diff --git a/build/search b/build/search index dfbe32e..1f90a8a 100644 --- a/build/search +++ b/build/search @@ -10,22 +10,22 @@ namespace build { class target; - class prerequisite; + class prerequisite_key; - // Search for an existing target in this prerequisite's directory scope. + // Search for an existing target in this prerequisite's scope. // target* - search_existing_target (prerequisite&); + search_existing_target (const prerequisite_key&); // Search for an existing file in the specified list of search paths. // target* - search_existing_file (prerequisite&, const dir_paths&); + search_existing_file (const prerequisite_key&, const dir_paths&); - // Create a new target in this prerequisite's directory scope. + // Create a new target in this prerequisite's scope. // target& - create_new_target (prerequisite&); + create_new_target (const prerequisite_key&); } #endif // BUILD_SEARCH diff --git a/build/search.cxx b/build/search.cxx index 6434bf7..7fa9720 100644 --- a/build/search.cxx +++ b/build/search.cxx @@ -19,29 +19,29 @@ using namespace std; namespace build { target* - search_existing_target (prerequisite& p) + search_existing_target (const prerequisite_key& pk) { tracer trace ("search_existing_target"); - assert (p.target == nullptr); + const target_key& tk (pk.tk); - // Look for an existing target in this prerequisite's directory scope. + // Look for an existing target in this directory scope. // dir_path d; - if (p.dir.absolute ()) - d = p.dir; // Already normalized. + if (tk.dir->absolute ()) + d = *tk.dir; // Already normalized. else { - d = p.scope.path (); + d = pk.scope->path (); - if (!p.dir.empty ()) + if (!tk.dir->empty ()) { - d /= p.dir; + d /= *tk.dir; d.normalize (); } } - auto i (targets.find (p.type, d, p.name, p.ext, trace)); + auto i (targets.find (*tk.type, d, *tk.name, *tk.ext, trace)); if (i == targets.end ()) return 0; @@ -49,29 +49,29 @@ namespace build target& t (**i); level4 ([&]{trace << "existing target " << t << " for prerequsite " - << p;}); + << pk;}); - p.target = &t; return &t; } target* - search_existing_file (prerequisite& p, const dir_paths& sp) + search_existing_file (const prerequisite_key& pk, const dir_paths& sp) { tracer trace ("search_existing_file"); - assert (p.dir.relative ()); + const target_key& tk (pk.tk); + assert (tk.dir->relative ()); // Go over paths and extension looking for a file. // for (const dir_path& d: sp) { - path f (d / p.dir / path (p.name)); + path f (d / *tk.dir / path (*tk.name)); f.normalize (); // @@ TMP: use target name as an extension. // - const string& e (p.ext != nullptr ? *p.ext : p.type.name); + const string& e (*tk.ext != nullptr ? **tk.ext : tk.type->name); if (!e.empty ()) { @@ -85,22 +85,23 @@ namespace build continue; level4 ([&]{trace << "found existing file " << f << " for prerequsite " - << p;}); + << pk;}); // Find or insert. // - auto r (targets.insert (p.type, f.directory (), p.name, p.ext, trace)); + auto r ( + targets.insert ( + *tk.type, f.directory (), *tk.name, *tk.ext, trace)); // Has to be a path_target. // path_target& t (dynamic_cast (r.first)); level4 ([&]{trace << (r.second ? "new" : "existing") << " target " - << t << " for prerequsite " << p;}); + << t << " for prerequsite " << pk;}); t.path (move (f)); t.mtime (mt); - p.target = &t; return &t; } @@ -108,38 +109,37 @@ namespace build } target& - create_new_target (prerequisite& p) + create_new_target (const prerequisite_key& pk) { tracer trace ("create_new_target"); - assert (p.target == nullptr); + const target_key& tk (pk.tk); - // We default to the target in this prerequisite's directory scope. + // We default to the target in this directory scope. // dir_path d; - if (p.dir.absolute ()) - d = p.dir; // Already normalized. + if (tk.dir->absolute ()) + d = *tk.dir; // Already normalized. else { - d = p.scope.path (); + d = pk.scope->path (); - if (!p.dir.empty ()) + if (!tk.dir->empty ()) { - d /= p.dir; + d /= *tk.dir; d.normalize (); } } // Find or insert. // - auto r (targets.insert (p.type, move (d), p.name, p.ext, trace)); + auto r (targets.insert (*tk.type, move (d), *tk.name, *tk.ext, trace)); assert (r.second); target& t (r.first); - level4 ([&]{trace << "new target " << t << " for prerequsite " << p;}); + level4 ([&]{trace << "new target " << t << " for prerequsite " << pk;}); - p.target = &t; return t; } } diff --git a/build/target b/build/target index 00be7b5..3033e4e 100644 --- a/build/target +++ b/build/target @@ -11,7 +11,6 @@ #include // unique_ptr #include // size_t #include // function, reference_wrapper -#include #include #include #include // move() @@ -22,11 +21,13 @@ #include #include #include +#include #include #include // compare_*, extension_pool namespace build { + class scope; class target; // Target state. @@ -70,36 +71,59 @@ namespace build target_state noop_recipe_function (action, target&); - // Target type. + // Prerequisite target. It consist of a reference to the prerequisite + // plus a pointer to target to which it resolves in this context. // - struct target_type + struct prerequisite_target { - std::type_index id; - const char* name; - const target_type* base; - target* (*const factory) (dir_path, std::string, const std::string*); - target* (*const search) (prerequisite&); - }; + typedef build::target target_type; - inline std::ostream& - operator<< (std::ostream& os, const target_type& tt) - { - return os << tt.name; - } + prerequisite* prereq; // Must not be NULL but can be reset. + + operator prerequisite& () const {return *prereq;} + + // Target to which this prerequisite resolves in this context. + // Note that unlike prerequisite::target, this can be a group + // member target. Depending on the stage, NULL means either + // the target is not yet resolved or it should be skipped. + // + // Note also that it is possible the target can vary from + // action to action, just like recipes. We don't need to keep + // track of the action here since the target will be updates + // if the recipe is updated, as part of rule::apply(). + // + target_type* target {nullptr}; + + operator target_type* () const {return target;} + + explicit + prerequisite_target (prerequisite& p): prereq (&p) {} + prerequisite_target (prerequisite& p, target_type& t) + : prereq (&p), target (&t) {} + }; + + // + // class target { public: virtual ~target () = default; + target (const target&) = delete; + target& operator= (const target&) = delete; + target (dir_path d, std::string n, const std::string* e) : dir (std::move (d)), name (std::move (n)), ext (e) {} - const dir_path dir; // Absolute and normalized. + const dir_path dir; // Absolute and normalized. const std::string name; - const std::string* ext; // Extension, NULL means unspecified, - // empty means no extension. + const std::string* ext; // Extension, NULL means unspecified, + // empty means no extension. + + target* group {nullptr}; // Target group to which this target + // belongs, if any. public: // Most qualified scope that contains this target. // @@ -116,10 +140,7 @@ namespace build // Prerequisites. // public: - typedef - std::vector> - prerequisites_type; - + typedef std::vector prerequisites_type; prerequisites_type prerequisites; // Target-specific variables. @@ -209,11 +230,17 @@ namespace build dependents = 0; } - private: - target (const target&) = delete; - target& operator= (const target&) = delete; - + // Target type info. + // public: + template + T* + is_a () {return dynamic_cast (this);} + + template + const T* + is_a () const {return dynamic_cast (this);} + virtual const target_type& type () const = 0; static const target_type static_type; @@ -225,39 +252,6 @@ namespace build std::ostream& operator<< (std::ostream&, const target&); - // Light-weight (by being shallow-pointing) target key. - // - struct target_key - { - mutable const target_type* type; - mutable const dir_path* dir; - mutable const std::string* name; - mutable const std::string* const* ext; - - friend bool - operator< (const target_key& x, const target_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 - // extension strings are from the pool, so we can just compare - // pointers. - // - return - (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); - } - }; - - std::ostream& - operator<< (std::ostream&, const target_key&); - struct target_set { typedef std::map> map; @@ -277,6 +271,19 @@ namespace build return find (target_key {&type, &dir, &name, &e}, trace); } + // As above but ignore the extension and return the target or + // nullptr instead of the iterator. + // + target* + find (const target_type& type, + const dir_path& dir, + const std::string& name) const + { + const std::string* e (nullptr); + auto i (map_.find (target_key {&type, &dir, &name, &e})); + return i != map_.end () ? i->second.get () : nullptr; + } + iterator begin () const {return map_.begin ();} iterator end () const {return map_.end ();} @@ -330,6 +337,20 @@ namespace build return new T (std::move (d), std::move (n), e); } + // Default implementation for a target that is a member of a + // target group. Besides creating the target as above this + // version also tries to "link up" with the group. + // + template + target* + member_target_factory (dir_path d, std::string n, const std::string* e) + { + target* g (targets.find (G::static_type, d, n)); + target* t (new T (std::move (d), std::move (n), e)); + t->group = g; + return t; + } + // Modification time-based target. // class mtime_target: public target @@ -377,11 +398,14 @@ namespace build // Return a path derived from target's dir, name, and, if specified, // ext. If ext is not specified, then use default_ext. If name_prefix - // if not NULL, add it before the name part. + // if not NULL, add it before the name part and after the directory. + // Similarly, if name_suffix if not NULL, add it after the name part + // and before the extension. // path_type derived_path (const char* default_ext = nullptr, - const char* name_prefix = nullptr); + const char* name_prefix = nullptr, + const char* name_suffix = nullptr); protected: virtual timestamp diff --git a/build/target-key b/build/target-key new file mode 100644 index 0000000..b18125f --- /dev/null +++ b/build/target-key @@ -0,0 +1,73 @@ +// file : build/target-key -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_TARGET_KEY +#define BUILD_TARGET_KEY + +#include +#include +#include + +#include + +namespace build +{ + class scope; + class target; + class target_key; + class prerequisite_key; + + // Target type. + // + struct target_type + { + std::type_index id; + const char* name; + const target_type* base; + target* (*const factory) (dir_path, std::string, const std::string*); + target* (*const search) (const prerequisite_key&); + }; + + inline std::ostream& + operator<< (std::ostream& os, const target_type& tt) + { + return os << tt.name; + } + + // Light-weight (by being shallow-pointing) target key. + // + class target_key + { + public: + mutable const target_type* type; + mutable const dir_path* dir; + mutable const std::string* name; + mutable const std::string* const* ext; + + friend bool + operator< (const target_key& x, const target_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 + // extension strings are from the pool, so we can just compare + // pointers. + // + return + (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); + } + }; + + std::ostream& + operator<< (std::ostream&, const target_key&); // Defined in target.cxx +} + +#endif // BUILD_TARGET_KEY diff --git a/build/target.cxx b/build/target.cxx index 5be0a8d..cb751ad 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -85,12 +85,12 @@ namespace build } static target* - search_target (prerequisite& p) + search_target (const prerequisite_key& pk) { // The default behavior is to look for an existing target in the // prerequisite's directory scope. // - return search_existing_target (p); + return search_existing_target (pk); } // target_set @@ -133,8 +133,8 @@ namespace build pair target_set:: insert (const target_type& tt, dir_path dir, - std::string name, - const std::string* ext, + string name, + const string* ext, tracer& trace) { iterator i (find (target_key {&tt, &dir, &name, &ext}, trace)); @@ -260,7 +260,7 @@ namespace build // path_target // path path_target:: - derived_path (const char* de, const char* np) + derived_path (const char* de, const char* np, const char* ns) { string n; @@ -269,6 +269,9 @@ namespace build n += name; + if (ns != nullptr) + n += ns; + if (ext != nullptr) { if (!ext->empty ()) @@ -297,22 +300,21 @@ namespace build // static target* - search_file (prerequisite& p) + search_file (const prerequisite_key& pk) { // First see if there is an existing target. // - if (target* t = search_existing_target (p)) + if (target* t = search_existing_target (pk)) return t; // Then look for an existing file in this target-type-specific // list of paths (@@ TODO: comes from the variable). // - if (p.dir.relative ()) + if (pk.tk.dir->relative ()) { dir_paths sp; - sp.push_back (src_out (p.scope.path (), p.scope)); // src_base - - return search_existing_file (p, sp); + sp.push_back (src_out (pk.scope->path (), *pk.scope)); // src_base + return search_existing_file (pk, sp); } else return nullptr; @@ -321,16 +323,16 @@ namespace build // dir target // static target* - search_alias (prerequisite& p) + search_alias (const prerequisite_key& pk) { // For an alias/action we don't want to silently create a target // since it will do nothing and it most likely not what the author // intended. // - target* t (search_existing_target (p)); + target* t (search_existing_target (pk)); if (t == nullptr) - fail << "no explicit target for prerequisite " << p; + fail << "no explicit target for prerequisite " << pk; return t; } -- cgit v1.1