From 4d1c02b736f4c1e827b11085cdc83ce4b46c03d1 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 26 Jun 2016 16:06:54 +0200 Subject: Add notion of ad hoc group, use to handle DLL/import library --- build2/algorithm.cxx | 23 +++- build2/bin/module.cxx | 12 +- build2/cxx/link | 3 - build2/cxx/link.cxx | 280 ++++++++++++++++++++++++++-------------------- build2/cxx/module.cxx | 20 +++- build2/dist/rule.cxx | 4 +- build2/install/module.cxx | 6 +- build2/install/rule.cxx | 64 ++++++++--- build2/install/utility | 22 +++- build2/parser.cxx | 47 +------- build2/scope | 14 +++ build2/scope.cxx | 68 +++++++++++ build2/search.cxx | 2 +- build2/target | 165 +++++++++++++++++++-------- build2/target.cxx | 64 +++++++---- build2/target.ixx | 95 ++++++++++++---- build2/target.txx | 24 ++++ 17 files changed, 611 insertions(+), 302 deletions(-) diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 17768e3..02f3b8e 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -502,9 +502,28 @@ namespace build2 : target_state::unchanged); if (r == target_state::changed && ef.empty ()) - { ef = move (f); - } + + er |= r; + } + + // Now clean the ad hoc group file members, if any. + // + for (target* m (ft.member); m != nullptr; m = m->member) + { + file* fm (dynamic_cast (m)); + + if (fm == nullptr || fm->path ().empty ()) + continue; + + const path& f (fm->path ()); + + target_state r (rmfile (f, false) + ? target_state::changed + : target_state::unchanged); + + if (r == target_state::changed && ef.empty ()) + ef = f; er |= r; } diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx index edb3c53..1ab29f9 100644 --- a/build2/bin/module.cxx +++ b/build2/bin/module.cxx @@ -213,7 +213,9 @@ namespace build2 // Configure "installability" of our target types. // - install::path (b, dir_path ("bin")); // Install into install.bin. + using namespace install; + + install_path (b, dir_path ("bin")); // Install into install.bin. // Should shared libraries have executable bit? That depends on // who you ask. In Debian, for example, it should not unless, it @@ -233,10 +235,12 @@ namespace build2 // // Everyone is happy then? // - install::path (b, dir_path ("lib")); // Install into install.lib. + install_path (b, dir_path ("lib")); // Install into install.lib. + + //@@ For Windows, libso{} is an import library and shouldn't be exec. - install::path (b, dir_path ("lib")); // Install into install.lib. - install::mode (b, "644"); + install_path (b, dir_path ("lib")); // Install into install.lib. + install_mode (b, "644"); return true; } diff --git a/build2/cxx/link b/build2/cxx/link index ca45e17..d0584de 100644 --- a/build2/cxx/link +++ b/build2/cxx/link @@ -28,9 +28,6 @@ namespace build2 static target_state perform_update (action, target&); - static target_state - perform_clean (action, target&); - static link instance; public: diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx index bf2b992..0e4fd5a 100644 --- a/build2/cxx/link.cxx +++ b/build2/cxx/link.cxx @@ -384,17 +384,8 @@ namespace build2 // Above we searched for the import library (.dll.a) but if it's // not found, then we also search for the .dll (unless the // extension was specified explicitly) since we can link to it - // directly. - // - // Note also that the resulting libso{} would end up being the .a - // import library. We could have tried to always find the - // corresponding .dll, but when installed they normally end up in - // different directories (lib/ and bin/). In fact, there is no - // reason to require the presence of the .dll at all (think cross- - // compilation). So while having libso{} being .a is a bit of - // hack, it is simple and appears harmless (think of it as the - // import library being a proxy for the real thing). But let me - // know if you have a better idea. + // directly. Note also that the resulting libso{} would end up + // being the .dll. // if (mt == timestamp_nonexistent && ext == nullptr) { @@ -566,7 +557,9 @@ namespace build2 path_target& t (static_cast (xt)); - scope& rs (t.root_scope ()); + scope& bs (t.base_scope ()); + scope& rs (*bs.root_scope ()); + const string& tsys (cast (rs["cxx.target.system"])); const string& tclass (cast (rs["cxx.target.class"])); type lt (link_type (t)); @@ -581,58 +574,117 @@ namespace build2 // if (t.path ().empty ()) { + const char* p (nullptr); + const char* e (nullptr); + switch (lt) { case type::e: { - const char* e; if (tclass == "windows") e = "exe"; else e = ""; - t.derive_path (e); break; } case type::a: - case type::so: { - auto l (t["bin.libprefix"]); + // To be anally precise, let's use the ar id to decide how to name + // the library in case, for example, someone wants to archive + // VC-compiled object files with MinGW ar. + // + if (cast (rs["bin.ar.id"]) == "msvc") + { + e = "lib"; + } + else + { + p = "lib"; + e = "a"; + } - const char* p (l ? cast (l).c_str () : nullptr); - const char* e (nullptr); + if (auto l = t["bin.libprefix"]) + p = cast (l).c_str (); + + break; + } + case type::so: + { + //@@ VC: DLL name. - if (lt == type::a) + if (tclass == "macosx") + { + p = "lib"; + e = "dylib"; + } + else if (tclass == "windows") { - // To be anally precise, let's use the ar id to decide how to - // name the library in case, for example, someone wants to - // archive VC-compiled object files with MINGW ar. + // On Windows libso{} is an ad hoc group. The libso{} itself is + // the import library and we add dll{} as a member (see below). + // While at first it may seem strange that libso{} is the import + // library and not the DLL, if you meditate on it, you will see + // it makes a lot of sense: our main task here is building and + // for that we need the import library, not the DLL. // - if (cast (rs["bin.ar.id"]) == "msvc") + if (tsys == "mingw32") { - e = "lib"; + p = "lib"; + e = "dll.a"; } else { - e = "a"; - if (p == nullptr) p = "lib"; + // Usually on Windows the import library is called the same as + // the DLL but with the .lib extension. Which means it clashes + // with the static library. Instead of decorating the static + // library name with ugly suffixes (as is customary), let's + // use the MinGW approach (one must admit it's quite elegant) + // and call it .dll.lib. + // + e = "dll.lib"; } } else { - //@@ VC: DLL name. - - if (tclass == "macosx") e = "dylib"; - if (tclass == "windows") e = "dll"; - else e = "so"; - - if (p == nullptr) p = "lib"; + p = "lib"; + e = "so"; } - t.derive_path (e, p); + if (auto l = t["bin.libprefix"]) + p = cast (l).c_str (); + break; } } + + t.derive_path (e, p); + } + + // On Windows add the DLL as an ad hoc group member. + // + if (so && tclass == "windows") + { + file* dll (nullptr); + + // Registered by cxx module's init(). + // + const target_type& tt (*bs.find_target_type ("dll")); + + if (t.member != nullptr) // Might already be there. + { + assert (t.member->type () == tt); + dll = static_cast (t.member); + } + else + { + t.member = dll = static_cast ( + &search (tt, t.dir, t.out, t.name, nullptr, nullptr)); + } + + if (dll->path ().empty ()) + dll->derive_path ("dll", tsys == "mingw32" ? "lib" : nullptr); + + dll->recipe (a, group_recipe); } t.prerequisite_targets.clear (); // See lib pre-match in match() above. @@ -846,7 +898,7 @@ namespace build2 switch (a) { case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; + case perform_clean_id: return &perform_clean_depdb; default: return noop_recipe; // Configure update. } } @@ -1158,76 +1210,98 @@ namespace build2 // path relt (relative (t.path ())); - if (lt == type::a) + switch (lt) { - //@@ VC: what are /LIBPATH, /NODEFAULTLIB for? - // - - args[0] = cast (rs["config.bin.ar"]).string ().c_str (); - - if (aid == "msvc") + case type::e: { - if (verb < 3) - args.push_back ("/NOLOGO"); + args[0] = cast (rs["config.cxx"]).string ().c_str (); - out = "/OUT:" + relt.string (); - args.push_back (out.c_str ()); - } - else - args.push_back (relt.string ().c_str ()); - } - else - { - args[0] = cast (rs["config.cxx"]).string ().c_str (); - - if (cid == "msvc") - { - uint64_t cver (cast (rs["cxx.version.major"])); - - if (verb < 3) - args.push_back ("/nologo"); + if (cid == "msvc") + { + uint64_t cver (cast (rs["cxx.version.major"])); - //@@ VC TODO: DLL building (names via /link?) + if (verb < 3) + args.push_back ("/nologo"); - // The /Fe: option (executable file name) only became available in - // VS2013/12.0. - // - if (cver >= 18) + // The /Fe: option (executable file name) only became available in + // VS2013/12.0. + // + if (cver >= 18) + { + args.push_back ("/Fe:"); + args.push_back (relt.string ().c_str ()); + } + else + { + out = "/Fe" + relt.string (); + args.push_back (out.c_str ()); + } + } + else { - args.push_back ("/Fe:"); + args.push_back ("-o"); args.push_back (relt.string ().c_str ()); } - else + + break; + } + case type::a: + { + //@@ VC: what are /LIBPATH, /NODEFAULTLIB for? + // + + args[0] = cast (rs["config.bin.ar"]).string ().c_str (); + + if (aid == "msvc") { - out = "/Fe" + relt.string (); + if (verb < 3) + args.push_back ("/NOLOGO"); + + out = "/OUT:" + relt.string (); args.push_back (out.c_str ()); } + else + args.push_back (relt.string ().c_str ()); + + break; } - else + case type::so: { - // Add the option that triggers building a shared library. - // - if (so) + args[0] = cast (rs["config.cxx"]).string ().c_str (); + + if (cid == "msvc") { + //@@ VC TODO: DLL building (names via /link?) + } + else + { + // Add the option that triggers building a shared library. + // if (tclass == "macosx") args.push_back ("-dynamiclib"); else args.push_back ("-shared"); - } - - args.push_back ("-o"); - args.push_back (relt.string ().c_str ()); - // Add any additional options (import library name, etc). - // - if (so) - { if (tsys == "mingw32") { - out = "-Wl,--out-implib=" + relt.string () + ".a"; + // On Windows libso{} is the import stub and its first ad hoc + // group member is dll{}. + // + out = "-Wl,--out-implib=" + relt.string (); + relt = relative (static_cast (t.member)->path ()); + + args.push_back ("-o"); + args.push_back (relt.string ().c_str ()); args.push_back (out.c_str ()); } + else + { + args.push_back ("-o"); + args.push_back (relt.string ().c_str ()); + } } + + break; } } @@ -1245,30 +1319,7 @@ namespace build2 ((ppt = a = pt->is_a ()) || (ppt = so = pt->is_a ())))) { - path p (relative (ppt->path ())); - - if (so != nullptr) - { - if (tsys == "mingw32") - { - // Normally we want to link to the import library (*.dll -> - // *.dll.a). However, this could already be an import library - // (see search_library()). - // - // @@ It could also be a DLL which we should try to link - // directly. We cannot handle it until we have the group - // support: we will check if there is an implib in - // the group, if there is, then we link to it. Otherwise, - // we assumy this is a DLL without the import library and - // try to link to it directly. - // - const char* e (p.extension ()); - if (e == nullptr || e[0] != 'a' || e[1] != '\0') - p += ".a"; - } - } - - sargs.push_back (move (p).string ()); // string()&& + sargs.push_back (relative (ppt->path ()).string ()); // string()&& // If this is a static library, link all the libraries it depends // on, recursively. @@ -1372,25 +1423,6 @@ namespace build2 return target_state::changed; } - target_state link:: - perform_clean (action a, target& xt) - { - file& t (static_cast (xt)); - - scope& rs (t.root_scope ()); - const string& tsys (cast (rs["cxx.target.system"])); - - const char* e (nullptr); - - if (link_type (t) == type::so) - { - if (tsys == "mingw32") - e = "+.a"; // Import library (*.dll.a). - } - - return clean_extra (a, t, {"+.d", e}); - } - link link::instance; } } diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx index fba52b8..b4ce142 100644 --- a/build2/cxx/module.cxx +++ b/build2/cxx/module.cxx @@ -255,13 +255,21 @@ namespace build2 // Configure "installability" of our target types. // - { - using build2::install::path; + using namespace install; + + install_path (b, dir_path ("include")); // Into install.include. + install_path (b, dir_path ("include")); + install_path (b, dir_path ("include")); + install_path (b, dir_path ("include")); - path (b, dir_path ("include")); // Install into install.include. - path (b, dir_path ("include")); - path (b, dir_path ("include")); - path (b, dir_path ("include")); + // Create additional target types for certain target platforms. + // + const string& tclass (cast (r["cxx.target.class"])); + + if (tclass == "windows") + { + const target_type& dll (b.derive_target_type ("dll").first); + install_path (dll, b, dir_path ("bin")); } return true; diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx index f3a99f9..e2bad9a 100644 --- a/build2/dist/rule.cxx +++ b/build2/dist/rule.cxx @@ -36,7 +36,9 @@ namespace build2 if (p.proj () != nullptr) continue; - // If we can, go inside see-through groups. + // If we can, go inside see-through groups. Note that here we are + // not going into ad hoc groups but maybe we should (which would + // have to be done after match()). // if (p.type ().see_through && i.enter_group ()) continue; diff --git a/build2/install/module.cxx b/build2/install/module.cxx index 1ebbb17..15c6ea8 100644 --- a/build2/install/module.cxx +++ b/build2/install/module.cxx @@ -182,9 +182,9 @@ namespace build2 // Configure "installability" for built-in target types. // - path (b, dir_path ("doc")); // Install into install.doc. - path (b, dir_path ("man")); // Install into install.man. - path (b, dir_path ("man1")); // Install into install.man1. + install_path (b, dir_path ("doc")); // Install into install.doc. + install_path (b, dir_path ("man")); // Install into install.man. + install_path (b, dir_path ("man1")); // Install into install.man1. return true; } diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 24c1df0..cbfb105 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -18,9 +18,9 @@ namespace build2 { namespace install { - // Lookup the install or install.* variable. Return NULL if - // not found or if the value is the special 'false' name (which - // means do not install). T is either scope or target. + // Lookup the install or install.* variable. Return NULL if not found or + // if the value is the special 'false' name (which means do not install). + // T is either scope or target. // template static const dir_path* @@ -131,8 +131,11 @@ namespace build2 // run standard search_and_match()? Will need an indicator // that it was forced (e.g., [install]) for filter() below. // - for (prerequisite_member p: group_prerequisite_members (a, t)) + auto r (group_prerequisite_members (a, t)); + for (auto i (r.begin ()); i != r.end (); ++i) { + prerequisite_member p (*i); + // Ignore unresolved targets that are imported from other projects. // We are definitely not installing those. // @@ -145,7 +148,7 @@ namespace build2 if (pt == nullptr) continue; - // See if the user instructed us not to install it. + // See if were explicitly instructed not to install this target. // auto l ((*pt)["install"]); if (l && cast (l).string () == "false") @@ -163,6 +166,11 @@ namespace build2 t.prerequisite_targets.push_back (pt); else unmatch (a, *pt); // No intent to execute. + + // Skip members of ad hoc groups. We handle them explicitly below. + // + if (pt->adhoc_group ()) + i.leave_group (); } // This is where we diverge depending on the operation. In the @@ -275,8 +283,11 @@ namespace build2 // install // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // static void - install (const install_dir& base, file& t) + install (const install_dir& base, file& t, bool verbose = true) { path reld (relative (base.dir)); path relf (relative (t.path ())); @@ -299,7 +310,7 @@ namespace build2 if (verb >= 2) print_process (args); - else if (verb) + else if (verb && verbose) text << "install " << t; try @@ -390,27 +401,44 @@ namespace build2 } target_state file_rule:: - perform_install (action a, target& t) + perform_install (action a, target& xt) { - file& ft (static_cast (t)); - assert (!ft.path ().empty ()); // Should have been assigned by update. + file& t (static_cast (xt)); + assert (!t.path ().empty ()); // Should have been assigned by update. + + scope& bs (t.base_scope ()); + + auto install_target = [&bs](file& t, const dir_path& d, bool verbose) + { + // Resolve and, if necessary, create target directory. + // + install_dir id (resolve (bs, d)); + + // Override mode if one was specified. + // + if (auto l = t["install.mode"]) + id.mode = cast (l); + + install (id, t, verbose); + }; // First handle installable prerequisites. // target_state r (execute_prerequisites (a, t)); - // Resolve and, if necessary, create target directory. + // Then installable ad hoc group members, if any. // - install_dir d ( - resolve (t.base_scope (), - cast (t["install"]))); // We know it's there. + for (target* m (t.member); m != nullptr; m = m->member) + { + if (const dir_path* d = lookup (*m, "install")) + install_target (static_cast (*m), *d, false); + } - // Override mode if one was specified. + // Finally install the target itself (since we got here we know the + // install variable is there). // - if (auto l = t["install.mode"]) - d.mode = cast (l); + install_target (t, cast (t["install"]), true); - install (d, ft); return (r |= target_state::changed); } } diff --git a/build2/install/utility b/build2/install/utility index 3d3b1a1..1abed27 100644 --- a/build2/install/utility +++ b/build2/install/utility @@ -16,23 +16,35 @@ namespace build2 { // Set install path, mode for a target type. // - template inline void - path (scope& s, dir_path d) + install_path (const target_type& tt, scope& s, dir_path d) { - auto r (s.target_vars[T::static_type]["*"].assign ("install")); + auto r (s.target_vars[tt]["*"].assign ("install")); if (r.second) // Already set by the user? r.first.get () = move (d); } template inline void - mode (scope& s, string m) + install_path (scope& s, dir_path d) + { + return install_path (T::static_type, s, d); + } + + inline void + install_mode (const target_type& tt, scope& s, string m) { - auto r (s.target_vars[T::static_type]["*"].assign ("install.mode")); + auto r (s.target_vars[tt]["*"].assign ("install.mode")); if (r.second) // Already set by the user? r.first.get () = move (m); } + + template + inline void + install_mode (scope& s, string m) + { + return install_mode (T::static_type, s, m); + } } } diff --git a/build2/parser.cxx b/build2/parser.cxx index 9d11d74..20cb75f 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -1112,31 +1112,6 @@ namespace build2 fail (t) << "expected newline instead of " << t; } - static target* - derived_factory (const target_type& t, - dir_path d, - dir_path o, - string n, - const string* e) - { - // Pass our type to the base factory so that it can detect that it is - // being called to construct a derived target. This can be used, for - // example, to decide whether to "link up" to the group. - // - // One exception: if we are derived from a derived target type, the this - // logic will lead to infinite recursion. In this case get the ultimate - // base. - // - const target_type* bt (t.base); - for (; bt->factory == &derived_factory; bt = bt->base) ; - - target* r (bt->factory (t, move (d), move (o), move (n), e)); - r->derived_type = &t; - return r; - } - - constexpr const char derived_ext_var[] = "extension"; - void parser:: define (token& t, type& tt) { @@ -1167,29 +1142,9 @@ namespace build2 if (bt == nullptr) fail (t) << "unknown target type " << bn; - unique_ptr dt (new target_type (*bt)); - dt->base = bt; - dt->factory = &derived_factory; - - // Override extension derivation function: we most likely don't want - // to use the same default as our base (think cli: file). But, if our - // base doesn't use extensions, then most likely neither do we (think - // foo: alias). - // - if (bt->extension != nullptr) - dt->extension = &target_extension_var; - - target_type& rdt (*dt); // Save a non-const reference to the object. - - auto pr (scope_->target_types.emplace (dn, target_type_ref (move (dt)))); - - if (!pr.second) + if (!scope_->derive_target_type (move (dn), *bt).second) fail (dnl) << "target type " << dn << " already define in this scope"; - // Patch the alias name to use the map's key storage. - // - rdt.name = pr.first->first.c_str (); - next (t, tt); // Get newline. } else diff --git a/build2/scope b/build2/scope index ad9c3a0..3649aa1 100644 --- a/build2/scope +++ b/build2/scope @@ -218,6 +218,20 @@ namespace build2 const target_type* find_target_type (name&, const string*& ext) const; + // Dynamically derive a new target type from an existing one. Return the + // reference to the target type and an indicator of whether it was + // actually created. + // + pair, bool> + derive_target_type (const string& name, const target_type& base); + + template + pair, bool> + derive_target_type (const string& name) + { + return derive_target_type (name, T::static_type); + } + // Rules. // public: diff --git a/build2/scope.cxx b/build2/scope.cxx index 3ca3db5..c9a8ab9 100644 --- a/build2/scope.cxx +++ b/build2/scope.cxx @@ -517,6 +517,74 @@ namespace build2 return r; } + static target* + derived_tt_factory (const target_type& t, + dir_path d, + dir_path o, + string n, + const string* e) + { + // Pass our type to the base factory so that it can detect that it is + // being called to construct a derived target. This can be used, for + // example, to decide whether to "link up" to the group. + // + // One exception: if we are derived from a derived target type, then this + // logic would lead to infinite recursion. So in this case get the + // ultimate base. + // + const target_type* bt (t.base); + for (; bt->factory == &derived_tt_factory; bt = bt->base) ; + + target* r (bt->factory (t, move (d), move (o), move (n), e)); + r->derived_type = &t; + return r; + } + + constexpr const char derived_tt_ext_var[] = "extension"; + + pair, bool> scope:: + derive_target_type (const string& name, const target_type& base) + { + // @@ Looks like we may need the ability to specify a fixed extension + // (which will be used to compare existing targets and not just + // search for existing files that is handled by the target_type:: + // extension hook). See the file_factory() for details. We will + // probably need to specify it as part of the define directive (and + // have the ability to specify empty). + // + // Currently, if we define myfile{}: file{}, then myfile{foo} and + // myfile{foo.x} are the same target. + // + // @@ Also, if derived from file{}, then we use its print function + // which always prints extension by default (e.g., we get + // dll{libhello.dll}). + // + + unique_ptr dt (new target_type (base)); + dt->base = &base; + dt->factory = &derived_tt_factory; + + // Override extension derivation function: we most likely don't want + // to use the same default as our base (think cli: file). But, if our + // base doesn't use extensions, then most likely neither do we (think + // foo: alias). + // + if (base.extension != nullptr) + dt->extension = &target_extension_var; + + target_type& rdt (*dt); // Save a non-const reference to the object. + + auto pr (target_types.emplace (name, target_type_ref (move (dt)))); + + // Patch the alias name to use the map's key storage. + // + if (pr.second) + rdt.name = pr.first->first.c_str (); + + return pair, bool> ( + pr.first->second.get (), pr.second); + } + // scope_map // scope_map scopes; diff --git a/build2/search.cxx b/build2/search.cxx index bff69e8..384bd55 100644 --- a/build2/search.cxx +++ b/build2/search.cxx @@ -39,7 +39,7 @@ namespace build2 } } - // Prerequisite's out directory can be on of the following: + // Prerequisite's out directory can be one of the following: // // empty This means out is undetermined and we simply search for a // target that is in the out tree which happens to be indicated diff --git a/build2/target b/build2/target index 8d2a22d..0205052 100644 --- a/build2/target +++ b/build2/target @@ -146,7 +146,8 @@ namespace build2 // Target group to which this target belongs, if any. Note that we assume // that the group and all its members are in the same scope (for example, - // in variable lookup). We also don't support nested groups. + // in variable lookup). We also don't support nested groups (with a small + // exception for ad hoc groups; see below). // // The semantics of the interaction between the group and its members and // what it means to, say, update the group, is unspecified and is @@ -186,6 +187,63 @@ namespace build2 // target* group = nullptr; + // What has been described above is a "normal" group. That is, there is + // a dedicated target type that explicitly serves as a group and there + // is an explicit mechanism for discovering the group's members. + // + // However, sometimes, we may want to create a group on the fly out of a + // normal target type. For example, we have the libso{} target type. But + // on Windows a shared library consist of (at least) two files: the import + // library and the DLL itself. So we somehow need to be able to capture + // that. One approach would be to imply the presence of the second file. + // However, that means that a lot of generic rules (e.g., clean, install, + // etc) will need to know about this special semantics on Windows. Also, + // there would be no convenient way to customize things like extensions, + // etc (for which we use target-specific variables). In other words, it + // would be much easier and more consistent to make these extra files + // proper targets. + // + // So to support this requirement we have "ad hoc" groups. The idea is + // that any target can be turned (by the rule that matched it) into an ad + // hoc group by chaining several targets. Ad hoc groups have a more + // restricted semantics compared to the normal groups. In particular: + // + // - The ad hoc group itself is in a sense its first/primary target. + // + // - Group member's recipes should be set to group_recipe by the group's + // rule. + // + // - Members are discovered lazily, they are only known after the group's + // rule's apply() call. + // + // - Members cannot be used as prerequisites but can be used as targets + // - (e.g., to set variables, etc). + // + // - Members don't have prerequisites. + // + // - Ad hoc group cannot have sub group (of any kind) though an ad hoc + // group can be a sub-group of a normal group. + // + // - Member variable lookup skips the ad hoc group (since the group is + // the first member, this is normally what we want). + // + target* member = nullptr; + + bool + adhoc_group () const + { + // An ad hoc group can be a member of a normal group. + // + return member != nullptr && + (group == nullptr || group->member == nullptr); + } + + bool + adhoc_member () const + { + return group != nullptr && group->member != nullptr; + } + public: virtual ~target () = default; @@ -454,7 +512,12 @@ namespace build2 typedef target::prerequisites_type prerequisites_type; explicit - group_prerequisites (target& t): t_ (t) {} + group_prerequisites (target& t) + : t_ (t), + g_ (t_.group == nullptr || + t_.group->member != nullptr || // Ad hoc group member. + t_.group->prerequisites.empty () + ? nullptr : t_.group) {} struct iterator { @@ -467,8 +530,8 @@ namespace build2 typedef std::bidirectional_iterator_tag iterator_category; iterator () {} - iterator (target* t, prerequisites_type* c, base_iterator i) - : t_ (t), c_ (c), i_ (i) {} + iterator (target* t, target* g, prerequisites_type* c, base_iterator i) + : t_ (t), g_ (g), c_ (c), i_ (i) {} iterator& operator++ () @@ -489,7 +552,7 @@ namespace build2 { if (i_ == c_->begin () && c_ == &t_->prerequisites) { - c_ = &t_->group->prerequisites; + c_ = &g_->prerequisites; i_ = c_->end (); } @@ -506,7 +569,7 @@ namespace build2 friend bool operator== (const iterator& x, const iterator& y) { - return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_; + return x.t_ == y.t_ && x.g_ == y.g_ && x.c_ == y.c_ && x.i_ == y.i_; } friend bool @@ -514,6 +577,7 @@ namespace build2 private: target* t_ = nullptr; + target* g_ = nullptr; prerequisites_type* c_ = nullptr; base_iterator i_; }; @@ -523,16 +587,15 @@ namespace build2 iterator begin () const { - auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty () - ? *t_.group : t_).prerequisites); - return iterator (&t_, &c, c.begin ()); + auto& c ((g_ != nullptr ? *g_ : t_).prerequisites); + return iterator (&t_, g_, &c, c.begin ()); } iterator end () const { auto& c (t_.prerequisites); - return iterator (&t_, &c, c.end ()); + return iterator (&t_, g_, &c, c.end ()); } reverse_iterator @@ -545,11 +608,12 @@ namespace build2 size () const { return t_.prerequisites.size () + - (t_.group != nullptr ? t_.group->prerequisites.size () : 0); + (g_ != nullptr ? g_->prerequisites.size () : 0); } private: target& t_; + target* g_; }; // A member of a prerequisite. If 'target' is NULL, then this is the @@ -618,12 +682,12 @@ namespace build2 } // A "range" that presents a sequence of prerequisites (e.g., from - // group_prerequisites()) as a sequence of prerequisite_member's. For - // each group prerequisite you will "see" either the prerequisite - // itself or all its members, depending on the default iteration - // mode of the target group type. You can skip the rest of the - // group members with leave_group() and you can force iteration - // over the members with enter_group(). Usage: + // group_prerequisites()) as a sequence of prerequisite_member's. For each + // group prerequisite you will "see" either the prerequisite itself or all + // its members, depending on the default iteration mode of the target group + // type (ad hoc groups are always see through). You can skip the rest of the + // group members with leave_group() and you can force iteration over the + // members with enter_group(). Usage: // // for (prerequisite_member pm: prerequisite_members (a, ...)) // @@ -670,48 +734,37 @@ namespace build2 iterator (): r_ (nullptr) {} iterator (const prerequisite_members_range* r, const base_iterator& i) - : r_ (r), i_ (i), g_ {nullptr, 0} + : r_ (r), i_ (i), g_ {nullptr, 0}, k_ (nullptr) { if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) - { - bool r (switch_members ()); - assert (r); // Group could not be resolved. - } + switch_mode (); } iterator& operator++ (); iterator operator++ (int) {iterator r (*this); operator++ (); return r;} - // Skip iterating over the rest of this group's members, if any. - // Note that the only valid operation after this call is to - // increment the iterator. + // Skip iterating over the rest of this group's members, if any. Note + // that the only valid operation after this call is to increment the + // iterator. Note that it can be used on ad hoc groups. // void - leave_group () - { - // Pretend we are on the last member of some group. - // - j_ = 0; - g_.count = 1; - } + leave_group (); // Iterate over this group's members. Return false if the member - // information is not available. Similar to leave_group(), you - // should increment the iterator after calling this function - // (provided it returned true). + // information is not available. Similar to leave_group(), you should + // increment the iterator after calling this function (provided it + // returned true). Note that it cannot be used on ad hoc groups (which + // will be always be entered). // bool - enter_group () - { - bool r (switch_members ()); - if (r) - --j_; // Compensate for the increment that will follow. - return r; - } + enter_group (); value_type operator* () const { - return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; + target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return value_type {*i_, t}; } pointer operator-> () const @@ -720,8 +773,10 @@ namespace build2 std::is_trivially_destructible::value, "prerequisite_member is not trivially destructible"); - return new (&m_) - value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; + target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return new (&m_) value_type {*i_, t}; } friend bool @@ -729,21 +784,34 @@ namespace build2 { return x.i_ == y.i_ && x.g_.count == y.g_.count && - (x.g_.count == 0 || x.j_ == y.j_); + (x.g_.count == 0 || x.j_ == y.j_) && + x.k_ == y.k_; } friend bool operator!= (const iterator& x, const iterator& y) {return !(x == y);} + // What we have here is a state for three nested iteration modes (and + // no, I am not proud of it). The innermost mode is iteration over an ad + // hoc group (k_). Then we have iteration over a normal group (g_ and + // j_). Finally, at the outer level, we have the range itself (i_). + // + // The ad hoc iteration is peculiar in that we only switch to this mode + // once the caller tries to increment past the group itself (which is + // the primary/first member). The reason for this is that the members + // will normally only be known once the caller searched and matched + // the group. + // private: - bool - switch_members (); + void + switch_mode (); private: const prerequisite_members_range* r_; base_iterator i_; group_view g_; size_t j_; // 1-based index, to support enter_group(). + target* k_; // Current member of ad hoc group or NULL. mutable std::aligned_storage::type m_; }; @@ -1173,7 +1241,6 @@ namespace build2 // target* search_file (const prerequisite_key&); - } #include diff --git a/build2/target.cxx b/build2/target.cxx index d64b0be..2e500aa 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -125,13 +125,21 @@ namespace build2 if (auto p = vars.find (var)) r.first = lookup (p, &vars); + target* g (nullptr); + if (!r.first) { ++r.second; - if (group != nullptr) + + // Skip looking up in the ad hoc group, which is semantically the + // first/primary member. + // + if ((g = group == nullptr + ? nullptr + : group->adhoc_group () ? group->group : group)) { - if (auto p = group->vars.find (var)) - r.first = lookup (p, &group->vars); + if (auto p = g->vars.find (var)) + r.first = lookup (p, &g->vars); } } @@ -143,8 +151,8 @@ namespace build2 var, &type (), &name, - group != nullptr ? &group->type () : nullptr, - group != nullptr ? &group->name : nullptr)); + g != nullptr ? &g->type () : nullptr, + g != nullptr ? &g->name : nullptr)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; @@ -348,7 +356,7 @@ namespace build2 const path_type& ep (path ()); if (ep.empty ()) - path (p); + path (p); else if (p != ep) fail << "path mismatch for target " << *this << info << "assigned '" << ep << "'" << @@ -467,22 +475,27 @@ namespace build2 false }; - template + template static target* - file_factory (const target_type&, + file_factory (const target_type& tt, dir_path d, dir_path o, string n, const string* e) { - // The file target type doesn't imply any extension. So if one wasn't - // specified, set it to empty rather than unspecified. In other words, we - // always treat file{foo} as file{foo.}. + // A generic file target type doesn't imply any extension while a very + // specific one (say man1) may have a fixed extension. So if one wasn't + // specified and this is not a dynamically derived target type, then set + // it to fixed ext rather than unspecified. For file{} we make it empty + // which means we treat file{foo} as file{foo.}. // - return new T (move (d), - move (o), - move (n), - (e != nullptr ? e : &extension_pool.find (""))); + return new T ( + move (d), + move (o), + move (n), + (e != nullptr || ext == nullptr || tt.factory != &file_factory + ? e + : &extension_pool.find (ext))); } constexpr const char file_ext_var[] = "extension"; @@ -492,7 +505,7 @@ namespace build2 { "file", &path_target::static_type, - &file_factory, + &file_factory, &target_extension_var, &target_print_1_ext_verb, // Print extension even at verbosity level 0. &search_file, @@ -532,6 +545,19 @@ namespace build2 false }; + static target* + buildfile_factory (const target_type&, + dir_path d, + dir_path o, + string n, + const string* e) + { + if (e == nullptr) + e = &extension_pool.find (n == "buildfile" ? "" : "build"); + + return new buildfile (move (d), move (o), move (n), e); + } + static const string* buildfile_target_extension (const target_key& tk, scope&) { @@ -545,7 +571,7 @@ namespace build2 { "buildfile", &file::static_type, - &file_factory, + &buildfile_factory, &buildfile_target_extension, nullptr, &search_file, @@ -556,7 +582,7 @@ namespace build2 { "doc", &file::static_type, - &file_factory, + &file_factory, // No extension by default. &target_extension_var, // Same as file. &target_print_1_ext_verb, // Same as file. &search_file, @@ -592,7 +618,7 @@ namespace build2 { "man1", &man::static_type, - &file_factory, + &file_factory, &target_extension_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &search_file, diff --git a/build2/target.ixx b/build2/target.ixx index d89ed08..5cf5ca9 100644 --- a/build2/target.ixx +++ b/build2/target.ixx @@ -21,6 +21,11 @@ namespace build2 if (target == nullptr) return prerequisite; + // An ad hoc group member cannot be used as a prerequisite (use the whole + // group instead). + // + assert (!target->adhoc_member ()); + // The use of the group's prerequisite scope is debatable. // scope& s (prerequisite.get ().scope); @@ -36,24 +41,32 @@ namespace build2 inline auto prerequisite_members_range::iterator:: operator++ () -> iterator& { - if (g_.count != 0) + if (k_ != nullptr) // Iterating over an ad hoc group. + k_ = k_->member; + else if (r_->members_) { - if (++j_ <= g_.count) - return *this; - - // Switch back to prerequisite iteration mode. + // Get the target if one has been resolved and see if it's an ad hoc + // group. If so, switch to the ad hoc mode. // - g_.count = 0; + target* t (g_.count != 0 + ? j_ != 0 ? g_.members[j_ - 1] : nullptr // enter_group() + : i_->get ().target); + if (t != nullptr && t->member != nullptr) + k_ = t->member; } - ++i_; + if (k_ == nullptr && g_.count != 0) // Iterating over a normal group. + { + if (++j_ > g_.count) + g_.count = 0; + } - // Switch to member iteration mode. - // - if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) + if (k_ == nullptr && g_.count == 0) // Iterating over the range. { - bool r (switch_members ()); - assert (r); // Group could not be resolved. + ++i_; + + if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) + switch_mode (); } return *this; @@ -61,25 +74,65 @@ namespace build2 template inline bool prerequisite_members_range::iterator:: - switch_members () + enter_group () { - do + // First see if we are about to enter an ad hoc group (the same code as in + // operator++() above). + // + target* t (g_.count != 0 + ? j_ != 0 ? g_.members[j_ - 1] : nullptr + : i_->get ().target); + + if (t != nullptr && t->member != nullptr) + k_ = t->member; + else { + // Otherwise assume it is a normal group. + // g_ = resolve_group_members (r_->a_, search (*i_)); - // If members are not know, iterate over the group as itself. - // - if (g_.members == nullptr) + if (g_.members == nullptr) // Members are not know. { g_.count = 0; return false; } + + if (g_.count != 0) // Group is not empty. + j_ = 0; // Account for the increment that will follow. } - while (g_.count == 0 && // Skip empty groups. - ++i_ != r_->e_ && - i_->get ().type.see_through); - j_ = 1; // Start from the first group member. + return true; } + + template + inline void prerequisite_members_range::iterator:: + leave_group () + { + // First see if we are about to enter an ad hoc group (the same code as in + // operator++() above). + // + if (k_ == nullptr) + { + target* t (g_.count != 0 + ? j_ != 0 ? g_.members[j_ - 1] : nullptr + : i_->get ().target); + if (t != nullptr && t->member != nullptr) + k_ = t->member; + } + + if (k_ != nullptr) + { + // Skip until the last element (next increment will reach the end). + // + for (; k_->member != nullptr; k_ = k_->member) ; + } + else + { + // Pretend we are on the last member of a normal group. + // + j_ = 0; + g_.count = 1; + } + } } diff --git a/build2/target.txx b/build2/target.txx index fe21016..dc627a6 100644 --- a/build2/target.txx +++ b/build2/target.txx @@ -9,6 +9,30 @@ namespace build2 { + // prerequisite_members_range + // + template + void prerequisite_members_range::iterator:: + switch_mode () + { + // A group could be empty, so we may have to iterate. + // + do + { + g_ = resolve_group_members (r_->a_, search (*i_)); + assert (g_.members == nullptr); // Group could not be resolved. + + if (g_.count != 0) // Skip empty see through groups. + { + j_ = 1; // Start from the first group member. + break; + } + } + while (++i_ != r_->e_ && i_->get ().type.see_through); + } + + // + // template const string* target_extension_fix (const target_key&, scope&) -- cgit v1.1