From ba83ffbaf5941366a399e3bc340bed3f55cc977c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 28 Aug 2016 11:12:02 +0200 Subject: Fix Windows rpath support --- build2/cc/windows-rpath.cxx | 187 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 146 insertions(+), 41 deletions(-) (limited to 'build2/cc/windows-rpath.cxx') diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index ea20a5c..e27dddb 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -4,8 +4,6 @@ #include // E* -#include - #include #include #include @@ -14,6 +12,8 @@ #include +#include + using namespace std; using namespace butl; @@ -36,60 +36,159 @@ namespace build2 // 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). // + // 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. + // 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 link:: + windows_rpath_timestamp (file& t, scope& bs, lorder lo) const { timestamp r (timestamp_nonexistent); - for (target* pt: t.prerequisite_targets) + // We need to collect all the DLLs, so go into implementation of both + // shared and static (in case they depend on shared). + // + auto imp = [] (file&, bool) {return true;}; + + auto lib = [&r] (file* l, const string& f, bool sys) { - if (libs* ls = pt->is_a ()) + // We don't rpath system libraries. + // + if (sys) + return; + + // Skip static libraries. + // + if (l != nullptr) { - // Skip installed DLLs. + // This can be an "undiscovered" DLL (see search_library()). // - if (ls->path ().empty ()) - continue; - - // 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. + if (!l->is_a () || l->path ().empty ()) + return; + } + else + { + // This is an absolute path and we need to decide whether it is + // a shared or static library. // - timestamp t; - - if ((t = ls->mtime ()) > r) - r = t; + // @@ This is so broken: we don't link to DLLs, we link to .lib or + // .dll.a! Should we even bother? Maybe only for "our" DLLs + // (.dll.lib/.dll.a)? But the DLL can also be in a different + // directory (lib/../bin). + // + // Though this can happen on MinGW with direct DLL link... + // + size_t p (path::traits::find_extension (f)); - if ((t = windows_rpath_timestamp (*ls)) > r) - r = t; + if (p == string::npos || casecmp (f.c_str () + p + 1, "dll") != 0) + return; } + + // Ok, this is a DLL. + // + timestamp t (l != nullptr ? l->mtime () : file_mtime (f.c_str ())); + + if (t > r) + r = t; + }; + + for (target* pt: t.prerequisite_targets) + { + file* f; + liba* a; + + if ((f = a = pt->is_a ()) || + (f = pt->is_a ())) + process_libraries (bs, lo, sys_lib_dirs, + *f, a != nullptr, + imp, lib, nullptr, true); } return r; } - // Like *_timestamp() but actually collect the DLLs. + // Like *_timestamp() but actually collect the DLLs (and weed out the + // duplicates). // - static void - rpath_dlls (set& s, file& t) + auto link:: + windows_rpath_dlls (file& t, scope& bs, lorder lo) const -> windows_dlls { - for (target* pt: t.prerequisite_targets) + windows_dlls r; + + auto imp = [] (file&, bool) {return true;}; + + auto lib = [&r] (file* l, const string& f, bool sys) { - if (libs* ls = pt->is_a ()) + if (sys) + return; + + if (l != nullptr) { - // Skip installed DLLs. - // - if (ls->path ().empty ()) - continue; + if (l->is_a () && !l->path ().empty ()) + { + // Get .pdb if there is one (second member of the ad hoc group). + // + const string* pdb ( + l->member != nullptr && l->member->member != nullptr + ? &static_cast (*l->member->member).path ().string () + : nullptr); + + r.insert (windows_dll {f, pdb, string ()}); + } + } + else + { + size_t p (path::traits::find_extension (f)); + + if (p != string::npos && casecmp (f.c_str () + p + 1, "dll") == 0) + { + // See if we can find a corresponding .pdb. + // + windows_dll wd {f, nullptr, string ()}; + string& pdb (wd.pdb_storage); + + // First try "our" naming: foo.dll.pdb. + // + pdb = f; + pdb += ".pdb"; - s.insert (ls); - rpath_dlls (s, *ls); + if (!file_exists (pdb.c_str ())) + { + // Then try the usual naming: foo.pdb. + // + pdb.assign (f, 0, p); + pdb += ".pdb"; + + if (!file_exists (pdb.c_str ())) + pdb.clear (); + } + + if (!pdb.empty ()) + wd.pdb = &pdb; + + r.insert (move (wd)); + } } + }; + + for (target* pt: t.prerequisite_targets) + { + file* f; + liba* a; + + if ((f = a = pt->is_a ()) || + (f = pt->is_a ())) + process_libraries (bs, lo, sys_lib_dirs, + *f, a != nullptr, + imp, lib, nullptr, true); } + + return r; } const char* @@ -103,11 +202,13 @@ namespace build2 // unnecessary work by comparing the DLLs timestamp against the assembly // manifest file. // - void + void link:: windows_rpath_assembly (file& t, + scope& bs, + lorder lo, const string& tcpu, timestamp ts, - bool scratch) + bool scratch) const { // Assembly paths and name. // @@ -139,9 +240,9 @@ namespace build2 // bool empty (ts == timestamp_nonexistent); - set dlls; + windows_dlls dlls; if (!empty) - rpath_dlls (dlls, t); + dlls = windows_rpath_dlls (t, bs, lo); // Clean the assembly directory and make sure it exists. Maybe it would // have been faster to overwrite the existing manifest rather than @@ -243,18 +344,22 @@ namespace build2 }; - for (libs* dll: dlls) + for (const windows_dll& wd: dlls) { - const path& dp (dll->path ()); // DLL path. - const path dn (dp.leaf ()); // DLL name. + //@@ Would be nice to avoid copying. Perhaps reuse buffers + // by adding path::assign() and traits::leaf(). + // + path dp (wd.dll); // DLL path. + path dn (dp.leaf ()); // DLL name. + link (dp, ad / dn); - // Link .pdb if there is one (second member of the ad hoc group). + // Link .pdb if there is one. // - if (dll->member != nullptr && dll->member->member != nullptr) + if (wd.pdb != nullptr) { - file& pdb (static_cast (*dll->member->member)); - link (pdb.path (), ad / pdb.path ().leaf ()); + path pp (*wd.pdb); + link (pp, ad / pp.leaf ()); } ofs << " \n"; -- cgit v1.1