From 89a9f8174ec858bf6df8515a84f061f211dec551 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 19 Jul 2016 17:10:51 +0200 Subject: Add import library target libi{}, make libs{} the DLL In the end, having libs{} be the DLL with import library being its member is more natural than making libs{} the import library and having dll{} as its member. --- build2/bin/module.cxx | 92 ++++++++++++-------- build2/bin/target | 12 +++ build2/bin/target.cxx | 13 +++ build2/cxx/compile.cxx | 4 +- build2/cxx/link.cxx | 201 ++++++++++++++++++++++++++++--------------- build2/cxx/module.cxx | 16 ---- build2/cxx/msvc.cxx | 17 +++- build2/cxx/windows-rpath.cxx | 65 +++++++------- build2/rule.cxx | 29 ++++--- build2/target | 12 ++- build2/target.cxx | 3 +- 11 files changed, 291 insertions(+), 173 deletions(-) diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx index 334384a..194b409 100644 --- a/build2/bin/module.cxx +++ b/build2/bin/module.cxx @@ -348,19 +348,59 @@ namespace build2 // const string& tclass (cast (r["bin.target.class"])); - // Register target types. + // Register target types and configure their default "installability". // + using namespace install; + { auto& t (b.target_types); + t.insert (); t.insert (); t.insert (); t.insert (); - t.insert (); + t.insert (); + install_path (b, dir_path ("bin")); // Install into install.bin. + + t.insert (); t.insert (); t.insert (); - t.insert (); + + install_path (b, dir_path ("lib")); // Install into install.lib. + install_mode (b, "644"); + + // Should shared libraries have the executable bit? That depends on + // who you ask. In Debian, for example, it should not unless, it + // really is executable (i.e., has main()). On the other hand, on + // some systems, this may be required in order for the dynamic + // linker to be able to load the library. So, by default, we will + // keep it executable, especially seeing that this is also the + // behavior of autotools. At the same time, it is easy to override + // this, for example: + // + // config.install.lib.mode=644 + // + // And a library that wants to override any such overrides (e.g., + // because it does have main()) can do: + // + // libs{foo}: install.mode=755 + // + // Everyone is happy then? On Windows libs{} is the DLL and goes to + // bin/, not lib/. + // + install_path (b, dir_path (tclass == "windows" ? "bin" : "lib")); + + // Create additional target types for certain targets. + // + if (tclass == "windows") + { + // Import library. + // + t.insert (); + install_path (b, dir_path ("lib")); + install_mode (b, "644"); + } } // Register rules. @@ -388,39 +428,6 @@ namespace build2 r.insert (perform_install_id, "bin.lib", lib_); } - // Configure "installability" of our target types. - // - using namespace install; - - install_path (b, dir_path ("bin")); // Install into install.bin. - - // Should shared libraries have executable bit? That depends on - // who you ask. In Debian, for example, it should not unless, it - // really is executable (i.e., has main()). On the other hand, on - // some systems, this may be required in order for the dynamic - // linker to be able to load the library. So, by default, we will - // keep it executable, especially seeing that this is also the - // behavior of autotools. At the same time, it is easy to override - // this, for example: - // - // config.install.lib.mode=644 - // - // And a library that wants to override any such overrides (e.g., - // because it does have main()) can do: - // - // libs{foo}: install.mode=755 - // - // Everyone is happy then? Not Windows users. When targeting Windows - // libs{} is an import library and shouldn't be exec. - // - install_path (b, dir_path ("lib")); // Install into install.lib. - - if (tclass == "windows") - install_mode (b, "644"); - - install_path (b, dir_path ("lib")); // Install into install.lib. - install_mode (b, "644"); - return true; } @@ -485,6 +492,19 @@ namespace build2 r.assign ("bin.ld.checksum") = move (ldi.checksum); } + const string& lid (cast (r["bin.ld.id"])); + + // Register the pdb{} target if using the VC toolchain. + // + using namespace install; + + if (lid == "msvc") + { + const target_type& pdb (b.derive_target_type ("pdb").first); + install_path (pdb, b, dir_path ("bin")); // Goes to install.bin + install_mode (pdb, b, "644"); // But not executable. + } + return true; } diff --git a/build2/bin/target b/build2/bin/target index 8c32e84..849316e 100644 --- a/build2/bin/target +++ b/build2/bin/target @@ -107,6 +107,18 @@ namespace build2 static const target_type static_type; virtual const target_type& dynamic_type () const {return static_type;} }; + + // Windows import library. + // + class libi: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; } } diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index 3f16467..d78d34c 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -261,5 +261,18 @@ namespace build2 &search_target, false }; + + // libi + // + const target_type libi::static_type + { + "libi", + &file::static_type, + &target_factory, + &target_extension_var, + nullptr, + &search_file, + false + }; } } diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx index 9efa0b6..432b485 100644 --- a/build2/cxx/compile.cxx +++ b/build2/cxx/compile.cxx @@ -67,7 +67,7 @@ namespace build2 { tracer trace ("cxx::compile"); - path_target& t (static_cast (xt)); + file& t (static_cast (xt)); scope& bs (t.base_scope ()); scope& rs (*bs.root_scope ()); @@ -1235,7 +1235,7 @@ namespace build2 target_state compile:: perform_update (action a, target& xt) { - path_target& t (static_cast (xt)); + file& t (static_cast (xt)); cxx* s (execute_prerequisites (a, t, t.mtime ())); if (s == nullptr) diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx index e13cfe3..b80fc8b 100644 --- a/build2/cxx/link.cxx +++ b/build2/cxx/link.cxx @@ -202,6 +202,10 @@ namespace build2 { tracer trace ("cxx::link::search_library"); + // @@ This is hairy enough to warrant a separate implementation for + // Windows. + // + // First check the cache. // if (p.target != nullptr) @@ -210,6 +214,7 @@ namespace build2 scope& rs (*p.scope.root_scope ()); const string& cid (cast (rs["cxx.id"])); const string& tsys (cast (rs["cxx.target.system"])); + const string& tclass (cast (rs["cxx.target.class"])); bool l (p.is_a ()); const string* ext (l ? nullptr : p.ext); // Only for liba/libs. @@ -321,7 +326,48 @@ namespace build2 f /= sn; mt = file_mtime (f); - if (tsys == "mingw32") + if (mt != timestamp_nonexistent) + { + // On Windows what we found is the import library which we need + // to make the first ad hoc member of libs{}. + // + if (tclass == "windows") + { + s = &targets.insert ( + d, dir_path (), p.name, nullptr, trace); + + if (s->member == nullptr) + { + libi& i ( + targets.insert ( + d, dir_path (), p.name, se, trace)); + + if (i.path ().empty ()) + i.path (move (f)); + + i.mtime (mt); + + // Presumably there is a DLL somewhere, we just don't know + // where (and its possible we might have to look for one if we + // decide we need to do rpath emulation for installed + // libraries as well). We will represent this as empty path + // but valid timestamp (aka "trust me, it's there"). + // + s->mtime (mt); + s->member = &i; + } + } + else + { + s = &targets.insert (d, dir_path (), p.name, se, trace); + + if (s->path ().empty ()) + s->path (move (f)); + + s->mtime (mt); + } + } + else if (ext == nullptr && tsys == "mingw32") { // Above we searched for the import library (.dll.a) but if it's // not found, then we also search for the .dll (unless the @@ -329,22 +375,19 @@ namespace build2 // directly. Note also that the resulting libs{} would end up // being the .dll. // - if (mt == timestamp_nonexistent && ext == nullptr) - { - se = &extension_pool.find ("dll"); - f = f.base (); // Remove .a from .dll.a. - mt = file_mtime (f); - } - } + se = &extension_pool.find ("dll"); + f = f.base (); // Remove .a from .dll.a. + mt = file_mtime (f); - if (mt != timestamp_nonexistent) - { - s = &targets.insert (d, dir_path (), p.name, se, trace); + if (mt != timestamp_nonexistent) + { + s = &targets.insert (d, dir_path (), p.name, se, trace); - if (s->path ().empty ()) - s->path (move (f)); + if (s->path ().empty ()) + s->path (move (f)); - s->mtime (mt); + s->mtime (mt); + } } } @@ -595,7 +638,7 @@ namespace build2 { tracer trace ("cxx::link::apply"); - path_target& t (static_cast (xt)); + file& t (static_cast (xt)); scope& bs (t.base_scope ()); scope& rs (*bs.root_scope ()); @@ -656,28 +699,13 @@ namespace build2 else if (tclass == "windows") { // On Windows libs{} is an ad hoc group. The libs{} itself is - // the import library and we add dll{} as a member (see below). - // While at first it may seem strange that libs{} is the import - // library and not the DLL, if you meditate on it, you will see - // it makes a lot of sense: our main task here is building and - // for that we need the import library, not the DLL. + // the DLL and we add libi{} import library as its member (see + // below). // if (tsys == "mingw32") - { p = "lib"; - e = "dll.a"; - } - else - { - // 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. - // - e = "dll.lib"; - } + + e = "dll"; } else { @@ -713,14 +741,21 @@ namespace build2 if (tclass == "windows") { - // DLL + // Import library. // if (lt == otype::s) { - file& dll (add_adhoc (t, "dll")); - - if (dll.path ().empty ()) - dll.derive_path ("dll", tsys == "mingw32" ? "lib" : nullptr); + 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"); } // PDB @@ -729,10 +764,13 @@ namespace build2 cid == "msvc" && find_option ("/DEBUG", t, "cxx.loptions", true)) { - // Add after the DLL if any. + // 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"); } @@ -1360,17 +1398,28 @@ namespace build2 for (target* pt: t.prerequisite_targets) { - path_target* ppt; + file* f; liba* a (nullptr); + libs* s (nullptr); - if ((ppt = pt->is_a ()) || - (ppt = pt->is_a ()) || - (ppt = pt->is_a ()) || + if ((f = pt->is_a ()) || + (f = pt->is_a ()) || + (f = pt->is_a ()) || (lt != otype::a && - ((ppt = a = pt->is_a ()) || - (ppt = pt->is_a ())))) + ((f = a = pt->is_a ()) || + (f = s = pt->is_a ())))) { - cs.append (ppt->path ().string ()); + // 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 ()); // If this is a static library, link all the libraries it depends // on, recursively. @@ -1487,26 +1536,26 @@ namespace build2 if (lt == otype::s) { - // On Windows libs{} is the import stub and its first ad hoc - // group member is dll{}. + // On Windows libs{} is the DLL and its first ad hoc group + // member is the import library. // // This will also create the .exp export file. Its name will be // derived from the import library by changing the extension. - // Lucky us -- there is no option to name it. + // Lucky for us -- there is no option to name it. // - out2 = "/IMPLIB:" + relt.string (); + auto imp (static_cast (t.member)); + out2 = "/IMPLIB:" + relative (imp->path ()).string (); args.push_back (out2.c_str ()); - - relt = relative (static_cast (t.member)->path ()); } - // If we have /DEBUG then name the .pdb file. 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 we have /DEBUG then name the .pdb file. It is either the + // first (exe) or the second (dll) ad hoc group member. // if (find_option ("/DEBUG", args, true)) { - out1 = "/PDB:" + relt.string () + ".pdb"; + auto pdb (static_cast ( + lt == otype::e ? t.member : t.member->member)); + out1 = "/PDB:" + relative (pdb->path ()).string (); args.push_back (out1.c_str ()); } @@ -1533,13 +1582,12 @@ namespace build2 if (tsys == "mingw32") { - // On Windows libs{} is the import stub and its first ad hoc - // group member is dll{}. + // On Windows libs{} is the DLL and its first ad hoc group + // member is the import library. // - out = "-Wl,--out-implib=" + relt.string (); + auto imp (static_cast (t.member)); + out = "-Wl,--out-implib=" + relative (imp->path ()).string (); args.push_back (out.c_str ()); - - relt = relative (static_cast (t.member)->path ()); } } @@ -1553,17 +1601,28 @@ namespace build2 for (target* pt: t.prerequisite_targets) { - path_target* ppt; + file* f; liba* a (nullptr); + libs* s (nullptr); - if ((ppt = pt->is_a ()) || - (ppt = pt->is_a ()) || - (ppt = pt->is_a ()) || + if ((f = pt->is_a ()) || + (f = pt->is_a ()) || + (f = pt->is_a ()) || (lt != otype::a && - ((ppt = a = pt->is_a ()) || - (ppt = pt->is_a ())))) + ((f = a = pt->is_a ()) || + (f = s = pt->is_a ())))) { - sargs.push_back (relative (ppt->path ()).string ()); // string()&& + // 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()&& // If this is a static library, link all the libraries it depends // on, recursively. @@ -1687,7 +1746,7 @@ namespace build2 { case otype::a: { - e = {"+.d"}; + e = {".d"}; break; } case otype::e: @@ -1717,7 +1776,7 @@ namespace build2 { // Assuming it's VC or alike. Clean up .exp and .ilk. // - e = {".d", "-.exp", "--.ilk"}; + e = {".d", ".exp", "-.ilk"}; } else e = {".d"}; diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx index 69e76a3..8972bf9 100644 --- a/build2/cxx/module.cxx +++ b/build2/cxx/module.cxx @@ -258,7 +258,6 @@ namespace build2 const string& cid (cast (r["cxx.id"])); const string& tsys (cast (r["cxx.target.system"])); - const string& tclass (cast (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. @@ -361,21 +360,6 @@ namespace build2 install_path (b, dir_path ("include")); install_path (b, dir_path ("include")); - // Create additional target types for certain target platforms. - // - if (tclass == "windows") - { - const target_type& dll (b.derive_target_type ("dll").first); - install_path (dll, b, dir_path ("bin")); - - if (cid == "msvc") - { - const target_type& pdb (b.derive_target_type ("pdb").first); - install_path (pdb, b, dir_path ("bin")); - install_mode (pdb, b, "644"); - } - } - return true; } } diff --git a/build2/cxx/msvc.cxx b/build2/cxx/msvc.cxx index 1c5a3fa..ff91018 100644 --- a/build2/cxx/msvc.cxx +++ b/build2/cxx/msvc.cxx @@ -227,11 +227,24 @@ namespace build2 libs* msvc_search_shared (const path& ld, const dir_path& d, prerequisite& p) { + tracer trace ("cxx::msvc_search_shared"); + libs* r (nullptr); - auto search = [&r, &ld, &d, &p] (const char* pf, const char* sf) -> bool + auto search = [&r, &ld, &d, &p, &trace] ( + const char* pf, const char* sf) -> bool { - r = search_library (ld, d, p, otype::s, pf, sf); + if (libi* i = search_library (ld, d, p, otype::s, pf, sf)) + { + r = &targets.insert (d, dir_path (), p.name, nullptr, trace); + + if (r->member == nullptr) + { + r->mtime (i->mtime ()); + r->member = i; + } + } + return r != nullptr; }; diff --git a/build2/cxx/windows-rpath.cxx b/build2/cxx/windows-rpath.cxx index 0bd4bc5..b5bad5e 100644 --- a/build2/cxx/windows-rpath.cxx +++ b/build2/cxx/windows-rpath.cxx @@ -51,22 +51,18 @@ namespace build2 { if (libs* ls = pt->is_a ()) { - // 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. + // Skip installed DLLs. // - if (ls->member == nullptr) + if (ls->path ().empty ()) continue; - file& dll (static_cast (*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) + if ((t = ls->mtime ()) > r) r = t; if ((t = windows_rpath_timestamp (*ls)) > r) @@ -80,18 +76,18 @@ namespace build2 // Like *_timestamp() but actually collect the DLLs. // static void - rpath_dlls (set& s, file& t) + rpath_dlls (set& s, file& t) { for (target* pt: t.prerequisite_targets) { if (libs* ls = pt->is_a ()) { - if (ls->member == nullptr) + // Skip installed DLLs. + // + if (ls->path ().empty ()) continue; - file& dll (static_cast (*ls->member)); - - s.insert (&dll); + s.insert (ls); rpath_dlls (s, *ls); } } @@ -143,7 +139,7 @@ namespace build2 // bool empty (ts == timestamp_nonexistent); - set dlls; + set dlls; if (!empty) rpath_dlls (dlls, t); @@ -185,16 +181,12 @@ namespace build2 scope& as (*rs.weak_scope ()); // Amalgamation scope. - for (file* dt: dlls) + auto link = [&as, &ad] (const path& f, const path& l) { - 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) + auto print = [&f, &l] (const char* cmd) { if (verb >= 3) - text << cmd << ' ' << dp << ' ' << lp; + text << cmd << ' ' << f << ' ' << l; }; // First we try to create a symlink. If that fails (e.g., "Windows @@ -208,10 +200,10 @@ namespace build2 // 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); + if (f.sub (as.out_path ())) + mksymlink (f.relative (ad), l); else - mksymlink (dp, lp); + mksymlink (f, l); print ("ln -s"); } @@ -222,12 +214,12 @@ namespace build2 if (c != EPERM && c != ENOSYS) { print ("ln -s"); - fail << "unable to create symlink " << lp << ": " << e.what (); + fail << "unable to create symlink " << l << ": " << e.what (); } try { - mkhardlink (dp, lp); + mkhardlink (f, l); print ("ln"); } catch (const system_error& e) @@ -237,23 +229,38 @@ namespace build2 if (c != EPERM && c != ENOSYS) { print ("ln"); - fail << "unable to create hard link " << lp << ": " - << e.what (); + fail << "unable to create hardlink " << l << ": " << e.what (); } try { - cpfile (dp, lp); + cpfile (f, l); print ("cp"); } catch (const system_error& e) { print ("cp"); - fail << "unable to create copy " << lp << ": " << e.what (); + fail << "unable to create copy " << l << ": " << e.what (); } } } + }; + + for (libs* dll: dlls) + { + const path& dp (dll->path ()); // DLL path. + const path dn (dp.leaf ()); // DLL name. + link (dp, ad / dn); + + // Link .pdb if there is one (second member of the ad hoc group). + // + if (dll->member != nullptr && dll->member->member != nullptr) + { + file& pdb (static_cast (*dll->member->member)); + link (pdb.path (), ad / pdb.path ().leaf ()); + } + ofs << " \n"; } diff --git a/build2/rule.cxx b/build2/rule.cxx index cbb7d08..0fcfde2 100644 --- a/build2/rule.cxx +++ b/build2/rule.cxx @@ -44,18 +44,25 @@ namespace build2 { path_target& pt (dynamic_cast (t)); - // Assign the path. While normally we shouldn't do this in match(), - // no other rule should ever be ambiguous with the fallback one. + // First check the timestamp. This allows for the special "trust me, + // this file exists" situations (used, for example, for installed + // stuff where we know it's there, just not exactly where). // - if (pt.path ().empty ()) - pt.derive_path (); - - // We cannot just call pt.mtime() since we haven't matched yet. - // - timestamp ts (file_mtime (pt.path ())); - pt.mtime (ts); - - if (ts != timestamp_nonexistent) + timestamp ts (pt.mtime ()); + + if (ts == timestamp_unknown) + { + // Assign the path. While normally we shouldn't do this in match(), + // no other rule should ever be ambiguous with the fallback one. + // + if (pt.path ().empty ()) + { + pt.derive_path (); + ts = pt.mtime (); + } + } + + if (ts != timestamp_unknown && ts != timestamp_nonexistent) return t; l4 ([&]{trace << "no existing file for target " << t;}); diff --git a/build2/target b/build2/target index 28753df..dfc6abc 100644 --- a/build2/target +++ b/build2/target @@ -954,10 +954,12 @@ namespace build2 public: using target::target; - // Generally, modification time for a target can only be queried - // after a rule has been matched since that's where the path is - // normally gets assigned. Normally, however, it would make sense - // to first execute the rule to get the "updated" timestamp. + // Generally, modification time for a target can only be queried after a + // rule has been matched since that's where the path is normally gets + // assigned (if the path was not assigned and no timestamp was set + // manually, this function will return timestamp_unknown). Normally, + // however, it would make sense to first execute the rule to get the + // "updated" timestamp. // // The rule for groups that utilize the group state is as follows: // if it has any members that are mtime_targets, then the group @@ -986,6 +988,8 @@ namespace build2 } protected: + // Return timestamp_unknown if the mtime cannot be loaded. + // virtual timestamp load_mtime () const = 0; diff --git a/build2/target.cxx b/build2/target.cxx index a8fae39..b0fc92d 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -383,8 +383,7 @@ namespace build2 load_mtime () const { const path_type& f (path ()); - assert (!f.empty ()); - return file_mtime (f); + return f.empty () ? timestamp_unknown : file_mtime (f); } // Search functions. -- cgit v1.1