aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-08-29 19:31:16 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-08-29 19:31:16 +0200
commit2720b45ef0ca9fd58c11fd9b4f000e1cf3a0819d (patch)
tree4505f65e4f997889e01d4b69bdf94257223d3dc6
parent47e89b188ac71627f43e0bb8c47ffe08f6c1b919 (diff)
Implement initial support for library versioning
Currently we only support platform-independent versions that get appended to the library name. The magic incantation is this: lib{foo}: bin.lib.version = @-1.2 This will produce libfoo-1.2.so, libfoo-1.2.dll, etc. In the future we will support things like this: lib{foo}: bin.lib.version = linux@1.2.3 freebsd@1.2 windows@1.2
-rw-r--r--build2/algorithm28
-rw-r--r--build2/algorithm.cxx102
-rw-r--r--build2/bin/init.cxx6
-rw-r--r--build2/cc/install10
-rw-r--r--build2/cc/install.cxx54
-rw-r--r--build2/cc/link33
-rw-r--r--build2/cc/link.cxx342
-rw-r--r--build2/dist/operation.cxx2
-rw-r--r--build2/install/rule51
-rw-r--r--build2/install/rule.cxx143
-rw-r--r--build2/target.cxx17
-rw-r--r--build2/utility.cxx2
12 files changed, 587 insertions, 203 deletions
diff --git a/build2/algorithm b/build2/algorithm
index 651ef24..9f43475 100644
--- a/build2/algorithm
+++ b/build2/algorithm
@@ -233,22 +233,32 @@ namespace build2
perform_clean_depdb (action, target&);
// Helper for custom perform(clean) implementations that cleans extra files
- // and directories (recursively) specified as a list of extensions. The
- // extension string can be NULL, in which case it is ignored. If the first
- // character is '/', then the resulting path is treated as a directory
- // rather than a file. Next can come zero or more '-' characters which
- // indicate the number of extensions that should stripped before the new
- // extension (if any) is added (so if you want to strip the extension,
- // specify "-"). For example:
+ // and directories (recursively) specified as a list of either absolute
+ // paths or "path derivation directives". The directive string can be NULL,
+ // or empty in which case it is ignored. If the last character in a
+ // directive is '/', then the resulting path is treated as a directory
+ // rather than a file. The directive can start with zero or more '-'
+ // characters which indicate the number of extensions that should be
+ // stripped before the new extension (if any) is added (so if you want to
+ // strip the extension, specify just "-"). For example:
//
- // clean_extra (a, t, {".d", "/.dlls", "-.dll"});
+ // clean_extra (a, t, {".d", ".dlls/", "-.dll"});
//
// The extra files/directories are removed first in the specified order
// followed by the ad hoc group member, then target itself, and, finally,
// the prerequisites in the reverse order.
//
+ // You can also clean extra files derived from adhoc group members.
+ //
target_state
- clean_extra (action, file&, initializer_list<const char*> extra_ext);
+ clean_extra (action, file&,
+ initializer_list<initializer_list<const char*>> extra);
+
+ inline target_state
+ clean_extra (action a, file& f, initializer_list<const char*> extra)
+ {
+ return clean_extra (a, f, {extra});
+ }
}
#include <build2/algorithm.ixx>
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index cae6645..6b28232 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -532,7 +532,9 @@ namespace build2
}
target_state
- clean_extra (action a, file& ft, initializer_list<const char*> es)
+ clean_extra (action a,
+ file& ft,
+ initializer_list<initializer_list<const char*>> extra)
{
// Clean the extras first and don't print the commands at verbosity level
// below 3. Note the first extra file/directory that actually got removed
@@ -542,58 +544,77 @@ namespace build2
bool ed (false);
path ep;
- for (const char* e: es)
+ auto clean = [&er, &ed, &ep] (file& f, initializer_list<const char*> es)
{
- if (e == nullptr)
- continue;
+ for (const char* e: es)
+ {
+ size_t n;
+ if (e == nullptr || (n = strlen (e)) == 0)
+ continue;
- bool d (*e == '/');
- if (d)
- ++e;
+ path p;
+ bool d;
- path p (ft.path ());
- for (; *e == '-'; ++e)
- p = p.base ();
+ if (path::traits::absolute (e))
+ {
+ p = path (e);
+ d = p.to_directory ();
+ }
+ else
+ {
+ if ((d = (e[n - 1] == '/')))
+ --n;
- p += e;
+ p = f.path ();
+ for (; *e == '-'; ++e)
+ p = p.base ();
- target_state r (target_state::unchanged);
+ p.append (e, n);
+ }
- if (d)
- {
- dir_path dp (path_cast<dir_path> (p));
+ target_state r (target_state::unchanged);
- switch (build2::rmdir_r (dp, true, 3))
+ if (d)
{
- case rmdir_status::success:
- {
- r = target_state::changed;
- break;
- }
- case rmdir_status::not_empty:
+ dir_path dp (path_cast<dir_path> (p));
+
+ switch (build2::rmdir_r (dp, true, 3))
{
- if (verb >= 3)
- text << dp << " is current working directory, not removing";
+ case rmdir_status::success:
+ {
+ r = target_state::changed;
+ break;
+ }
+ case rmdir_status::not_empty:
+ {
+ if (verb >= 3)
+ text << dp << " is current working directory, not removing";
+ break;
+ }
+ case rmdir_status::not_exist:
break;
}
- case rmdir_status::not_exist:
- break;
}
- }
- else
- {
- if (rmfile (p, 3))
- r = target_state::changed;
- }
+ else
+ {
+ if (rmfile (p, 3))
+ r = target_state::changed;
+ }
- if (r == target_state::changed && ep.empty ())
- {
- ed = d;
- ep = move (p);
+ if (r == target_state::changed && ep.empty ())
+ {
+ ed = d;
+ ep = move (p);
+ }
+
+ er |= r;
}
+ };
- er |= r;
- }
+ auto ei (extra.begin ()), ee (extra.end ());
+
+ if (ei != ee)
+ clean (ft, *ei++);
// Now clean the ad hoc group file members, if any.
//
@@ -604,6 +625,9 @@ namespace build2
if (fm == nullptr || fm->path ().empty ())
continue;
+ if (ei != ee)
+ clean (*fm, *ei++);
+
const path& f (fm->path ());
target_state r (rmfile (f, 3)
@@ -659,7 +683,7 @@ namespace build2
target_state
perform_clean (action a, target& t)
{
- return clean_extra (a, dynamic_cast<file&> (t), {});
+ return clean_extra (a, dynamic_cast<file&> (t), {nullptr});
}
target_state
diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx
index bf7dd69..6a417e7 100644
--- a/build2/bin/init.cxx
+++ b/build2/bin/init.cxx
@@ -4,6 +4,8 @@
#include <build2/bin/init>
+#include <map>
+
#include <butl/triplet>
#include <build2/scope>
@@ -79,6 +81,10 @@ namespace build2
v.insert<string> ("bin.lib.suffix");
v.insert<string> ("bin.exe.prefix");
v.insert<string> ("bin.exe.suffix");
+
+ v.insert<map<string, string>> ("bin.lib.version",
+ false,
+ variable_visibility::project);
}
// Configure.
diff --git a/build2/cc/install b/build2/cc/install
index e2be905..e8035b7 100644
--- a/build2/cc/install
+++ b/build2/cc/install
@@ -25,10 +25,16 @@ namespace build2
install (data&&, const link&);
virtual target*
- filter (action, target&, prerequisite_member) const;
+ filter (action, target&, prerequisite_member) const override;
virtual match_result
- match (action, target&, const string&) const;
+ match (action, target&, const string&) const override;
+
+ virtual void
+ install_extra (file&, const install_dir&) const override;
+
+ virtual bool
+ uninstall_extra (file&, const install_dir&) const override;
private:
const link& link_;
diff --git a/build2/cc/install.cxx b/build2/cc/install.cxx
index c62ea95..a69cc52 100644
--- a/build2/cc/install.cxx
+++ b/build2/cc/install.cxx
@@ -68,5 +68,59 @@ namespace build2
match_result r (link_.match (a, t, hint));
return r ? install::file_rule::match (a, t, "") : r;
}
+
+ void install::
+ install_extra (file& t, const install_dir& id) const
+ {
+ if (t.is_a<libs> () && tclass != "windows")
+ {
+ // Here we may have a bunch of symlinks that we need to install.
+ //
+ link::libs_paths lp (link_.derive_libs_paths (t));
+
+ auto ln = [&id, this] (const path& f, const path& l)
+ {
+ install_l (id, f.leaf (), l.leaf (), false);
+ };
+
+ const path& lk (lp.link);
+ const path& so (lp.soname);
+ const path& in (lp.interm);
+
+ const path* f (lp.real);
+
+ if (!in.empty ()) {ln (*f, in); f = &in;}
+ if (!so.empty ()) {ln (*f, so); f = &so;}
+ if (!lk.empty ()) {ln (*f, lk);}
+ }
+ }
+
+ bool install::
+ uninstall_extra (file& t, const install_dir& id) const
+ {
+ bool r (false);
+
+ if (t.is_a<libs> () && tclass != "windows")
+ {
+ // Here we may have a bunch of symlinks that we need to uninstall.
+ //
+ link::libs_paths lp (link_.derive_libs_paths (t));
+
+ auto rm = [&id, this] (const path& l)
+ {
+ return uninstall (id, nullptr, l.leaf (), false);
+ };
+
+ const path& lk (lp.link);
+ const path& so (lp.soname);
+ const path& in (lp.interm);
+
+ if (!lk.empty ()) r = rm (lk) || r;
+ if (!so.empty ()) r = rm (so) || r;
+ if (!in.empty ()) r = rm (in) || r;
+ }
+
+ return r;
+ }
}
}
diff --git a/build2/cc/link b/build2/cc/link
index 52ad4a4..cd8c10e 100644
--- a/build2/cc/link
+++ b/build2/cc/link
@@ -37,6 +37,39 @@ namespace build2
perform_clean (action, target&) const;
private:
+ friend class install;
+
+ // Shared library paths.
+ //
+ struct libs_paths
+ {
+ // If any (except real) is empty, then it is the same as the next
+ // one. Except for intermediate, for which empty indicates that it is
+ // not used.
+ //
+ // The libs{} path is always the real path. On Windows the link path
+ // is the import library.
+ //
+ // @@ TODO: change real to reference, make other const once cache the
+ // object.
+ //
+ path link; // What we link: libfoo.so
+ path soname; // SONAME: libfoo-1.so, libfoo.so.1
+ path interm; // Intermediate: libfoo.so.1.2
+ const path* real; // Real: libfoo.so.1.2.3
+
+ inline const path&
+ effect_link () const {return link.empty () ? effect_soname () : link;}
+
+ inline const path&
+ effect_soname () const {return soname.empty () ? *real : soname;}
+ };
+
+ libs_paths
+ derive_libs_paths (file&) const;
+
+ // Library handling.
+ //
void
append_libraries (strings&, file&, bool, scope&, lorder) const;
diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx
index d0e887e..c363a10 100644
--- a/build2/cc/link.cxx
+++ b/build2/cc/link.cxx
@@ -4,6 +4,7 @@
#include <build2/cc/link>
+#include <map>
#include <cstdlib> // exit()
#include <iostream> // cerr
@@ -169,6 +170,145 @@ namespace build2
return &t;
}
+ auto link::
+ derive_libs_paths (file& ls) const -> libs_paths
+ {
+ const char* ext (nullptr);
+ const char* pfx (nullptr);
+ const char* sfx (nullptr);
+
+ bool win (tclass == "windows");
+
+ if (win)
+ {
+ if (tsys == "mingw32")
+ pfx = "lib";
+
+ ext = "dll";
+ }
+ else if (tclass == "macosx")
+ {
+ pfx = "lib";
+ ext = "dylib";
+ }
+ else
+ {
+ pfx = "lib";
+ ext = "so";
+ }
+
+ if (auto l = ls["bin.lib.prefix"]) pfx = cast<string> (l).c_str ();
+ if (auto l = ls["bin.lib.suffix"]) sfx = cast<string> (l).c_str ();
+
+ // First sort out which extension we are using.
+ //
+ const string& e (ls.derive_extension (ext));
+
+ auto append_ext = [&e] (path& p)
+ {
+ if (!e.empty ())
+ {
+ p += '.';
+ p += e;
+ }
+ };
+
+ // Figure out the version.
+ //
+ string v;
+ using verion_map = map<string, string>;
+ if (const verion_map* m = cast_null<verion_map> (ls["bin.lib.version"]))
+ {
+ // First look for the target system.
+ //
+ auto i (m->find (tsys));
+
+ // Then look for the target class.
+ //
+ if (i == m->end ())
+ i = m->find (tclass);
+
+ // Then look for the wildcard. Since it is higly unlikely one can have
+ // a version that will work across platforms, this is only useful to
+ // say "all others -- no version".
+ //
+ if (i == m->end ())
+ i = m->find ("*");
+
+ // At this stage the only platform-specific version we support is the
+ // "no version" override.
+ //
+ if (i != m->end () && !i->second.empty ())
+ fail << i->first << "-specific bin.lib.version not yet supported";
+
+ // Finally look for the platform-independent version.
+ //
+ if (i == m->end ())
+ i = m->find ("");
+
+ // If we didn't find anything, fail. If the bin.lib.version was
+ // specified, then it should explicitly handle all the targets.
+ //
+ if (i == m->end ())
+ fail << "no version for " << ctg << " in bin.lib.version" <<
+ info << "considere adding " << tsys << "@<ver> or " << tclass
+ << "@<ver>";
+
+ v = i->second;
+ }
+
+ // Now determine the paths.
+ //
+ path lk, so, in;
+ const path* re (nullptr);
+
+ // We start with the basic path.
+ //
+ path b (ls.dir);
+ {
+ if (pfx == nullptr)
+ b /= ls.name;
+ else
+ {
+ b /= pfx;
+ b += ls.name;
+ }
+
+ if (sfx != nullptr)
+ b += sfx;
+ }
+
+ // On Windows the real path is to libs{} and the link path is to the
+ // import library.
+ //
+ if (win)
+ {
+ // 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.
+ //
+ lk = b;
+ append_ext (lk);
+
+ libi& li (static_cast<libi&> (*ls.member));
+ lk = li.derive_path (move (lk), tsys == "mingw32" ? "a" : "lib");
+ }
+ else if (!v.empty ())
+ {
+ lk = b;
+ append_ext (lk);
+ }
+
+ if (!v.empty ())
+ b += v;
+
+ re = &ls.derive_path (move (b));
+
+ return libs_paths {move (lk), move (so), move (in), re};
+ }
+
recipe link::
apply (action a, target& xt, const match_result&) const
{
@@ -182,13 +322,26 @@ namespace build2
otype lt (link_type (t));
lorder lo (link_order (bs, lt));
- // Derive file name from target name.
+ // Derive file name(s) and add ad hoc group members.
//
- if (t.path ().empty ())
+ auto add_adhoc = [a, &bs] (target& t, const char* type) -> file&
+ {
+ const target_type& tt (*bs.find_target_type (type));
+
+ if (t.member != nullptr) // Might already be there.
+ assert (t.member->type () == tt);
+ else
+ t.member = &search (tt, t.dir, t.out, t.name, nullptr, nullptr);
+
+ file& r (static_cast<file&> (*t.member));
+ r.recipe (a, group_recipe);
+ return r;
+ };
+
{
+ const char* e (nullptr); // Extension.
const char* p (nullptr); // Prefix.
const char* s (nullptr); // Suffix.
- const char* e (nullptr); // Extension.
switch (lt)
{
@@ -202,18 +355,13 @@ namespace build2
if (auto l = t["bin.exe.prefix"]) p = cast<string> (l).c_str ();
if (auto l = t["bin.exe.suffix"]) s = cast<string> (l).c_str ();
+ t.derive_path (e, p, s);
break;
}
case otype::a:
{
- // 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 or vice versa.
- //
- if (cast<string> (rs["bin.ar.id"]) == "msvc")
- {
+ if (cid == "msvc")
e = "lib";
- }
else
{
p = "lib";
@@ -223,94 +371,38 @@ namespace build2
if (auto l = t["bin.lib.prefix"]) p = cast<string> (l).c_str ();
if (auto l = t["bin.lib.suffix"]) s = cast<string> (l).c_str ();
+ t.derive_path (e, p, s);
break;
}
case otype::s:
{
- if (tclass == "macosx")
- {
- p = "lib";
- e = "dylib";
- }
- else if (tclass == "windows")
- {
- // On Windows libs{} is an ad hoc group. The libs{} itself is
- // the DLL and we add libi{} import library as its member (see
- // below).
- //
- if (tsys == "mingw32")
- p = "lib";
-
- e = "dll";
- }
- else
- {
- p = "lib";
- e = "so";
- }
-
- if (auto l = t["bin.lib.prefix"]) p = cast<string> (l).c_str ();
- if (auto l = t["bin.lib.suffix"]) s = cast<string> (l).c_str ();
+ // On Windows libs{} is an ad hoc group. The libs{} itself is the
+ // DLL and we add libi{} import library as its member.
+ //
+ if (tclass == "windows")
+ add_adhoc (t, "libi");
+ derive_libs_paths (t);
break;
}
}
-
- t.derive_path (e, p, s);
}
- // Add ad hoc group members.
+ // PDB
//
- auto add_adhoc = [a, &bs] (target& t, const char* type) -> file&
- {
- const target_type& tt (*bs.find_target_type (type));
-
- if (t.member != nullptr) // Might already be there.
- assert (t.member->type () == tt);
- else
- t.member = &search (tt, t.dir, t.out, t.name, nullptr, nullptr);
-
- file& r (static_cast<file&> (*t.member));
- r.recipe (a, group_recipe);
- return r;
- };
-
- if (tclass == "windows")
+ if (lt != otype::a &&
+ cid == "msvc" &&
+ (find_option ("/DEBUG", t, c_loptions, true) ||
+ find_option ("/DEBUG", t, x_loptions, true)))
{
- // Import library.
+ // Add after the import library if any.
//
- if (lt == otype::s)
- {
- file& imp (add_adhoc (t, "libi"));
-
- // 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.
- //
- if (imp.path ().empty ())
- imp.derive_path (t.path (), tsys == "mingw32" ? "a" : "lib");
- }
+ file& pdb (add_adhoc (t.member == nullptr ? t : *t.member, "pdb"));
- // PDB
+ // We call it foo.{exe,dll}.pdb rather than just foo.pdb because we
+ // can have both foo.exe and foo.dll in the same directory.
//
- if (lt != otype::a &&
- cid == "msvc" &&
- (find_option ("/DEBUG", t, c_loptions, true) ||
- find_option ("/DEBUG", t, x_loptions, true)))
- {
- // Add after the import library if any.
- //
- file& pdb (add_adhoc (t.member == nullptr ? t : *t.member, "pdb"));
-
- // We call it foo.{exe,dll}.pdb rather than just foo.pdb because we
- // can have both foo.exe and foo.dll in the same directory.
- //
- if (pdb.path ().empty ())
- pdb.derive_path (t.path (), "pdb");
- }
+ pdb.derive_path (t.path (), "pdb");
}
t.prerequisite_targets.clear (); // See lib pre-match in match() above.
@@ -964,6 +1056,10 @@ namespace build2
//
cstrings args {nullptr}; // Reserve one for config.bin.ar/config.x.
+ libs_paths paths;
+ if (lt == otype::s)
+ paths = derive_libs_paths (t);
+
// Storage.
//
string soname1, soname2;
@@ -1016,7 +1112,7 @@ namespace build2
//
if (lt == otype::s)
{
- const string& leaf (t.path ().leaf ().string ());
+ const string& leaf (paths.effect_soname ().leaf ().string ());
if (tclass == "macosx")
{
@@ -1413,7 +1509,7 @@ namespace build2
// Remove the target file if any of the subsequent actions fail. If we
// don't do that, we will end up with a broken build that is up-to-date.
//
- auto_rmfile rm (t.path ());
+ auto_rmfile rm (relt);
if (ranlib)
{
@@ -1445,17 +1541,52 @@ namespace build2
}
}
- // For Windows generate rpath-emulating assembly (unless updaing for
- // install).
- //
- if (lt == otype::e && tclass == "windows")
+ if (tclass == "windows")
{
- if (!for_install)
+ // For Windows generate rpath-emulating assembly (unless updaing for
+ // install).
+ //
+ if (lt == otype::e && !for_install)
windows_rpath_assembly (t, bs, lo,
cast<string> (rs[x_target_cpu]),
rpath_timestamp,
scratch);
}
+ else if (lt == otype::s)
+ {
+ // For shared libraries we may need to create a bunch of symlinks.
+ //
+ auto ln = [] (const path& f, const path& l)
+ {
+ // Note that we don't bother making the paths relative since they
+ // will only be seen at verbosity level 3.
+ //
+ if (verb >= 3)
+ text << "ln -sf " << f << ' ' << l;
+
+ try
+ {
+ if (file_exists (l, false)) // The -f part.
+ try_rmfile (l);
+
+ mksymlink (f, l);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to create symlink " << l << ": " << e.what ();
+ }
+ };
+
+ const path& lk (paths.link);
+ const path& so (paths.soname);
+ const path& in (paths.interm);
+
+ const path* f (paths.real);
+
+ if (!in.empty ()) {ln (f->leaf (), in); f = &in;}
+ if (!so.empty ()) {ln (f->leaf (), so); f = &so;}
+ if (!lk.empty ()) {ln (f->leaf (), lk);}
+ }
rm.cancel ();
@@ -1472,13 +1603,14 @@ namespace build2
{
file& t (static_cast<file&> (xt));
- initializer_list<const char*> e;
+ libs_paths paths;
+ initializer_list<initializer_list<const char*>> e;
switch (link_type (t))
{
case otype::a:
{
- e = {".d"};
+ e = {{".d"}};
break;
}
case otype::e:
@@ -1487,31 +1619,45 @@ namespace build2
{
if (tsys == "mingw32")
{
- e = {".d", "/.dlls", ".manifest.o", ".manifest"};
+ e = {{".d", ".dlls/", ".manifest.o", ".manifest"}};
}
else
{
// Assuming it's VC or alike. Clean up .ilk in case the user
// enabled incremental linking (note that .ilk replaces .exe).
//
- e = {".d", "/.dlls", ".manifest", "-.ilk"};
+ e = {{".d", ".dlls/", ".manifest", "-.ilk"}};
}
}
else
- e = {".d"};
+ e = {{".d"}};
break;
}
case otype::s:
{
- if (tclass == "windows" && tsys != "mingw32")
+ if (tclass == "windows")
{
// Assuming it's VC or alike. Clean up .exp and .ilk.
//
- e = {".d", ".exp", "-.ilk"};
+ // Note that .exp is based on the .lib, not .dll name. And with
+ // versioning their bases may not be the same.
+ //
+ if (tsys != "mingw32")
+ e = {{".d", "-.ilk"}, {"-.exp"}};
}
else
- e = {".d"};
+ {
+ // Here we can have a bunch of symlinks that we need to remove. If
+ // the paths are empty, then they will be ignored.
+ //
+ paths = derive_libs_paths (t);
+
+ e = {{".d",
+ paths.link.string ().c_str (),
+ paths.soname.string ().c_str (),
+ paths.interm.string ().c_str ()}};
+ }
break;
}
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
index bbd7357..d03fe28 100644
--- a/build2/dist/operation.cxx
+++ b/build2/dist/operation.cxx
@@ -397,7 +397,7 @@ namespace build2
// Delete old archive for good measure.
//
path ap (root / path (a));
- if (file_exists (ap))
+ if (file_exists (ap, false))
rmfile (ap);
// Use zip for .zip archives. Everything else goes to tar in the
diff --git a/build2/install/rule b/build2/install/rule
index 99ebd8a..56cd8f9 100644
--- a/build2/install/rule
+++ b/build2/install/rule
@@ -26,6 +26,8 @@ namespace build2
apply (action, target&, const match_result&) const;
};
+ struct install_dir;
+
class file_rule: public rule
{
public:
@@ -42,11 +44,52 @@ namespace build2
virtual recipe
apply (action, target&, const match_result&) const;
- static target_state
- perform_install (action, target&);
+ // Extra installation hooks.
+ //
+ using install_dir = install::install_dir;
+
+ virtual void
+ install_extra (file&, const install_dir&) const;
+
+ // Return true if anything was uninstalled.
+ //
+ virtual bool
+ uninstall_extra (file&, const install_dir&) const;
+
+ // Installation "commands".
+ //
+ // If verbose is false, then only print the command at verbosity level 2
+ // or higher.
+ //
+ public:
+ // Install a symlink: base/link -> target.
+ //
+ static void
+ install_l (const install_dir& base,
+ const path& target,
+ const path& link,
+ bool verbose);
+
+ // Uninstall a file or symlink:
+ //
+ // uninstall <target> <base>/ rm <base>/<target>.leaf (); name empty
+ // uninstall <target> <name> rm <base>/<name>; target can be NULL
+ //
+ // Return false if nothing has been removed (i.e., the file does not
+ // exist).
+ //
+ static bool
+ uninstall (const install_dir& base,
+ file* t,
+ const path& name,
+ bool verbose);
+
+ private:
+ target_state
+ perform_install (action, target&) const;
- static target_state
- perform_uninstall (action, target&);
+ target_state
+ perform_uninstall (action, target&) const;
};
}
}
diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx
index 257f14b..6753a23 100644
--- a/build2/install/rule.cxx
+++ b/build2/install/rule.cxx
@@ -218,12 +218,18 @@ namespace build2
return r;
};
}
+ else if (a.operation () == install_id)
+ return [this] (action a, target& t) {return perform_install (a, t);};
else
- return a.operation () == uninstall_id
- ? &perform_uninstall
- : &perform_install;
+ return [this] (action a, target& t) {return perform_uninstall (a, t);};
}
+ void file_rule::
+ install_extra (file&, const install_dir&) const {}
+
+ bool file_rule::
+ uninstall_extra (file&, const install_dir&) const {return false;}
+
struct install_dir
{
dir_path dir;
@@ -499,13 +505,60 @@ namespace build2
}
}
+ void file_rule::
+ install_l (const install_dir& base,
+ const path& target,
+ const path& link,
+ bool verbose)
+ {
+ path rell (relative (base.dir));
+ rell /= link;
+
+ // We can create a symlink directly without calling ln. This, however,
+ // won't work if we have sudo. Also, we would have to deal with existing
+ // destinations (ln's -f takes care of that). So we are just going to
+ // always use ln.
+ //
+ const char* args_a[] = {
+ base.sudo != nullptr ? base.sudo->c_str () : nullptr,
+ "ln",
+ "-sf",
+ target.string ().c_str (),
+ rell.string ().c_str (),
+ nullptr};
+
+ const char** args (&args_a[base.sudo == nullptr ? 1 : 0]);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb && verbose)
+ text << "install " << rell << " -> " << target;
+
+ try
+ {
+ process pr (args);
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
target_state file_rule::
- perform_install (action a, target& xt)
+ perform_install (action a, target& xt) const
{
file& t (static_cast<file&> (xt));
assert (!t.path ().empty ()); // Should have been assigned by update.
- auto install_target = [](file& t, const path& p, bool verbose)
+ auto install_target = [this] (file& t, const path& p, bool verbose)
{
bool n (!p.to_directory ());
dir_path d (n ? p.directory () : path_cast<dir_path> (p));
@@ -528,7 +581,10 @@ namespace build2
if (auto l = t["install.mode"])
id.mode = &cast<string> (l);
+ // Install the target and extras.
+ //
install (id, n ? p.leaf () : path (), t, verbose);
+ install_extra (t, id);
};
// First handle installable prerequisites.
@@ -607,10 +663,10 @@ namespace build2
}
else
{
- cstrings args {base.sudo->c_str (),
- "rmdir",
- reld.string ().c_str (),
- nullptr};
+ const char* args[] = {base.sudo->c_str (),
+ "rmdir",
+ reld.string ().c_str (),
+ nullptr};
if (verb >= 2)
print_process (args);
@@ -619,7 +675,7 @@ namespace build2
try
{
- process pr (args.data ());
+ process pr (args);
if (!pr.wait ())
throw failed ();
@@ -650,26 +706,21 @@ namespace build2
return r;
}
- // uninstall <file> <dir>/
- // uninstall <file> <file>
- //
- // Return false if nothing has been removed (i.e., the file does not
- // exist).
- //
- // If verbose is false, then only print the command at verbosity level 2
- // or higher.
- //
- static bool
+ bool file_rule::
uninstall (const install_dir& base,
+ file* t,
const path& name,
- file& t,
- bool verbose = true)
+ bool verbose)
{
- path f (base.dir / (name.empty () ? t.path ().leaf () : name));
+ assert (t != nullptr || !name.empty ());
+ path f (base.dir / (name.empty () ? t->path ().leaf () : name));
try
{
- if (!file_exists (f)) // May throw (e.g., EACCES).
+ // Note: don't follow symlinks so if the target is a dangling symlinks
+ // we will proceed to removing it.
+ //
+ if (!file_exists (f, false)) // May throw (e.g., EACCES).
return false;
}
catch (const system_error& e)
@@ -679,14 +730,20 @@ namespace build2
path relf (relative (f));
+ if (verb == 1 && verbose)
+ {
+ if (t != nullptr)
+ text << "uninstall " << t;
+ else
+ text << "uninstall " << relf;
+ }
+
// The same story as with uninstall -d.
//
if (base.sudo == nullptr)
{
if (verb >= 2)
text << "rm " << relf;
- else if (verb && verbose)
- text << "uninstall " << t;
try
{
@@ -699,20 +756,18 @@ namespace build2
}
else
{
- cstrings args {base.sudo->c_str (),
- "rm",
- "-f",
- relf.string ().c_str (),
- nullptr};
+ const char* args[] = {base.sudo->c_str (),
+ "rm",
+ "-f",
+ relf.string ().c_str (),
+ nullptr};
if (verb >= 2)
print_process (args);
- else if (verb && verbose)
- text << "uninstall " << t;
try
{
- process pr (args.data ());
+ process pr (args);
if (!pr.wait ())
throw failed ();
@@ -732,12 +787,12 @@ namespace build2
}
target_state file_rule::
- perform_uninstall (action a, target& xt)
+ perform_uninstall (action a, target& xt) const
{
file& t (static_cast<file&> (xt));
assert (!t.path ().empty ()); // Should have been assigned by update.
- auto uninstall_target = [](file& t, const path& p, bool verbose)
+ auto uninstall_target = [this] (file& t, const path& p, bool verbose)
-> target_state
{
bool n (!p.to_directory ());
@@ -747,12 +802,16 @@ namespace build2
//
install_dirs ids (resolve (t, d));
- // Remove the target itself.
+ // Remove extras and the target itself.
//
- target_state r (
- uninstall (ids.back (), n ? p.leaf () : path (), t, verbose)
- ? target_state::changed
- : target_state::unchanged);
+ const install_dir& id (ids.back ());
+
+ target_state r (uninstall_extra (t, id)
+ ? target_state::changed
+ : target_state::unchanged);
+
+ if (uninstall (id, &t, n ? p.leaf () : path (), verbose))
+ r |= target_state::changed;
// Clean up empty leading directories (in reverse).
//
@@ -761,7 +820,7 @@ namespace build2
//
for (auto i (ids.rbegin ()), j (i), e (ids.rend ()); i != e; j = ++i)
{
- if (uninstall (++j != e ? *j : *i, i->dir, verbose))
+ if (install::uninstall (++j != e ? *j : *i, i->dir, verbose))
r |= target_state::changed;
}
diff --git a/build2/target.cxx b/build2/target.cxx
index 7a33dbc..8541deb 100644
--- a/build2/target.cxx
+++ b/build2/target.cxx
@@ -351,17 +351,20 @@ namespace build2
const path& path_target::
derive_path (const char* de, const char* np, const char* ns)
{
- string n;
+ path_type p (dir);
- if (np != nullptr)
- n += np;
-
- n += name;
+ if (np == nullptr)
+ p /= name;
+ else
+ {
+ p /= np;
+ p += name;
+ }
if (ns != nullptr)
- n += ns;
+ p += ns;
- return derive_path (dir / path_type (move (n)), de);
+ return derive_path (move (p), de);
}
const path& path_target::
diff --git a/build2/utility.cxx b/build2/utility.cxx
index db0d6b2..bb26b9d 100644
--- a/build2/utility.cxx
+++ b/build2/utility.cxx
@@ -105,7 +105,7 @@ namespace build2
}
catch (const process_error& e)
{
- error << "unable execute " << f << ": " << e.what ();
+ error << "unable to execute " << f << ": " << e.what ();
throw failed ();
}