aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-07-08 14:05:28 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-07-08 14:11:55 +0200
commit6205a2d9eb7db0a25959ae34dc5406f228da92a5 (patch)
tree0214d042250f290a852267c3552efc54a8b76629
parent9bd06458e869ab0b41db2d3d7b190d6183ff8547 (diff)
Implement limited rpath emulation for Windows
-rw-r--r--build2/algorithm20
-rw-r--r--build2/algorithm.cxx67
-rw-r--r--build2/config/operation.cxx4
-rw-r--r--build2/context68
-rw-r--r--build2/context.cxx26
-rw-r--r--build2/context.txx75
-rw-r--r--build2/cxx/link3
-rw-r--r--build2/cxx/link.cxx470
-rw-r--r--build2/types1
9 files changed, 589 insertions, 145 deletions
diff --git a/build2/algorithm b/build2/algorithm
index d7dddba..e57b94d 100644
--- a/build2/algorithm
+++ b/build2/algorithm
@@ -222,13 +222,19 @@ namespace build2
perform_clean_depdb (action, target&);
// Helper for custom perform(clean) implementations that cleans extra files
- // specified as a list of extensions. The extension string can be NULL, in
- // which case it is ignored. Otherwise, the first character can be '+', in
- // which case the extension is added (without the plus) to the existing
- // extension (if any). In all other cases, the old extension is replaced
- // with the new one. For example:
- //
- // clean_extra (a, t, {"+.d", ".lib"});
+ // 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. The next character can be '+', in which case the
+ // extension is added (without the plus) to the existing extension (if
+ // any). In all other cases, the old extension is replaced with the new one
+ // (so if you want to strip the extension, specify ""). For example:
+ //
+ // 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.
//
target_state
clean_extra (action, file&, initializer_list<const char*> extra_ext);
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 02f3b8e..5aca7ee 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -481,28 +481,62 @@ namespace build2
clean_extra (action a, file& ft, initializer_list<const char*> es)
{
// Clean the extras first and don't print the commands at verbosity level
- // below 3.
+ // below 3. Note the first extra file/directory that actually got removed
+ // for diagnostics below.
//
target_state er (target_state::unchanged);
- path ef; // First extra file that actually got removed (see below).
+ bool ed (false);
+ path ep;
for (const char* e: es)
{
if (e == nullptr)
continue;
- path f;
+ bool d (*e == '/');
+ if (d)
+ ++e;
+
+ path p;
if (*e == '+')
- f = ft.path () + ++e;
+ p = ft.path () + ++e;
else
- f = ft.path ().base () + e;
+ p = ft.path ().base () + e;
- target_state r (rmfile (f, false)
- ? target_state::changed
- : target_state::unchanged);
+ target_state r (target_state::unchanged);
- if (r == target_state::changed && ef.empty ())
- ef = move (f);
+ if (d)
+ {
+ dir_path dp (path_cast<dir_path> (p));
+
+ switch (build2::rmdir_r (dp, true, 3))
+ {
+ 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;
+ }
+ }
+ else
+ {
+ if (rmfile (p, 3))
+ r = target_state::changed;
+ }
+
+ if (r == target_state::changed && ep.empty ())
+ {
+ ed = d;
+ ep = move (p);
+ }
er |= r;
}
@@ -518,12 +552,12 @@ namespace build2
const path& f (fm->path ());
- target_state r (rmfile (f, false)
+ target_state r (rmfile (f, 3)
? target_state::changed
: target_state::unchanged);
- if (r == target_state::changed && ef.empty ())
- ef = f;
+ if (r == target_state::changed && ep.empty ())
+ ep = f;
er |= r;
}
@@ -556,7 +590,12 @@ namespace build2
if (tr != target_state::changed && er == target_state::changed)
{
if (verb > 0 && verb < 3)
- text << "rm " << ef;
+ {
+ if (ed)
+ text << "rm -r " << path_cast<dir_path> (ep);
+ else
+ text << "rm " << ep;
+ }
}
tr |= er;
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index 20425ea..1a207ca 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -44,7 +44,7 @@ namespace build2
path f (out_root / src_root_file);
if (verb)
- text << (verb >= 2 ? "config::save " : "save ") << f;
+ text << (verb >= 2 ? "cat >" : "save ") << f;
try
{
@@ -73,7 +73,7 @@ namespace build2
path f (out_root / config_file);
if (verb)
- text << (verb >= 2 ? "config::save " : "save ") << f;
+ text << (verb >= 2 ? "cat >" : "save ") << f;
const module& mod (*root.modules.lookup<const module> (module::name));
diff --git a/build2/context b/build2/context
index 30d29d1..3f47af6 100644
--- a/build2/context
+++ b/build2/context
@@ -5,6 +5,8 @@
#ifndef BUILD2_CONTEXT
#define BUILD2_CONTEXT
+#include <type_traits> // enable_if
+
#include <butl/filesystem>
#include <build2/types>
@@ -73,45 +75,59 @@ namespace build2
explicit operator bool () const {return v == T::success;}
};
- // Create the directory and print the standard diagnostics. Note that
- // this implementation is not suitable if it is expected that the
- // directory will exist in the majority of case and performance is
+ // Create the directory and print the standard diagnostics starting from
+ // the specified verbosity level.
+ //
+ // Note that this implementation is not suitable if it is expected that the
+ // directory will exist in the majority of cases and performance is
// important. See the fsdir{} rule for details.
//
- fs_status<butl::mkdir_status>
- mkdir (const dir_path&);
+ using mkdir_status = butl::mkdir_status;
- fs_status<butl::mkdir_status>
- mkdir_p (const dir_path&);
+ fs_status<mkdir_status>
+ mkdir (const dir_path&, uint16_t verbosity = 1);
- // Remove the file and print the standard diagnostics. The second argument
- // is only used in diagnostics, to print the target name. Passing the path
- // for target will result in the relative path being printed.
- //
- // If verbose is false, then only print the command at verbosity level 3
- // or higher.
+ fs_status<mkdir_status>
+ mkdir_p (const dir_path&, uint16_t verbosity = 1);
+
+ // Remove the file and print the standard diagnostics starting from the
+ // specified verbosity level. The second argument is only used in
+ // diagnostics, to print the target name. Passing the path for target will
+ // result in the relative path being printed.
//
+ using rmfile_status = butl::rmfile_status;
+
template <typename T>
- fs_status<butl::rmfile_status>
- rmfile (const path&, const T& target, bool verbose = true);
+ fs_status<rmfile_status>
+ rmfile (const path&, const T& target, uint16_t verbosity = 1);
- inline fs_status<butl::rmfile_status>
- rmfile (const path& f, bool verbose = true) {return rmfile (f, f, verbose);}
+ inline fs_status<rmfile_status>
+ rmfile (const path& f, int verbosity = 1) // Literal overload (int).
+ {
+ return rmfile (f, f, static_cast<uint16_t> (verbosity));
+ }
- // Similar to rmfile() but for directories.
+ // Similar to rmfile() but for directories (note: not -r).
//
+ using rmdir_status = butl::rmdir_status;
+
template <typename T>
- fs_status<butl::rmdir_status>
- rmdir (const dir_path&, const T& target);
+ fs_status<rmdir_status>
+ rmdir (const dir_path&, const T& target, uint16_t verbosity = 1);
- inline fs_status<butl::rmdir_status>
- rmdir (const dir_path& d) {return rmdir (d, d);}
+ inline fs_status<rmdir_status>
+ rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int).
+ {
+ return rmdir (d, d, static_cast<uint16_t> (verbosity));
+ }
- // Note that this function returns not_empty if we try to remove
- // a working directory.
+ // Remove the directory recursively and print the standard diagnostics
+ // starting from the specified verbosity level. Note that this function
+ // returns not_empty if we try to remove a working directory. If the dir
+ // argument is false, then the directory itself is not removed.
//
- fs_status<butl::rmdir_status>
- rmdir_r (const dir_path&);
+ fs_status<rmdir_status>
+ rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1);
// Return the src/out directory corresponding to the given out/src. The
// passed directory should be a sub-directory of out/src_root.
diff --git a/build2/context.cxx b/build2/context.cxx
index 59eb912..5530ce3 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -326,10 +326,10 @@ namespace build2
}
fs_status<mkdir_status>
- mkdir (const dir_path& d)
+ mkdir (const dir_path& d, uint16_t v)
{
- // We don't want to print the command if the directory already
- // exists. This makes the below code a bit ugly.
+ // We don't want to print the command if the directory already exists.
+ // This makes the below code a bit ugly.
//
mkdir_status ms;
@@ -339,7 +339,7 @@ namespace build2
}
catch (const system_error& e)
{
- if (verb)
+ if (verb >= v)
text << "mkdir " << d;
error << "unable to create directory " << d << ": " << e.what ();
@@ -348,7 +348,7 @@ namespace build2
if (ms == mkdir_status::success)
{
- if (verb)
+ if (verb >= v)
text << "mkdir " << d;
}
@@ -356,10 +356,10 @@ namespace build2
}
fs_status<mkdir_status>
- mkdir_p (const dir_path& d)
+ mkdir_p (const dir_path& d, uint16_t v)
{
- // We don't want to print the command if the directory already
- // exists. This makes the below code a bit ugly.
+ // We don't want to print the command if the directory already exists.
+ // This makes the below code a bit ugly.
//
mkdir_status ms;
@@ -369,7 +369,7 @@ namespace build2
}
catch (const system_error& e)
{
- if (verb)
+ if (verb >= v)
text << "mkdir -p " << d;
error << "unable to create directory " << d << ": " << e.what ();
@@ -378,7 +378,7 @@ namespace build2
if (ms == mkdir_status::success)
{
- if (verb)
+ if (verb >= v)
text << "mkdir -p " << d;
}
@@ -386,7 +386,7 @@ namespace build2
}
fs_status<butl::rmdir_status>
- rmdir_r (const dir_path& d)
+ rmdir_r (const dir_path& d, bool dir, uint16_t v)
{
using namespace butl;
@@ -396,12 +396,12 @@ namespace build2
if (!dir_exists (d))
return rmdir_status::not_exist;
- if (verb)
+ if (verb >= v)
text << "rmdir -r " << d;
try
{
- butl::rmdir_r (d);
+ butl::rmdir_r (d, dir);
}
catch (const system_error& e)
{
diff --git a/build2/context.txx b/build2/context.txx
index 1f3fce9..9223681 100644
--- a/build2/context.txx
+++ b/build2/context.txx
@@ -8,20 +8,25 @@ namespace build2
{
template <typename T>
fs_status<butl::rmfile_status>
- rmfile (const path& f, const T& t, bool verbose)
+ rmfile (const path& f, const T& t, uint16_t v)
{
using namespace butl;
- // Verbosity thresholds.
+ // We don't want to print the command if we couldn't remove the file
+ // because it does not exist (just like we don't print the update command
+ // if the file is up to date). This makes the below code a bit ugly.
//
- uint16_t l1 (verbose ? 2 : 3);
- uint16_t l2 (verbose ? 1 : 3);
+ auto print = [&f, &t, v] ()
+ {
+ if (verb >= v)
+ {
+ if (verb >= 2)
+ text << "rm " << f;
+ else if (verb)
+ text << "rm " << t;
+ }
+ };
- // We don't want to print the command if we couldn't remove the
- // file because it does not exist (just like we don't print the
- // update command if the file is up to date). This makes the
- // below code a bit ugly.
- //
rmfile_status rs;
try
@@ -30,51 +35,48 @@ namespace build2
}
catch (const system_error& e)
{
- if (verb >= l1)
- text << "rm " << f;
- else if (verb >= l2)
- text << "rm " << t;
-
+ print ();
error << "unable to remove file " << f << ": " << e.what ();
throw failed ();
}
if (rs == rmfile_status::success)
- {
- if (verb >= l1)
- text << "rm " << f;
- else if (verb >= l2)
- text << "rm " << t;
- }
+ print ();
return rs;
}
template <typename T>
fs_status<butl::rmdir_status>
- rmdir (const dir_path& d, const T& t)
+ rmdir (const dir_path& d, const T& t, uint16_t v)
{
using namespace butl;
bool w (work.sub (d)); // Don't try to remove working directory.
rmdir_status rs;
- // We don't want to print the command if we couldn't remove the
- // directory because it does not exist (just like we don't print
- // mkdir if it already exists) or if it is not empty. This makes
- // the below code a bit ugly.
+ // We don't want to print the command if we couldn't remove the directory
+ // because it does not exist (just like we don't print mkdir if it already
+ // exists) or if it is not empty. This makes the below code a bit ugly.
//
+ auto print = [&d, &t, v] ()
+ {
+ if (verb >= v)
+ {
+ if (verb >= 2)
+ text << "rm " << d;
+ else if (verb)
+ text << "rm " << t;
+ }
+ };
+
try
{
rs = !w ? try_rmdir (d) : rmdir_status::not_empty;
}
catch (const system_error& e)
{
- if (verb >= 2)
- text << "rmdir " << d;
- else if (verb)
- text << "rmdir " << t;
-
+ print ();
error << "unable to remove directory " << d << ": " << e.what ();
throw failed ();
}
@@ -83,20 +85,17 @@ namespace build2
{
case rmdir_status::success:
{
- if (verb >= 2)
- text << "rmdir " << d;
- else if (verb)
- text << "rmdir " << t;
-
+ print ();
break;
}
case rmdir_status::not_empty:
{
- if (verb >= 2)
- text << "directory " << d << " is "
+ if (verb >= v && verb >= 2)
+ {
+ text << d << " is "
<< (w ? "current working directory" : "not empty")
<< ", not removing";
-
+ }
break;
}
case rmdir_status::not_exist:
diff --git a/build2/cxx/link b/build2/cxx/link
index d0584de..ca45e17 100644
--- a/build2/cxx/link
+++ b/build2/cxx/link
@@ -28,6 +28,9 @@ 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 0e4fd5a..4de3194 100644
--- a/build2/cxx/link.cxx
+++ b/build2/cxx/link.cxx
@@ -4,6 +4,9 @@
#include <build2/cxx/link>
+#include <errno.h> // E*
+
+#include <set>
#include <cstdlib> // exit()
#include <butl/path-map>
@@ -898,7 +901,7 @@ namespace build2
switch (a)
{
case perform_update_id: return &perform_update;
- case perform_clean_id: return &perform_clean_depdb;
+ case perform_clean_id: return &perform_clean;
default: return noop_recipe; // Configure update.
}
}
@@ -949,19 +952,339 @@ namespace build2
}
}
+ // Provide limited emulation of the rpath functionality on Windows using a
+ // manifest and a side-by-side assembly. In a nutshell, the idea is to
+ // create an assembly with links to all the prerequisite DLLs.
+ //
+ // The scratch argument should be true if the DLL set has changed and we
+ // need to regenerate everything from scratch. Otherwise, we try to avoid
+ // unnecessary work by comparing the DLL timestamps against the assembly
+ // manifest file.
+ //
+ // If the manifest argument is false, then don't generate the target
+ // manifest (i.e., it will be embedded).
+ //
+ // Note that currently our assemblies contain all the DLLs that the
+ // executable depends on, recursively. The alternative approach could be
+ // to also create assemblies for DLLs. This appears to be possible (but we
+ // will have to use the resource ID 2 for such a manifest). And it will
+ // probably be necessary for DLLs that are loaded dynamically with
+ // LoadLibrary(). The tricky part is how such nested assemblies will be
+ // found. Since we are effectively (from the loader's point of view)
+ // copying the DLLs, we will also have to copy their assemblies (because
+ // the loader looks for them in the same directory as the DLL). It's not
+ // clear how well such nested assemblies are supported.
+ //
+ static timestamp
+ timestamp_dlls (target&);
+
+ static void
+ collect_dlls (set<file*>&, target&);
+
+ static void
+ emulate_rpath_windows (file& t, bool scratch, bool manifest)
+ {
+ // Assembly paths and name.
+ //
+ dir_path ad (path_cast<dir_path> (t.path () + ".dlls"));
+ string an (ad.leaf ().string ());
+ path am (ad / path (an + ".manifest"));
+
+ // First check if we actually need to do anything. Since most of the
+ // time we won't, we don't want to combine it with the collect_dlls()
+ // call below which allocates memory, etc.
+ //
+ if (!scratch)
+ {
+ // The corner case here is when timestamp_dlls() returns nonexistent
+ // signalling that there aren't any DLLs but the assembly manifest
+ // file exists. This, however, can only happen if we somehow managed
+ // to transition from the "have DLLs" state to "no DLLs" without going
+ // through the from scratch update. And this shouldn't happen (famous
+ // last words before a core dump).
+ //
+ if (timestamp_dlls (t) <= file_mtime (am))
+ return;
+ }
+
+ scope& rs (t.root_scope ());
+
+ // Next collect the set of DLLs that will be in our assembly. We need to
+ // do this recursively which means we may end up with duplicates. Also,
+ // it is possible that there will (no longer) be any DLLs which means we
+ // just need to clean things up.
+ //
+ set<file*> dlls;
+ collect_dlls (dlls, t);
+ bool empty (dlls.empty ());
+
+ // Target manifest.
+ //
+ path tm;
+ if (manifest)
+ tm = t.path () + ".manifest";
+
+ // Clean the assembly directory and make sure it exists. Maybe it would
+ // have been faster to overwrite the existing manifest rather than
+ // removing the old one and creating a new one. But this is definitely
+ // simpler.
+ //
+ {
+ rmdir_status s (build2::rmdir_r (ad, empty, 3));
+
+ // What if there is a user-defined manifest in the src directory? We
+ // would just overwrite it if src == out. While we could add a comment
+ // with some signature that can be used to detect an auto-generated
+ // manifest, we can also use the presence of the assembly directory as
+ // such a marker.
+ //
+ // @@ And what can we do instead? One idea is for the user to call it
+ // something else and we merge the two. Perhaps the link rule could
+ // have support for manifests (i.e., manifest will be one of the
+ // prerequisites). A similar problem is with embedded vs standalone
+ // manifests (embedded preferred starting from Vista). I guess if we
+ // support embedding manifests, then we can also merge them.
+ //
+ if (manifest &&
+ s == rmdir_status::not_exist &&
+ rs.src_path () == rs.out_path () &&
+ file_exists (tm))
+ {
+ fail << tm << " looks like a custom manifest" <<
+ info << "remove it manually if that's not the case";
+ }
+
+ if (empty)
+ {
+ if (manifest)
+ rmfile (tm, 3);
+
+ return;
+ }
+
+ if (s == rmdir_status::not_exist)
+ mkdir (ad, 3);
+ }
+
+ // Translate the compiler target CPU value to the processorArchitecture
+ // attribute value.
+ //
+ const string& tcpu (cast<string> (rs["cxx.target.cpu"]));
+
+ const char* pa (tcpu == "i386" || tcpu == "i686" ? "x86" :
+ tcpu == "x86_64" ? "amd64" :
+ nullptr);
+
+ if (pa == nullptr)
+ fail << "unable to translate CPU " << tcpu << " to manifest "
+ << "processor architecture";
+
+ if (verb >= 3)
+ text << "cat >" << am;
+
+ try
+ {
+ ofstream ofs;
+ ofs.exceptions (ofstream::failbit | ofstream::badbit);
+ ofs.open (am.string ());
+
+ ofs << "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
+ << "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n"
+ << " manifestVersion='1.0'>\n"
+ << " <assemblyIdentity name='" << an << "'\n"
+ << " type='win32'\n"
+ << " processorArchitecture='" << pa << "'\n"
+ << " version='0.0.0.0'/>\n";
+
+ scope& as (*rs.weak_scope ()); // Amalgamation scope.
+
+ for (file* dt: dlls)
+ {
+ const path& dp (dt->path ()); // DLL path.
+ const path dn (dp.leaf ()); // DLL name.
+ const path lp (ad / dn); // Link path.
+
+ auto print = [&dp, &lp] (const char* cmd)
+ {
+ if (verb >= 3)
+ text << cmd << ' ' << dp << ' ' << lp;
+ };
+
+ // First we try to create a symlink. If that fails (e.g., "Windows
+ // happens"), then we resort to hard links. If that doesn't work
+ // out either (e.g., not on the same filesystem), then we fall back
+ // to copies. So things are going to get a bit nested.
+ //
+ try
+ {
+ // For the symlink use a relative target path if both paths are
+ // part of the same amalgamation. This way if the amalgamation is
+ // moved as a whole, the links will remain valid.
+ //
+ if (dp.sub (as.out_path ()))
+ mksymlink (dp.relative (ad), lp);
+ else
+ mksymlink (dp, lp);
+
+ print ("ln -s");
+ }
+ catch (const system_error& e)
+ {
+ int c (e.code ().value ());
+
+ if (c != EPERM && c != ENOSYS)
+ {
+ print ("ln -s");
+ fail << "unable to create symlink " << lp << ": " << e.what ();
+ }
+
+ try
+ {
+ mkhardlink (dp, lp);
+ print ("ln");
+ }
+ catch (const system_error& e)
+ {
+ int c (e.code ().value ());
+
+ if (c != EPERM && c != ENOSYS)
+ {
+ print ("ln");
+ fail << "unable to create hard link " << lp << ": "
+ << e.what ();
+ }
+
+ try
+ {
+ cpfile (dp, lp);
+ print ("cp");
+ }
+ catch (const system_error& e)
+ {
+ print ("cp");
+ fail << "unable to create copy " << lp << ": " << e.what ();
+ }
+ }
+ }
+
+ ofs << " <file name='" << dn.string () << "'/>\n";
+ }
+
+ ofs << "</assembly>\n";
+ }
+ catch (const ofstream::failure&)
+ {
+ fail << "unable to write to " << am;
+ }
+
+ // Create the manifest if requested.
+ //
+ if (!manifest)
+ return;
+
+ if (verb >= 3)
+ text << "cat >" << tm;
+
+ try
+ {
+ ofstream ofs;
+ ofs.exceptions (ofstream::failbit | ofstream::badbit);
+ ofs.open (tm.string (), ofstream::out | ofstream::trunc);
+
+ ofs << "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
+ << "<!-- Note: auto-generated, do not edit. -->\n"
+ << "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n"
+ << " manifestVersion='1.0'>\n"
+ << " <assemblyIdentity name='" << t.path ().leaf () << "'\n"
+ << " type='win32'\n"
+ << " processorArchitecture='" << pa << "'\n"
+ << " version='0.0.0.0'/>\n"
+ << " <dependency>\n"
+ << " <dependentAssembly>\n"
+ << " <assemblyIdentity name='" << an << "'\n"
+ << " type='win32'\n"
+ << " processorArchitecture='" << pa << "'\n"
+ << " language='*'\n"
+ << " version='0.0.0.0'/>\n"
+ << " </dependentAssembly>\n"
+ << " </dependency>\n"
+ << "</assembly>\n";
+ }
+ catch (const ofstream::failure&)
+ {
+ fail << "unable to write to " << tm;
+ }
+ }
+
+ // Return the greatest (newest) timestamp of all the DLLs that we will be
+ // adding to the assembly or timestamp_nonexistent if there aren't any.
+ //
+ static timestamp
+ timestamp_dlls (target& t)
+ {
+ timestamp r (timestamp_nonexistent);
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (libso* ls = pt->is_a<libso> ())
+ {
+ // This can be an installed library in which case we will have just
+ // the import stub but may also have just the DLL. For now we don't
+ // bother with installed libraries.
+ //
+ if (ls->member == nullptr)
+ continue;
+
+ file& dll (static_cast<file&> (*ls->member));
+
+ // What if the DLL is in the same directory as the executable, will
+ // it still be found even if there is an assembly? On the other
+ // hand, handling it as any other won't hurt us much.
+ //
+ timestamp t;
+
+ if ((t = dll.mtime ()) > r)
+ r = t;
+
+ if ((t = timestamp_dlls (*ls)) > r)
+ r = t;
+ }
+ }
+
+ return r;
+ }
+
+ static void
+ collect_dlls (set<file*>& s, target& t)
+ {
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (libso* ls = pt->is_a<libso> ())
+ {
+ if (ls->member == nullptr)
+ continue;
+
+ file& dll (static_cast<file&> (*ls->member));
+
+ s.insert (&dll);
+ collect_dlls (s, *ls);
+ }
+ }
+ }
+
target_state link::
perform_update (action a, target& xt)
{
tracer trace ("cxx::link::perform_update");
- path_target& t (static_cast<path_target&> (xt));
+ file& t (static_cast<file&> (xt));
type lt (link_type (t));
bool so (lt == type::so);
// Update prerequisites.
//
- bool up (execute_prerequisites (a, t, t.mtime ()));
+ bool update (execute_prerequisites (a, t, t.mtime ()));
scope& rs (t.root_scope ());
@@ -1059,51 +1382,57 @@ namespace build2
append_options (args, t, "cxx.coptions");
append_std (args, rs, cid, t, std);
- // Set soname.
+ // Handle soname/rpath. Emulation for Windows is done after we have
+ // built the target.
//
- if (so && tclass != "windows")
+ if (tclass == "windows")
{
- const string& leaf (t.path ().leaf ().string ());
+ auto l (t["bin.rpath"]);
- if (tclass == "macosx")
+ if (l && !l->empty ())
+ fail << cast<string> (rs["cxx.target"]) << " does not have rpath";
+ }
+ else
+ {
+ // Set soname.
+ //
+ if (so)
{
- // With Mac OS 10.5 (Leopard) Apple finally caved in and gave us
- // a way to emulate vanilla -rpath.
- //
- // It may seem natural to do something different on update for
- // install. However, if we don't make it @rpath, then the user
- // won't be able to use config.bin.rpath for installed libraries.
- //
- soname1 = "-install_name";
- soname2 = "@rpath/" + leaf;
- }
- else
- soname1 = "-Wl,-soname," + leaf;
+ const string& leaf (t.path ().leaf ().string ());
- if (!soname1.empty ())
- args.push_back (soname1.c_str ());
+ if (tclass == "macosx")
+ {
+ // With Mac OS 10.5 (Leopard) Apple finally caved in and gave us
+ // a way to emulate vanilla -rpath.
+ //
+ // It may seem natural to do something different on update for
+ // install. However, if we don't make it @rpath, then the user
+ // won't be able to use config.bin.rpath for installed libraries.
+ //
+ soname1 = "-install_name";
+ soname2 = "@rpath/" + leaf;
+ }
+ else
+ soname1 = "-Wl,-soname," + leaf;
- if (!soname2.empty ())
- args.push_back (soname2.c_str ());
- }
+ if (!soname1.empty ())
+ args.push_back (soname1.c_str ());
- // Add rpaths. We used to first add the ones specified by the user so
- // that they take precedence. But that caused problems if we have old
- // versions of the libraries sitting in the rpath location (e.g.,
- // installed libraries). And if you think about this, it's probably
- // correct to prefer libraries that we explicitly imported to the
- // ones found via rpath.
- //
- // Note also that if this is update for install, then we don't add
- // rpath of the imported libraries (i.e., we assume the are also
- // installed).
- //
- if (tclass == "windows")
- {
- // @@ VC TODO: emulate own rpath somehow and complain on user's.
- }
- else
- {
+ if (!soname2.empty ())
+ args.push_back (soname2.c_str ());
+ }
+
+ // Add rpaths. We used to first add the ones specified by the user
+ // so that they take precedence. But that caused problems if we have
+ // old versions of the libraries sitting in the rpath location
+ // (e.g., installed libraries). And if you think about this, it's
+ // probably correct to prefer libraries that we explicitly imported
+ // to the ones found via rpath.
+ //
+ // Note also that if this is update for install, then we don't add
+ // rpath of the imported libraries (i.e., we assume they are also
+ // installed).
+ //
for (target* pt: t.prerequisite_targets)
{
if (libso* ls = pt->is_a<libso> ())
@@ -1188,17 +1517,19 @@ namespace build2
}
// If any of the above checks resulted in a mismatch (different linker,
- // options, or input file set), or if the database is newer than the
- // target (interrupted update) then force the target update.
+ // options or input file set), or if the database is newer than the
+ // target (interrupted update) then force the target update. Also
+ // note this situation in the "from scratch" flag.
//
+ bool scratch (false);
if (dd.writing () || dd.mtime () > t.mtime ())
- up = true;
+ scratch = update = true;
dd.close ();
// If nothing changed, then we are done.
//
- if (!up)
+ if (!update)
return target_state::unchanged;
// Ok, so we are updating. Finish building the command line.
@@ -1387,6 +1718,11 @@ namespace build2
throw failed ();
}
+ // 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 ());
+
if (ranlib)
{
const char* args[] = {
@@ -1415,6 +1751,13 @@ namespace build2
}
}
+ // Emulate rpath on Windows.
+ //
+ if (lt == type::e && tclass == "windows")
+ emulate_rpath_windows (t, scratch, cid != "msvc");
+
+ rm.cancel ();
+
// Should we go to the filesystem and get the new mtime? We know the
// file has been modified, so instead just use the current clock time.
// It has the advantage of having the subseconds precision.
@@ -1423,6 +1766,43 @@ namespace build2
return target_state::changed;
}
+ target_state link::
+ perform_clean (action a, target& xt)
+ {
+ tracer trace ("cxx::link::perform_clean");
+
+ file& t (static_cast<file&> (xt));
+
+ type lt (link_type (t));
+
+ scope& rs (t.root_scope ());
+ const string& cid (cast<string> (rs["cxx.id"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
+
+ if (lt == type::e && tclass == "windows")
+ {
+ bool m (cid != "msvc");
+
+ // Check for custom manifest, just like in emulate_rpath_windows().
+ //
+ if (m &&
+ rs.src_path () == rs.out_path () &&
+ file_exists (t.path () + ".manifest") &&
+ !dir_exists (path_cast<dir_path> (t.path () + ".dlls")))
+ {
+ fail << t.path () + ".manifest" << " looks like a custom manifest" <<
+ info << "remove it manually if that's not the case";
+ }
+
+ return clean_extra (
+ a,
+ t,
+ {"+.d", (m ? "+.manifest" : nullptr), "/+.dlls"});
+ }
+ else
+ return clean_extra (a, t, {"+.d"});
+ }
+
link link::instance;
}
}
diff --git a/build2/types b/build2/types
index 943c9e1..6fdea4e 100644
--- a/build2/types
+++ b/build2/types
@@ -80,6 +80,7 @@ namespace build2
using butl::dir_path;
using butl::basic_path;
using butl::invalid_path;
+ using butl::path_cast;
// Absolute directory path. Note that for now we don't do any checking that
// the path is in fact absolute.