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/link | 31 ++++- build2/cc/link.cxx | 269 ++++++++++++++++++++++---------------------- build2/cc/windows-rpath.cxx | 187 +++++++++++++++++++++++------- build2/target | 4 +- build2/utility | 2 + 5 files changed, 314 insertions(+), 179 deletions(-) diff --git a/build2/cc/link b/build2/cc/link index 2652285..05f7c5a 100644 --- a/build2/cc/link +++ b/build2/cc/link @@ -5,6 +5,8 @@ #ifndef BUILD2_CC_LINK #define BUILD2_CC_LINK +#include + #include #include @@ -50,7 +52,8 @@ namespace build2 const function&) const; + bool)>&, + bool = false) const; void append_libraries (strings&, file&, bool, scope&, lorder) const; @@ -58,8 +61,32 @@ namespace build2 hash_libraries (sha256&, file&, bool, scope&, lorder) const; void - rpath_libraries (strings&, file&, bool, scope&, lorder, bool) const; + rpath_libraries (strings&, target&, scope&, lorder, bool) const; + + // Windows rpath emulation (windows-rpath.cxx). + // + struct windows_dll + { + const string& dll; + const string* pdb; // NULL if none. + string pdb_storage; + + bool operator< (const windows_dll& y) const {return dll < y.dll;} + }; + using windows_dlls = std::set; + + timestamp + windows_rpath_timestamp (file&, scope&, lorder) const; + + windows_dlls + windows_rpath_dlls (file&, scope&, lorder) const; + + void + windows_rpath_assembly (file&, scope&, lorder, + const string&, + timestamp, + bool) const; file& resolve_library (name, scope&, diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index d8ccc0e..0c3534c 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -1007,6 +1007,10 @@ namespace build2 // *.export.libs is up-to-date (which will happen automatically if it is // listed as a prerequisite of this library). // + // Storing a reference to library path in proc_lib is legal (it comes + // either from the target's path or from one of the *.libs variables + // neither of which should change on this run). + // void link:: process_libraries ( scope& top_bs, @@ -1020,17 +1024,51 @@ namespace build2 const string& path, // Library path. bool sys)>& proc_lib, // True if system library. const function& proc_opt) const // *.export. + const string& type, // cc.type + bool com, // cc. or x. + bool exp)>& proc_opt, // *.export. + bool self /*= false*/) const // Call proc_lib on l? { - bool impl (proc_impl && proc_impl (l, la)); + // Determine if an absolute path is to a system library. Note that + // we assume both paths to be normalized. + // + auto sys = [] (const dir_paths& sysd, const string& p) -> bool + { + size_t pn (p.size ()); + + for (const dir_path& d: sysd) + { + const string& ds (d.string ()); // Can be "/", otherwise no slash. + size_t dn (ds.size ()); + + if (pn > dn && + p.compare (0, dn, ds) == 0 && + (path::traits::is_separator (ds[dn - 1]) || + path::traits::is_separator (p[dn]))) + return true; + } + + return false; + }; // See what type of library this is (C, C++, etc). Use it do decide // which x.libs variable name to use. If it's unknown, then we only // look into prerequisites. // const string* t (cast_null (l.vars[c_type])); + + if (self && proc_lib) + { + const string& p (l.path ().string ()); + + bool s (t != nullptr // If cc library (matched or imported). + ? cast_false (l.vars[c_system]) + : sys (top_sysd, p)); + + proc_lib (&l, p, s); + } + + bool impl (proc_impl && proc_impl (l, la)); bool cc, same; auto& vp (var_pool); @@ -1066,47 +1104,28 @@ namespace build2 } } + scope& bs (t == nullptr || cc ? top_bs : l.base_scope ()); + optional lo; // Calculate lazily. + const dir_paths* sysd (nullptr); // Resolve lazily. + // Find system search directories corresponding to this library, i.e., // from its project and for its type (C, C++, etc). // - auto find_sysd = [&top_sysd, this] ( - bool cc, bool same, const string& t, scope& bs) -> const dir_paths& + auto find_sysd = [&top_sysd, t, cc, same, &bs, &sysd, this] () { // Use the search dirs corresponding to this library scope/type. // - return cc - ? top_sysd // Imported library, use importer's sysd. - : cast ( + sysd = (t == nullptr || cc) + ? &top_sysd // Imported library, use importer's sysd. + : &cast ( bs.root_scope ()->vars[same ? x_sys_lib_dirs - : var_pool[t + ".sys_lib_dirs"]]); + : var_pool[*t + ".sys_lib_dirs"]]); }; - auto find_lo = [top_lo, this] (file& l, bool cc, scope& bs) -> lorder + auto find_lo = [top_lo, t, cc, &bs, &l, &lo, this] () { - return cc ? top_lo : link_order (bs, link_type (l)); - }; - - // Determine if an absolute path is to a system library. Note that - // we assume both paths to be normalized. - // - auto sys = [] (const dir_paths& sysd, const string& p) -> bool - { - size_t pn (p.size ()); - - for (const dir_path& d: sysd) - { - const string& ds (d.string ()); // Can be "/", otherwise no slash. - size_t dn (ds.size ()); - - if (pn > dn && - p.compare (0, dn, ds) == 0 && - (path::traits::is_separator (ds[dn - 1]) || - path::traits::is_separator (p[dn]))) - return true; - } - - return false; + lo = (t == nullptr || cc) ? top_lo : link_order (bs, link_type (l)); }; // Only go into prerequisites (implementation) if instructed and we are @@ -1123,23 +1142,12 @@ namespace build2 if ((a = (f = p->is_a ()) != nullptr) || (f = p->is_a ()) != nullptr) { - if (proc_lib) - { - const string& p (f->path ().string ()); - proc_lib (f, p, sys (top_sysd, p)); - } - - const string& t (cast (f->vars[c_type])); // Must be there. - bool cc (t == "cc"); - bool same (!cc && t == x); - - scope& bs (cc ? top_bs : f->base_scope ()); + if (sysd == nullptr) find_sysd (); + if (!lo) find_lo (); - process_libraries (bs, - find_lo (*f, cc, bs), - find_sysd (cc, same, t, bs), + process_libraries (bs, *lo, *sysd, *f, a, - proc_impl, proc_lib, proc_opt); + proc_impl, proc_lib, proc_opt, true); } } } @@ -1153,22 +1161,17 @@ namespace build2 if (t == nullptr) return; - scope& bs (cc ? top_bs : l.base_scope ()); - optional lo; // Calculate lazily. - const dir_paths* sysd (nullptr); // Resolve lazily. optional usrd; // Extract lazily. // Determine if a "simple path" is a system library. // - auto sys_simple = [cc, same, t, &bs, &sysd, &sys, &find_sysd] ( - const string& p) -> bool + auto sys_simple = [&sysd, &sys, &find_sysd] (const string& p) -> bool { bool s (!path::traits::absolute (p)); if (!s) { - if (sysd == nullptr) - sysd = &find_sysd (cc, same, *t, bs); + if (sysd == nullptr) find_sysd (); s = sys (*sysd, p); } @@ -1176,7 +1179,7 @@ namespace build2 return s; }; - auto proc_int = [&l, cc, same, t, + auto proc_int = [&l, &proc_impl, &proc_lib, &proc_opt, &sysd, &usrd, &find_sysd, &find_lo, &sys, &sys_simple, @@ -1202,11 +1205,8 @@ namespace build2 { // This is a potentially project-qualified target. // - if (sysd == nullptr) - sysd = &find_sysd (cc, same, *t, bs); - - if (!lo) - lo = find_lo (l, cc, bs); + if (sysd == nullptr) find_sysd (); + if (!lo) find_lo (); file& t (resolve_library (n, bs, *lo, *sysd, usrd)); @@ -1217,25 +1217,21 @@ namespace build2 // library's prerequisites (i.e., it is not an implementation // dependency). // - if (t.path ().empty ()) - fail << "target " << t << " is out of date" << + // Note that we used to just check for path being assigned but + // on Windows import-installed DLLs may legally have empty + // paths. + // + if (t.mtime (false) == timestamp_unknown) + fail << "interface dependency " << t << " is out of date" << info << "mentioned in *.export.libs of target " << l << info << "is it a prerequisite of " << l << "?"; - - const string& p (t.path ().string ()); - - bool s (t.vars[c_type] // If cc library (matched or imported). - ? cast_false (t.vars[c_system]) - : sys (*sysd, p)); - - proc_lib (&t, p, s); } // Process it recursively. // process_libraries (bs, *lo, *sysd, t, t.is_a (), - proc_impl, proc_lib, proc_opt); + proc_impl, proc_lib, proc_opt, true); } } }; @@ -1376,11 +1372,25 @@ namespace build2 file& l, bool la, scope& bs, lorder lo) const { + bool win (tclass == "windows"); + auto imp = [] (file&, bool la) {return la;}; - auto lib = [&args] (file* f, const string& p, bool) + auto lib = [&args, win] (file* f, const string& p, bool) { - args.push_back (f != nullptr ? relative (f->path ()).string () : p); + if (f != nullptr) + { + // On Windows a shared library is a DLL with the import library as a + // first ad hoc group member. MinGW though can link directly to DLLs + // (see search_library() for details). + // + if (win && f->member != nullptr && f->is_a ()) + f = static_cast (f->member); + + args.push_back (relative (f->path ()).string ()); + } + else + args.push_back (p); }; auto opt = [&args, this] (file& l, const string& t, bool com, bool exp) @@ -1400,17 +1410,31 @@ namespace build2 } }; - process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt); + process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt, true); } void link:: hash_libraries (sha256& cs, file& l, bool la, scope& bs, lorder lo) const { + bool win (tclass == "windows"); + auto imp = [] (file&, bool la) {return la;}; - auto lib = [&cs] (file*, const string& p, bool) + auto lib = [&cs, win] (file* f, const string& p, bool) { - cs.append (p); + if (f != nullptr) + { + // On Windows a shared library is a DLL with the import library as a + // first ad hoc group member. MinGW though can link directly to DLLs + // (see search_library() for details). + // + if (win && f->member != nullptr && f->is_a ()) + f = static_cast (f->member); + + cs.append (f->path ().string ()); + } + else + cs.append (p); }; auto opt = [&cs, this] (file& l, const string& t, bool com, bool exp) @@ -1428,13 +1452,12 @@ namespace build2 } }; - process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt); + process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, opt, true); } void link:: rpath_libraries (strings& args, - file& l, bool la, - scope& bs, lorder lo, + target& t, scope& bs, lorder lo, bool for_install) const { // Use -rpath-link on targets that support it (Linux, FreeBSD). Note @@ -1445,14 +1468,6 @@ namespace build2 if (tclass != "linux" && tclass != "freebsd") return; } - else if (!la) - { - // Top-level sharen library dependency. It is either matched or - // imported so should be a cc library. - // - if (!cast_false (l.vars[c_system])) - args.push_back ("-Wl,-rpath," + l.path ().directory ().string ()); - } auto imp = [for_install] (file&, bool la) { @@ -1532,16 +1547,30 @@ namespace build2 d.args.push_back (move (o)); }; - process_libraries (bs, lo, sys_lib_dirs, l, la, imp, lib, nullptr); - } + for (target* pt: t.prerequisite_targets) + { + file* f; + liba* a; - // See windows-rpath.cxx. - // - timestamp - windows_rpath_timestamp (file&); + if ((f = a = pt->is_a ()) || + (f = pt->is_a ())) + { + if (!for_install && a == nullptr) + { + // Top-level sharen library dependency. It is either matched or + // imported so should be a cc library. + // + if (!cast_false (f->vars[c_system])) + args.push_back ( + "-Wl,-rpath," + f->path ().directory ().string ()); + } - void - windows_rpath_assembly (file&, const string& cpu, timestamp, bool scratch); + process_libraries (bs, lo, sys_lib_dirs, + *f, a != nullptr, + imp, lib, nullptr); + } + } + } // Filter link.exe noise (msvc.cxx). // @@ -1585,7 +1614,7 @@ namespace build2 // it if we are updating for install. // if (!for_install) - rpath_timestamp = windows_rpath_timestamp (t); + rpath_timestamp = windows_rpath_timestamp (t, bs, lo); path mf ( windows_manifest ( @@ -1828,15 +1857,7 @@ namespace build2 // rpath of the imported libraries (i.e., we assume they are also // installed). But we add -rpath-link for some platforms. // - for (target* pt: t.prerequisite_targets) - { - file* f; - liba* a; - - if ((f = a = pt->is_a ()) || - (f = pt->is_a ())) - rpath_libraries (sargs, *f, a != nullptr, bs, lo, for_install); - } + rpath_libraries (sargs, t, bs, lo, for_install); if (auto l = t["bin.rpath"]) for (const dir_path& p: cast (l)) @@ -1882,23 +1903,13 @@ namespace build2 ((f = a = pt->is_a ()) || (f = s = pt->is_a ())))) { - // On Windows a shared library is a DLL with the import library as - // a first ad hoc group member. MinGW though can link directly to - // DLLs (see search_library() for details). - // - if (s != nullptr && tclass == "windows") - { - if (s->member != nullptr) - f = static_cast (s->member); - } - - cs.append (f->path ().string ()); - - // Link all the dependent interface libraries (shared) or - // interface and implementation (static), recursively. + // Link all the dependent interface libraries (shared) or interface + // and implementation (static), recursively. // if (a != nullptr || s != nullptr) hash_libraries (cs, *f, a != nullptr, bs, lo); + else + cs.append (f->path ().string ()); } } @@ -2118,23 +2129,13 @@ namespace build2 ((f = a = pt->is_a ()) || (f = s = pt->is_a ())))) { - // On Windows a shared library is a DLL with the import library as a - // first ad hoc group member. MinGW though can link directly to DLLs - // (see search_library() for details). - // - if (s != nullptr && tclass == "windows") - { - if (s->member != nullptr) - f = static_cast (s->member); - } - - sargs.push_back (relative (f->path ()).string ()); // string()&& - // Link all the dependent interface libraries (shared) or interface // and implementation (static), recursively. // if (a != nullptr || s != nullptr) append_libraries (sargs, *f, a != nullptr, bs, lo); + else + sargs.push_back (relative (f->path ()).string ()); // string()&& } } @@ -2256,7 +2257,7 @@ namespace build2 if (lt == otype::e && tclass == "windows") { if (!for_install) - windows_rpath_assembly (t, + windows_rpath_assembly (t, bs, lo, cast (rs[x_target_cpu]), rpath_timestamp, scratch); 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"; diff --git a/build2/target b/build2/target index 103b719..681fcee 100644 --- a/build2/target +++ b/build2/target @@ -985,13 +985,13 @@ namespace build2 // should be mtime_target and the members get the mtime from it. // timestamp - mtime () const + mtime (bool load = true) const { const mtime_target* t (raw_state == target_state::group ? static_cast (group) : this); - if (t->mtime_ == timestamp_unknown) + if (load && t->mtime_ == timestamp_unknown) t->mtime_ = t->load_mtime (); return t->mtime_; diff --git a/build2/utility b/build2/utility index 7f05dd0..6c73e8e 100644 --- a/build2/utility +++ b/build2/utility @@ -37,6 +37,8 @@ namespace build2 using butl::combine_hash; using butl::reverse_iterate; using butl::casecmp; + using butl::case_compare_string; + using butl::case_compare_c_string; using butl::lcase; using butl::alpha; using butl::alnum; -- cgit v1.1