aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-06-26 16:06:54 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-06-26 16:06:54 +0200
commit4d1c02b736f4c1e827b11085cdc83ce4b46c03d1 (patch)
tree8d6a8318c23b1e9085d73c36843c90e875a52095
parent70d00b9f7f3266c1962f6d5a6fc8de1866c67949 (diff)
Add notion of ad hoc group, use to handle DLL/import library
-rw-r--r--build2/algorithm.cxx23
-rw-r--r--build2/bin/module.cxx12
-rw-r--r--build2/cxx/link3
-rw-r--r--build2/cxx/link.cxx280
-rw-r--r--build2/cxx/module.cxx20
-rw-r--r--build2/dist/rule.cxx4
-rw-r--r--build2/install/module.cxx6
-rw-r--r--build2/install/rule.cxx64
-rw-r--r--build2/install/utility22
-rw-r--r--build2/parser.cxx47
-rw-r--r--build2/scope14
-rw-r--r--build2/scope.cxx68
-rw-r--r--build2/search.cxx2
-rw-r--r--build2/target165
-rw-r--r--build2/target.cxx64
-rw-r--r--build2/target.ixx95
-rw-r--r--build2/target.txx24
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<file*> (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<exe> (b, dir_path ("bin")); // Install into install.bin.
+ using namespace install;
+
+ install_path<exe> (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<libso> (b, dir_path ("lib")); // Install into install.lib.
+ install_path<libso> (b, dir_path ("lib")); // Install into install.lib.
+
+ //@@ For Windows, libso{} is an import library and shouldn't be exec.
- install::path<liba> (b, dir_path ("lib")); // Install into install.lib.
- install::mode<liba> (b, "644");
+ install_path<liba> (b, dir_path ("lib")); // Install into install.lib.
+ install_mode<liba> (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<path_target&> (xt));
- scope& rs (t.root_scope ());
+ scope& bs (t.base_scope ());
+ scope& rs (*bs.root_scope ());
+ const string& tsys (cast<string> (rs["cxx.target.system"]));
const string& tclass (cast<string> (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<string> (rs["bin.ar.id"]) == "msvc")
+ {
+ e = "lib";
+ }
+ else
+ {
+ p = "lib";
+ e = "a";
+ }
- const char* p (l ? cast<string> (l).c_str () : nullptr);
- const char* e (nullptr);
+ if (auto l = t["bin.libprefix"])
+ p = cast<string> (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<string> (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<string> (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<file*> (t.member);
+ }
+ else
+ {
+ t.member = dll = static_cast<file*> (
+ &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<path> (rs["config.bin.ar"]).string ().c_str ();
-
- if (aid == "msvc")
+ case type::e:
{
- if (verb < 3)
- args.push_back ("/NOLOGO");
+ args[0] = cast<path> (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<path> (rs["config.cxx"]).string ().c_str ();
-
- if (cid == "msvc")
- {
- uint64_t cver (cast<uint64_t> (rs["cxx.version.major"]));
-
- if (verb < 3)
- args.push_back ("/nologo");
+ if (cid == "msvc")
+ {
+ uint64_t cver (cast<uint64_t> (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<path> (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<path> (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<file*> (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<liba> ()) ||
(ppt = so = pt->is_a<libso> ()))))
{
- 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<file&> (xt));
-
- scope& rs (t.root_scope ());
- const string& tsys (cast<string> (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<hxx> (b, dir_path ("include")); // Into install.include.
+ install_path<ixx> (b, dir_path ("include"));
+ install_path<txx> (b, dir_path ("include"));
+ install_path<h> (b, dir_path ("include"));
- path<hxx> (b, dir_path ("include")); // Install into install.include.
- path<ixx> (b, dir_path ("include"));
- path<txx> (b, dir_path ("include"));
- path<h> (b, dir_path ("include"));
+ // Create additional target types for certain target platforms.
+ //
+ const string& tclass (cast<string> (r["cxx.target.class"]));
+
+ if (tclass == "windows")
+ {
+ const target_type& dll (b.derive_target_type<file> ("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<doc> (b, dir_path ("doc")); // Install into install.doc.
- path<man> (b, dir_path ("man")); // Install into install.man.
- path<man1> (b, dir_path ("man1")); // Install into install.man1.
+ install_path<doc> (b, dir_path ("doc")); // Install into install.doc.
+ install_path<man> (b, dir_path ("man")); // Install into install.man.
+ install_path<man1> (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 <typename T>
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<dir_path> (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 <file> <dir>
//
+ // 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<file&> (t));
- assert (!ft.path ().empty ()); // Should have been assigned by update.
+ file& t (static_cast<file&> (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<string> (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<dir_path> (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<file&> (*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<string> (l);
+ install_target (t, cast<dir_path> (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 <typename T>
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 <typename T>
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 <typename T>
+ 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<target_type> 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<derived_ext_var, nullptr>;
-
- 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<reference_wrapper<const target_type>, bool>
+ derive_target_type (const string& name, const target_type& base);
+
+ template <typename T>
+ pair<reference_wrapper<const target_type>, 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<reference_wrapper<const target_type>, 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<target_type> 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<derived_tt_ext_var, nullptr>;
+
+ 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<reference_wrapper<const target_type>, 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<prerequisite_member>::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<sizeof (prerequisite_member),
alignof (prerequisite_member)>::type m_;
};
@@ -1173,7 +1241,6 @@ namespace build2
//
target*
search_file (const prerequisite_key&);
-
}
#include <build2/target.ixx>
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 <typename T>
+ template <typename T, const char* ext>
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<T, ext>
+ ? 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>,
+ &file_factory<file, file_ext_def>,
&target_extension_var<file_ext_var, file_ext_def>,
&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>,
+ &buildfile_factory,
&buildfile_target_extension,
nullptr,
&search_file,
@@ -556,7 +582,7 @@ namespace build2
{
"doc",
&file::static_type,
- &file_factory<doc>,
+ &file_factory<doc, file_ext_def>, // No extension by default.
&target_extension_var<file_ext_var, file_ext_def>, // 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<man1>,
+ &file_factory<man1, man1_ext>,
&target_extension_fix<man1_ext>,
&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<T>::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 <typename T>
inline bool prerequisite_members_range<T>::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 <typename T>
+ inline void prerequisite_members_range<T>::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 <typename T>
+ void prerequisite_members_range<T>::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 char* ext>
const string*
target_extension_fix (const target_key&, scope&)