aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-07-11 15:33:43 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-07-11 15:33:43 +0200
commite3839b800a9ab1bc4824b742ccaef7ce3d59c291 (patch)
tree181c9c78605d6b3a92101065e507f577325ac3d8
parent0760742386e8e6034bbd619487ef156bc574e408 (diff)
Reimplement Windows rpath emulation using embedded manifests
As a bonus, everyone now gets a sane default manifest.
-rw-r--r--build2/buildfile116
-rw-r--r--build2/cxx/link.cxx443
-rw-r--r--build2/cxx/module.cxx16
-rw-r--r--build2/cxx/windows-manifest.cxx132
-rw-r--r--build2/cxx/windows-rpath.cxx268
5 files changed, 584 insertions, 391 deletions
diff --git a/build2/buildfile b/build2/buildfile
index d754db2..5474fbd 100644
--- a/build2/buildfile
+++ b/build2/buildfile
@@ -4,63 +4,65 @@
import libs = libbutl%lib{butl}
-exe{b}: \
- {hxx ixx txx cxx}{ algorithm } \
- { cxx}{ b } \
- {hxx ixx cxx}{ b-options } \
- {hxx txx cxx}{ context } \
- {hxx cxx}{ depdb } \
- {hxx cxx}{ diagnostics } \
- {hxx cxx}{ dump } \
- {hxx ixx cxx}{ file } \
- {hxx txx cxx}{ filesystem } \
- {hxx cxx}{ lexer } \
- {hxx cxx}{ module } \
- {hxx ixx cxx}{ name } \
- {hxx cxx}{ operation } \
- {hxx cxx}{ parser } \
- {hxx cxx}{ prerequisite } \
- {hxx cxx}{ rule } \
- {hxx }{ rule-map } \
- {hxx cxx}{ scope } \
- {hxx cxx}{ search } \
- {hxx cxx}{ spec } \
- {hxx ixx txx cxx}{ target } \
- {hxx }{ target-key } \
- {hxx }{ target-type } \
- {hxx cxx}{ token } \
- {hxx }{ types } \
- {hxx cxx}{ types-parsers } \
- {hxx ixx txx cxx}{ utility } \
- {hxx ixx txx cxx}{ variable } \
- {hxx }{ version } \
- bin/{hxx cxx}{ guess } \
- bin/{hxx cxx}{ module } \
- bin/{hxx cxx}{ rule } \
- bin/{hxx cxx}{ target } \
- cli/{hxx cxx}{ module } \
- cli/{hxx cxx}{ rule } \
- cli/{hxx cxx}{ target } \
- config/{hxx cxx}{ module } \
- config/{hxx cxx}{ operation } \
- config/{hxx txx cxx}{ utility } \
- cxx/{hxx cxx}{ compile } \
- cxx/{hxx cxx}{ guess } \
- cxx/{hxx cxx}{ install } \
- cxx/{hxx cxx}{ link } \
- cxx/{hxx cxx}{ module } \
- cxx/{hxx cxx}{ target } \
- cxx/{hxx ixx cxx}{ utility } \
- dist/{hxx cxx}{ module } \
- dist/{hxx cxx}{ operation } \
- dist/{hxx cxx}{ rule } \
-install/{hxx cxx}{ module } \
-install/{hxx cxx}{ operation } \
-install/{hxx cxx}{ rule } \
-install/{hxx }{ utility } \
- test/{hxx cxx}{ module } \
- test/{hxx cxx}{ operation } \
- test/{hxx cxx}{ rule } \
+exe{b}: \
+ {hxx ixx txx cxx}{ algorithm } \
+ { cxx}{ b } \
+ {hxx ixx cxx}{ b-options } \
+ {hxx txx cxx}{ context } \
+ {hxx cxx}{ depdb } \
+ {hxx cxx}{ diagnostics } \
+ {hxx cxx}{ dump } \
+ {hxx ixx cxx}{ file } \
+ {hxx txx cxx}{ filesystem } \
+ {hxx cxx}{ lexer } \
+ {hxx cxx}{ module } \
+ {hxx ixx cxx}{ name } \
+ {hxx cxx}{ operation } \
+ {hxx cxx}{ parser } \
+ {hxx cxx}{ prerequisite } \
+ {hxx cxx}{ rule } \
+ {hxx }{ rule-map } \
+ {hxx cxx}{ scope } \
+ {hxx cxx}{ search } \
+ {hxx cxx}{ spec } \
+ {hxx ixx txx cxx}{ target } \
+ {hxx }{ target-key } \
+ {hxx }{ target-type } \
+ {hxx cxx}{ token } \
+ {hxx }{ types } \
+ {hxx cxx}{ types-parsers } \
+ {hxx ixx txx cxx}{ utility } \
+ {hxx ixx txx cxx}{ variable } \
+ {hxx }{ version } \
+ bin/{hxx cxx}{ guess } \
+ bin/{hxx cxx}{ module } \
+ bin/{hxx cxx}{ rule } \
+ bin/{hxx cxx}{ target } \
+ cli/{hxx cxx}{ module } \
+ cli/{hxx cxx}{ rule } \
+ cli/{hxx cxx}{ target } \
+ config/{hxx cxx}{ module } \
+ config/{hxx cxx}{ operation } \
+ config/{hxx txx cxx}{ utility } \
+ cxx/{hxx cxx}{ compile } \
+ cxx/{hxx cxx}{ guess } \
+ cxx/{hxx cxx}{ install } \
+ cxx/{hxx cxx}{ link } \
+ cxx/{hxx cxx}{ module } \
+ cxx/{hxx cxx}{ target } \
+ cxx/{hxx ixx cxx}{ utility } \
+ cxx/{ cxx}{ windows-manifest } \
+ cxx/{ cxx}{ windows-rpath } \
+ dist/{hxx cxx}{ module } \
+ dist/{hxx cxx}{ operation } \
+ dist/{hxx cxx}{ rule } \
+install/{hxx cxx}{ module } \
+install/{hxx cxx}{ operation } \
+install/{hxx cxx}{ rule } \
+install/{hxx }{ utility } \
+ test/{hxx cxx}{ module } \
+ test/{hxx cxx}{ operation } \
+ test/{hxx cxx}{ rule } \
$libs
# Pass our compiler target to be used as build2 host.
diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx
index 82d98a1..34dc8d9 100644
--- a/build2/cxx/link.cxx
+++ b/build2/cxx/link.cxx
@@ -4,9 +4,6 @@
#include <build2/cxx/link>
-#include <errno.h> // E*
-
-#include <set>
#include <cstdlib> // exit()
#include <butl/path-map>
@@ -952,349 +949,132 @@ 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).
+ // See windows-manifest.cxx.
//
- // 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.
+ path
+ windows_manifest (file&, bool rpath_assembly);
+
+ // See windows-rpath.cxx.
//
- static timestamp
- timestamp_dlls (target&);
+ timestamp
+ windows_rpath_timestamp (file&);
- static void
- collect_dlls (set<file*>&, target&);
+ void
+ windows_rpath_assembly (file&, timestamp, bool scratch);
- static void
- emulate_rpath_windows (file& t, bool scratch, bool manifest)
+ target_state link::
+ perform_update (action a, target& xt)
{
- // 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;
- }
+ tracer trace ("cxx::link::perform_update");
- scope& rs (t.root_scope ());
+ file& t (static_cast<file&> (xt));
- // 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 ());
+ type lt (link_type (t));
+ bool so (lt == type::so);
- // 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.
+ // Update prerequisites.
//
- {
- 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";
- }
+ bool update (execute_prerequisites (a, t, t.mtime ()));
- if (empty)
- {
- if (manifest)
- rmfile (tm, 3);
+ scope& rs (t.root_scope ());
- return;
- }
+ const string& cid (cast<string> (rs["cxx.id"]));
+ const string& tsys (cast<string> (rs["cxx.target.system"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
- if (s == rmdir_status::not_exist)
- mkdir (ad, 3);
- }
+ const string& aid (lt == type::a
+ ? cast<string> (rs["bin.ar.id"])
+ : string ());
- // Translate the compiler target CPU value to the processorArchitecture
- // attribute value.
+ // If targeting Windows, take care of the manifest.
//
- const string& tcpu (cast<string> (rs["cxx.target.cpu"]));
+ path manifest; // Manifest itself (msvc) or compiled object file.
+ timestamp rpath_timestamp (timestamp_nonexistent); // DLLs timestamp.
- 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
+ if (lt == type::e && tclass == "windows")
{
- 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";
+ // First determine if we need to add our rpath emulating assembly. The
+ // assembly itself is generated later, after updating the target. Omit
+ // it if we are updating for install.
+ //
+ if (a.outer_operation () != install_id)
+ rpath_timestamp = windows_rpath_timestamp (t);
- scope& as (*rs.weak_scope ()); // Amalgamation scope.
+ // Whether
+ //
+ path mf (
+ windows_manifest (
+ t,
+ rpath_timestamp != timestamp_nonexistent));
- for (file* dt: dlls)
+ if (tsys == "mingw32")
{
- const path& dp (dt->path ()); // DLL path.
- const path dn (dp.leaf ()); // DLL name.
- const path lp (ad / dn); // Link path.
+ // Compile the manifest into the object file with windres. While we
+ // are going to synthesize an .rc file to pipe to windres' stdin, we
+ // will still use .manifest to check if everything is up-to-date.
+ //
+ manifest = mf + ".o";
- auto print = [&dp, &lp] (const char* cmd)
+ if (file_mtime (mf) > file_mtime (manifest))
{
- if (verb >= 3)
- text << cmd << ' ' << dp << ' ' << lp;
- };
+ path of (relative (manifest));
- // 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.
+ // @@ Would be good to add this to depdb (e.g,, rc changes).
//
- 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 ());
+ const char* args[] = {
+ cast<path> (rs["config.bin.rc"]).string ().c_str (),
+ "--input-format=rc",
+ "--output-format=coff",
+ "-o", of.string ().c_str (),
+ nullptr};
- if (c != EPERM && c != ENOSYS)
- {
- print ("ln -s");
- fail << "unable to create symlink " << lp << ": " << e.what ();
- }
+ if (verb >= 3)
+ print_process (args);
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 ();
- }
+ process pr (args, -1);
try
{
- cpfile (dp, lp);
- print ("cp");
+ ofdstream os (pr.out_fd);
+ os.exceptions (ofdstream::badbit | ofdstream::failbit);
+
+ // 1 is resource ID, 24 is RT_MANIFEST.
+ //
+ os << "1 24 \"" << mf << "\"" << endl;
+
+ os.close ();
}
- catch (const system_error& e)
+ catch (const ofdstream::failure&)
{
- print ("cp");
- fail << "unable to create copy " << lp << ": " << e.what ();
+ if (pr.wait ()) // Ignore if child failed.
+ fail << "unable to pipe resource file to " << args[0];
}
- }
- }
-
- 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;
+ if (!pr.wait ())
+ throw failed (); // Assume diagnostics issued.
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
- file& dll (static_cast<file&> (*ls->member));
+ if (e.child ())
+ exit (1);
- // 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;
+ throw failed ();
+ }
- if ((t = timestamp_dlls (*ls)) > r)
- r = t;
+ update = true; // Force update.
+ }
}
- }
-
- return r;
- }
-
- static void
- collect_dlls (set<file*>& s, target& t)
- {
- for (target* pt: t.prerequisite_targets)
- {
- if (libso* ls = pt->is_a<libso> ())
+ else
{
- if (ls->member == nullptr)
- continue;
-
- file& dll (static_cast<file&> (*ls->member));
-
- s.insert (&dll);
- collect_dlls (s, *ls);
+ // @@ VC: /MANIFESTINPUT should do the trick (via manifest).
+ //
+ manifest = move (mf);
}
}
- }
-
- target_state link::
- perform_update (action a, target& xt)
- {
- tracer trace ("cxx::link::perform_update");
-
- file& t (static_cast<file&> (xt));
-
- type lt (link_type (t));
- bool so (lt == type::so);
-
- // Update prerequisites.
- //
- bool update (execute_prerequisites (a, t, t.mtime ()));
-
- scope& rs (t.root_scope ());
-
- const string& cid (cast<string> (rs["cxx.id"]));
- const string& tsys (cast<string> (rs["cxx.target.system"]));
- const string& tclass (cast<string> (rs["cxx.target.class"]));
-
- const string& aid (lt == type::a
- ? cast<string> (rs["bin.ar.id"])
- : string ());
// Check/update the dependency database.
//
@@ -1382,11 +1162,13 @@ namespace build2
append_options (args, t, "cxx.coptions");
append_std (args, rs, cid, t, std);
- // Handle soname/rpath. Emulation for Windows is done after we have
- // built the target.
+ // Handle soname/rpath.
//
if (tclass == "windows")
{
+ // Limited emulation for Windows with no support for user-defined
+ // rpaths.
+ //
auto l (t["bin.rpath"]);
if (l && !l->empty ())
@@ -1507,6 +1289,9 @@ namespace build2
}
}
+ if (!manifest.empty ())
+ cs.append (manifest.string ());
+
// Treat them as inputs, not options.
//
if (lt != type::a)
@@ -1660,8 +1445,13 @@ namespace build2
}
}
+ // For MinGW manifest is an object file.
+ //
+ if (!manifest.empty () && tsys == "mingw32")
+ sargs.push_back (relative (manifest).string ());
+
// Copy sargs to args. Why not do it as we go along pushing into sargs?
- // Because of potential realocations.
+ // Because of potential reallocations.
//
for (size_t i (0); i != sargs.size (); ++i)
{
@@ -1751,10 +1541,14 @@ namespace build2
}
}
- // Emulate rpath on Windows.
+ // For Windows generate rpath-emulating assembly (unless updaing for
+ // install).
//
if (lt == type::e && tclass == "windows")
- emulate_rpath_windows (t, scratch, cid != "msvc");
+ {
+ if (a.outer_operation () != install_id)
+ windows_rpath_assembly (t, rpath_timestamp, scratch);
+ }
rm.cancel ();
@@ -1769,35 +1563,24 @@ namespace build2
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& tsys (cast<string> (rs["cxx.target.system"]));
const string& tclass (cast<string> (rs["cxx.target.class"]));
+ // On Windows we need to clean up manifest business.
+ //
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"});
+ return clean_extra (a,
+ t,
+ {"+.d",
+ "/+.dlls",
+ tsys == "mingw32" ? "+.manifest.o" : nullptr,
+ "+.manifest"});
}
else
return clean_extra (a, t, {"+.d"});
diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx
index 28892cf..caeea9b 100644
--- a/build2/cxx/module.cxx
+++ b/build2/cxx/module.cxx
@@ -256,6 +256,9 @@ namespace build2
}
}
+ const string& tsys (cast<string> (r["cxx.target.system"]));
+ const string& tclass (cast<string> (r["cxx.target.class"]));
+
// Initialize the bin module. Only do this if it hasn't already been
// loaded so that we don't overwrite user's bin.* settings.
//
@@ -274,6 +277,15 @@ namespace build2
info << "cxx.target is " << ct;
}
+ // If our target is MinGW, then we will need the resource compiler
+ // (windres) in order to embed the manifest.
+ //
+ if (tsys == "mingw32")
+ {
+ if (!cast_false<bool> (b["bin.rc.loaded"]))
+ load_module ("bin.rc", r, b, loc, false, bin_hints);
+ }
+
// Register target types.
//
{
@@ -329,8 +341,6 @@ namespace build2
r.insert<libso> (perform_install_id, "cxx.install", install::instance);
}
-
-
// Configure "installability" of our target types.
//
using namespace install;
@@ -342,8 +352,6 @@ namespace build2
// 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);
diff --git a/build2/cxx/windows-manifest.cxx b/build2/cxx/windows-manifest.cxx
new file mode 100644
index 0000000..cabc6ca
--- /dev/null
+++ b/build2/cxx/windows-manifest.cxx
@@ -0,0 +1,132 @@
+// file : build2/cxx/windows-manifest.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <fstream>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/variable>
+#include <build2/filesystem>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cxx
+ {
+ // Translate the compiler target CPU value to the processorArchitecture
+ // attribute value.
+ //
+ const char*
+ windows_manifest_arch (const string& tcpu)
+ {
+ 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";
+
+ return pa;
+ }
+
+ // Generate a Windows manifest and if necessary create/update the manifest
+ // file corresponding to the exe{} target. Return the manifest file path.
+ //
+ path
+ windows_manifest (file& t, bool rpath_assembly)
+ {
+ tracer trace ("cxx::windows_manifest");
+
+ scope& rs (t.root_scope ());
+
+ const char* pa (
+ windows_manifest_arch (
+ cast<string> (rs["cxx.target.cpu"])));
+
+ string m;
+
+ m += "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n";
+ m += "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n";
+ m += " manifestVersion='1.0'>\n";
+
+ // Program name, version, etc.
+ //
+ string name (t.path ().leaf ().string ());
+
+ m += " <assemblyIdentity name='"; m += name; m += "'\n";
+ m += " type='win32'\n";
+ m += " processorArchitecture='"; m += pa; m += "'\n";
+ m += " version='0.0.0.0'/>\n";
+
+ // Our rpath-emulating assembly.
+ //
+ if (rpath_assembly)
+ {
+ m += " <dependency>\n";
+ m += " <dependentAssembly>\n";
+ m += " <assemblyIdentity name='"; m += name; m += ".dlls'\n";
+ m += " type='win32'\n";
+ m += " processorArchitecture='"; m += pa; m += "'\n";
+ m += " language='*'\n";
+ m += " version='0.0.0.0'/>\n";
+ m += " </dependentAssembly>\n";
+ m += " </dependency>\n";
+ }
+
+ // UAC information. Without it Windows will try to guess, which, as you
+ // can imagine, doesn't end well.
+ //
+ m += " <trustInfo xmlns='urn:schemas-microsoft-com:asm.v3'>\n";
+ m += " <security>\n";
+ m += " <requestedPrivileges>\n";
+ m += " <requestedExecutionLevel level='asInvoker' uiAccess='false'/>\n";
+ m += " </requestedPrivileges>\n";
+ m += " </security>\n";
+ m += " </trustInfo>\n";
+
+ m += "</assembly>\n";
+
+ // If the manifest file exists, compare to its content. If nothing
+ // changed (common case), then we can avoid any further updates.
+ //
+ // The potentially faster alternative would be to hash it and store an
+ // entry in depdb. This, however, gets a bit complicated since we will
+ // need to avoid a race between the depdb and .manifest updates.
+ //
+ path mf (t.path () + ".manifest");
+
+ if (file_exists (mf))
+ {
+ ifstream ifs (mf.string ());
+ string s;
+ getline (ifs, s, '\0');
+
+ if (s == m)
+ return mf;
+ }
+
+ if (verb >= 3)
+ text << "cat >" << mf;
+
+ try
+ {
+ ofstream ofs;
+ ofs.exceptions (ofstream::failbit | ofstream::badbit);
+ ofs.open (mf.string (), ofstream::out | ofstream::trunc);
+ ofs << m;
+ }
+ catch (const ofstream::failure&)
+ {
+ fail << "unable to write to " << m;
+ }
+
+ return mf;
+ }
+ }
+}
diff --git a/build2/cxx/windows-rpath.cxx b/build2/cxx/windows-rpath.cxx
new file mode 100644
index 0000000..8f19f79
--- /dev/null
+++ b/build2/cxx/windows-rpath.cxx
@@ -0,0 +1,268 @@
+// file : build2/cxx/windows-rpath.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <errno.h> // E*
+
+#include <set>
+#include <fstream>
+
+#include <build2/scope>
+#include <build2/context>
+#include <build2/variable>
+#include <build2/filesystem>
+#include <build2/diagnostics>
+
+#include <build2/bin/target>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cxx
+ {
+ // Provide limited emulation of the rpath functionality on Windows using a
+ // side-by-side assembly. In a nutshell, the idea is to create an assembly
+ // with links to all the prerequisite DLLs.
+ //
+ // 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 (e.g., in Wine).
+ //
+ using namespace bin;
+
+ // 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.
+ //
+ timestamp
+ windows_rpath_timestamp (file& 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 = windows_rpath_timestamp (*ls)) > r)
+ r = t;
+ }
+ }
+
+ return r;
+ }
+
+ // Like *_timestamp() but actually collect the DLLs.
+ //
+ static void
+ rpath_dlls (set<file*>& s, file& 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);
+ rpath_dlls (s, *ls);
+ }
+ }
+ }
+
+ const char*
+ windows_manifest_arch (const string& tcpu); // windows-manifest.cxx
+
+ // The ts argument should be the the DLLs timestamp returned by
+ // *_timestamp().
+ //
+ // 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 DLLs timestamp against the assembly
+ // manifest file.
+ //
+ void
+ windows_rpath_assembly (file& t, timestamp ts, bool scratch)
+ {
+ // 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 *_dlls() call
+ // below which allocates memory, etc.
+ //
+ if (!scratch)
+ {
+ // The corner case here is when _timestamp() 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 (ts <= 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 aren't/no longer are any DLLs which means
+ // we just need to clean things up.
+ //
+ bool empty (ts == timestamp_nonexistent);
+
+ set<file*> dlls;
+ if (!empty)
+ rpath_dlls (dlls, t);
+
+ // 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));
+
+ if (empty)
+ return;
+
+ if (s == rmdir_status::not_exist)
+ mkdir (ad, 3);
+ }
+
+ const char* pa (
+ windows_manifest_arch (
+ cast<string> (rs["cxx.target.cpu"])));
+
+ 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;
+ }
+ }
+ }
+}