diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-19 11:10:27 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-19 11:10:27 +0200 |
commit | 3ec07c196c9ab86db09c77bff7eb11cd5a5a9b1e (patch) | |
tree | 64fa1a8ec1e0152898843bb82a775c4ba1f194e0 | |
parent | 1470c64377bfd29d434261208cd91b001b17c3f4 (diff) |
Add support for building DLLs with VC
-rw-r--r-- | build2/algorithm | 10 | ||||
-rw-r--r-- | build2/algorithm.cxx | 12 | ||||
-rw-r--r-- | build2/buildfile | 1 | ||||
-rw-r--r-- | build2/cxx/compile.cxx | 26 | ||||
-rw-r--r-- | build2/cxx/link.cxx | 265 | ||||
-rw-r--r-- | build2/cxx/msvc.cxx | 249 |
6 files changed, 421 insertions, 142 deletions
diff --git a/build2/algorithm b/build2/algorithm index 509e478..d52cca9 100644 --- a/build2/algorithm +++ b/build2/algorithm @@ -227,12 +227,12 @@ namespace build2 // 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: + // rather than a file. Next can come zero or more '-' characters which + // indicate the number of extensions that should stripped before the new + // extension (if any) is added (so if you want to strip the extension, + // specify "-"). For example: // - // clean_extra (a, t, {"+.d", "/+.dlls", ".dll"}); + // 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, diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 59a7800..595603c 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -500,11 +500,11 @@ namespace build2 if (d) ++e; - path p; - if (*e == '+') - p = ft.path () + ++e; - else - p = ft.path ().base () + e; + path p (ft.path ()); + for (; *e == '-'; ++e) + p = p.base (); + + p += e; target_state r (target_state::unchanged); @@ -614,6 +614,6 @@ namespace build2 target_state perform_clean_depdb (action a, target& t) { - return clean_extra (a, dynamic_cast<file&> (t), {"+.d"}); + return clean_extra (a, dynamic_cast<file&> (t), {".d"}); } } diff --git a/build2/buildfile b/build2/buildfile index cbd09e8..3986015 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -50,6 +50,7 @@ exe{b}: \ cxx/{hxx cxx}{ install } \ cxx/{hxx cxx}{ link } \ cxx/{hxx cxx}{ module } \ + cxx/{ cxx}{ msvc } \ cxx/{hxx cxx}{ target } \ cxx/{hxx ixx cxx}{ utility } \ cxx/{ cxx}{ windows-manifest } \ diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx index 085f6b4..9efa0b6 100644 --- a/build2/cxx/compile.cxx +++ b/build2/cxx/compile.cxx @@ -340,22 +340,24 @@ namespace build2 for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { - // -I can either be in the "-Ifoo" or "-I foo" form. - // - // @@ VC: should we also handle /I? + // -I can either be in the "-Ifoo" or "-I foo" form. For VC it can + // also be /I. // + const string& o (*i); + + if (o.size () < 2 || (o[0] != '-' && o[0] != '/') || o[1] != 'I') + continue; + dir_path d; - if (*i == "-I") + if (o.size () == 2) { if (++i == e) break; // Let the compiler complain. d = dir_path (*i); } - else if (i->compare (0, 2, "-I") == 0) - d = dir_path (*i, 2, string::npos); else - continue; + d = dir_path (*i, 2, string::npos); l6 ([&]{trace << "-I '" << d << "'";}); @@ -1298,8 +1300,8 @@ namespace build2 // create a .pdb per object file. // // Note that this also changes the name of the .idb file (used for - // minimal rebuild and incremental compilation) by taking /Fd value - // replacing the .pdb extension to .idb. + // minimal rebuild and incremental compilation): cl.exe take the /Fd + // value and replaces the .pdb extension with .idb. // // Note also that what we are doing here appears to be incompatible // with PCH (/Y* options) and /Gm (minimal rebuild). @@ -1361,6 +1363,8 @@ namespace build2 // @@ VC prints file name being compiled to stdout as the first // line, would be good to weed it out (but check if it is // always printed, for example if the file does not exist). + // Seems always. The same story with link.exe when creating + // the DLL. // // VC++ cl.exe sends diagnostics to stdout. To fix this (and any other @@ -1407,9 +1411,9 @@ namespace build2 initializer_list<const char*> e; if (cid == "msvc") - e = {"+.d", "+.idb", "+.pdb"}; + e = {".d", ".idb", ".pdb"}; else - e = {"+.d"}; + e = {".d"}; return clean_extra (a, t, e); } diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx index 7111aa5..e13cfe3 100644 --- a/build2/cxx/link.cxx +++ b/build2/cxx/link.cxx @@ -122,16 +122,10 @@ namespace build2 } } - // Extract system library search paths from MSVC. The linker doesn't seem - // to have any built-in paths and all of them are passed via the LIB - // environment variable. + // Extract system library search paths from MSVC. // - static void - msvc_library_search_paths (scope&, const string&, dir_paths&) - { - // @@ VC: how are we going to do this? E.g., cl-14 does this internally. - // Maybe that cld.c hack, seems to be passing stuff from INCLUDE..? - } + void + msvc_library_search_paths (scope&, const string&, dir_paths&); // msvc.cxx dir_paths link:: extract_library_paths (scope& bs) @@ -195,6 +189,14 @@ namespace build2 return r; } + // Alternative search for VC (msvc.cxx). + // + liba* + msvc_search_static (const path& ld, const dir_path&, prerequisite&); + + libs* + msvc_search_shared (const path& ld, const dir_path&, prerequisite&); + target* link:: search_library (optional<dir_paths>& spc, prerequisite& p) { @@ -227,6 +229,14 @@ namespace build2 // prefix/extension that correspond to this compiler and/or its // target. // + // Unlike MinGW, VC's .lib/.dll.lib naming is by no means standard and + // we might need to search for other names. In fact, there is no + // reliable way to guess from the file name what kind of library it + // is, static or import and we will have to do deep inspection of such + // alternative names. However, if we did find .dll.lib, then we can + // assume that .lib is the static library without any deep inspection + // overhead. + // const char* e (""); if (cid == "msvc") @@ -262,24 +272,8 @@ namespace build2 if (cid == "msvc") { - // @@ VC TODO: still .lib, right? - // - // @@ Unlike MinGW, .dll.lib naming is by no means standard. So - // we might need to search for other names. In fact, there is - // no reliable way to guess from the file name what kind of - // library it is, static or import lib. I wonder if there is - // any way to tell by examining it (e.g., presence of __imp_* - // symbols)? - // - // Yes, there are several, in fact. One is lib.exe /LIST -- if - // there aren't any members, then it is most likely an import (or - // an empty static library -- is such a thing possible?). - // - // Another approach is dumpbin.exe (or link.exe /DUMP equivalent) - // /ARCHIVEMEMBERS and /LINKERMEMBER options and the __impl__ - // symbols (or _IMPORT_DESCRIPTOR_). Note, however, that - // apparently it is possible to have a hybrid library. - // + sn = path (p.name); + e = "dll.lib"; } else { @@ -315,32 +309,12 @@ namespace build2 { timestamp mt; - // liba - // - if (!an.empty ()) - { - f = d; - f /= an; - - if ((mt = file_mtime (f)) != timestamp_nonexistent) - { - // Enter the target. Note that because the search paths are - // normalized, the result is automatically normalized as well. - // - // Note that this target is outside any project which we treat - // as out trees. - // - a = &targets.insert<liba> (d, dir_path (), p.name, ae, trace); - - if (a->path ().empty ()) - a->path (move (f)); - - a->mtime (mt); - } - } - // libs // + // Look for the shared library first. The order is important for VC: + // only if we found .dll.lib can we safely assumy that just .lib is a + // static library. + // if (!sn.empty ()) { f = d; @@ -374,6 +348,45 @@ namespace build2 } } + // liba + // + // If we didn't find .dll.lib then we cannot assume .lib is static. + // + if (!an.empty () && (s != nullptr || cid != "msvc")) + { + f = d; + f /= an; + + if ((mt = file_mtime (f)) != timestamp_nonexistent) + { + // Enter the target. Note that because the search paths are + // normalized, the result is automatically normalized as well. + // + // Note that this target is outside any project which we treat + // as out trees. + // + a = &targets.insert<liba> (d, dir_path (), p.name, ae, trace); + + if (a->path ().empty ()) + a->path (move (f)); + + a->mtime (mt); + } + } + + // Alternative search for VC. + // + if (cid == "msvc") + { + const path& ld (cast<path> (rs["config.bin.ld"])); + + if (s == nullptr && !sn.empty ()) + s = msvc_search_shared (ld, d, p); + + if (a == nullptr && !an.empty ()) + a = msvc_search_static (ld, d, p); + } + if (a != nullptr || s != nullptr) { pd = &d; @@ -635,8 +648,6 @@ namespace build2 } case otype::s: { - //@@ VC: DLL name. - if (tclass == "macosx") { p = "lib"; @@ -1401,7 +1412,7 @@ namespace build2 // Ok, so we are updating. Finish building the command line. // - string out, out1; // Storage. + string out, out1, out2; // Storage. // Translate paths to relative (to working directory) ones. This results // in easier to read diagnostics. @@ -1410,7 +1421,31 @@ namespace build2 switch (lt) { + case otype::a: + { + args[0] = cast<path> (rs["config.bin.ar"]).string ().c_str (); + + if (cid == "msvc") + { + // lib.exe has /LIBPATH but it's not clear/documented what it's + // used for. Perhaps for link-time code generation (/LTCG)? If + // that's the case, then we may need to pass cxx.loptions. + // + if (verb < 3) + args.push_back ("/NOLOGO"); + + out = "/OUT:" + relt.string (); + args.push_back (out.c_str ()); + } + else + args.push_back (relt.string ().c_str ()); + + break; + } + // The options are usually similar enough to handle them together. + // case otype::e: + case otype::s: { if (cid == "msvc") { @@ -1421,6 +1456,9 @@ namespace build2 if (verb < 3) args.push_back ("/NOLOGO"); + if (lt == otype::s) + args.push_back ("/DLL"); + // Unless explicitly enabled with /INCREMENTAL, disable // incremental linking (it is implicitly enabled if /DEBUG is // specified). The reason is the .ilk file: its name cannot be @@ -1437,7 +1475,7 @@ namespace build2 if (!find_option ("/INCREMENTAL", args, true)) args.push_back ("/INCREMENTAL:NO"); - // Take care of the manifest. + // Take care of the manifest (will be empty for the DLL). // if (!manifest.empty ()) { @@ -1447,9 +1485,24 @@ namespace build2 args.push_back (std.c_str ()); } + if (lt == otype::s) + { + // On Windows libs{} is the import stub and its first ad hoc + // group member is dll{}. + // + // 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. + // + out2 = "/IMPLIB:" + relt.string (); + args.push_back (out2.c_str ()); + + relt = relative (static_cast<file*> (t.member)->path ()); + } + // If we have /DEBUG then name the .pdb file. We call it - // foo.exe.pdb rather than foo.pdb because we can have, say, - // foo.dll in the same directory. + // foo.{exe,dll}.pdb rather than just foo.pdb because we can have, + // both foo.exe and foo.dll in the same directory. // if (find_option ("/DEBUG", args, true)) { @@ -1459,6 +1512,7 @@ namespace build2 // @@ An executable can have an import library and VS seems to // always name it. I wonder what would trigger its generation? + // Could it be the presence of export symbols? out = "/OUT:" + relt.string (); args.push_back (out.c_str ()); @@ -1466,67 +1520,31 @@ namespace build2 else { args[0] = cast<path> (rs["config.cxx"]).string ().c_str (); - args.push_back ("-o"); - args.push_back (relt.string ().c_str ()); - } - - break; - } - case otype::a: - { - args[0] = cast<path> (rs["config.bin.ar"]).string ().c_str (); - if (cid == "msvc") - { - // lib.exe has /LIBPATH but it's not clear/documented what it's - // used for. Perhaps for link-time code generation (/LTCG)? If - // that's the case, then we may need to pass cxx.loptions. + // Add the option that triggers building a shared library and take + // care of any extras (e.g., import library). // - if (verb < 3) - args.push_back ("/NOLOGO"); - - out = "/OUT:" + relt.string (); - args.push_back (out.c_str ()); - } - else - args.push_back (relt.string ().c_str ()); - - break; - } - case otype::s: - { - if (cid == "msvc") - { - //@@ VC TODO: DLL building (names via /link?) - } - else - { - args[0] = cast<path> (rs["config.cxx"]).string ().c_str (); - - // Add the option that triggers building a shared library. - // - if (tclass == "macosx") - args.push_back ("-dynamiclib"); - else - args.push_back ("-shared"); - - if (tsys == "mingw32") + if (lt == otype::s) { - // On Windows libs{} is the import stub and its first ad hoc - // group member is dll{}. - // - out = "-Wl,--out-implib=" + relt.string (); - relt = relative (static_cast<file*> (t.member)->path ()); + if (tclass == "macosx") + args.push_back ("-dynamiclib"); + else + args.push_back ("-shared"); - args.push_back ("-o"); - args.push_back (relt.string ().c_str ()); - args.push_back (out.c_str ()); - } - else - { - args.push_back ("-o"); - args.push_back (relt.string ().c_str ()); + if (tsys == "mingw32") + { + // On Windows libs{} is the import stub and its first ad hoc + // group member is dll{}. + // + out = "-Wl,--out-implib=" + relt.string (); + args.push_back (out.c_str ()); + + relt = relative (static_cast<file*> (t.member)->path ()); + } } + + args.push_back ("-o"); + args.push_back (relt.string ().c_str ()); } break; @@ -1678,25 +1696,32 @@ namespace build2 { if (tsys == "mingw32") { - e = {"+.d", "/+.dlls", "+.manifest.o", "+.manifest"}; + e = {".d", "/.dlls", ".manifest.o", ".manifest"}; } else { - // Assuming it's VC or alike. + // Assuming it's VC or alike. Clean up .ilk in case the user + // enabled incremental linking (note that .ilk replaces .exe). // - // Clean up .ilk in case the user enabled incremental linking. - // - e = {"+.d", "/+.dlls", "+.manifest", ".ilk"}; + e = {".d", "/.dlls", ".manifest", "-.ilk"}; } } else - e = {"+.d"}; + e = {".d"}; break; } case otype::s: { - e = {"+.d"}; + if (tclass == "windows" && tsys != "mingw32") + { + // Assuming it's VC or alike. Clean up .exp and .ilk. + // + e = {".d", "-.exp", "--.ilk"}; + } + else + e = {".d"}; + break; } } diff --git a/build2/cxx/msvc.cxx b/build2/cxx/msvc.cxx new file mode 100644 index 0000000..1c5a3fa --- /dev/null +++ b/build2/cxx/msvc.cxx @@ -0,0 +1,249 @@ +// file : build2/cxx/msvc.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build2/scope> +#include <build2/target> +#include <build2/context> +#include <build2/variable> +#include <build2/filesystem> +#include <build2/diagnostics> + +#include <build2/cxx/common> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cxx + { + using namespace bin; + + // Extract system library search paths from MSVC. + // + void + msvc_library_search_paths (scope&, const string&, dir_paths&) + { + // The linker doesn't seem to have any built-in paths and all of them + // come from the LIB environment variable. + + // @@ VC: how are we going to do this? E.g., cl-14 does this internally. + // cl.exe /Be prints LIB. + // + // Should we actually bother? LIB is normally used for system + // libraries and its highly unlikely we will see an explicit import + // for a library from one of those directories. + // + } + + // Inspect the file and determine if it is static or import library. + // Return otype::e if it is neither (which we quietly ignore). + // + static otype + library_type (const path& ld, const path& l) + { + // The are several reasonably reliable methods to tell whether it is a + // static or import library. One is lib.exe /LIST -- if there aren't any + // .obj members, then it is most likely an import library (it can also + // be an empty static library in which case there won't be any members). + // For an import library /LIST will print a bunch of .dll members. + // + // Another approach is dumpbin.exe (link.exe /DUMP) with /ARCHIVEMEMBERS + // (similar to /LIST) and /LINKERMEMBER (looking for __impl__ symbols or + // _IMPORT_DESCRIPTOR_). + // + // Note also, that apparently it is possible to have a hybrid library. + // + // While the lib.exe approach is probably the simplest, the problem is + // it will require us loading the bin.ar module even if we are not + // building any static libraries. On the other hand, if we are searching + // for libraries then we have bin.ld. So we will use the link.exe /DUMP + // /ARCHIVEMEMBERS. + // + const char* args[] = {ld.string ().c_str (), + "/DUMP", // Must come first. + "/NOLOGO", + "/ARCHIVEMEMBERS", + l.string ().c_str (), + nullptr}; + + // Link.exe seem to always dump everything to stdout but just in case + // redirect stderr to stdout. + // + process pr (start_run (args, false)); + ifdstream is (pr.in_ofd); + + bool obj (false), dll (false); + + string s; + while (getline (is, s)) + { + // Detect the one error we should let through. + // + if (s.compare (0, 18, "unable to execute ") == 0) + break; + + // The lines we are interested in seem to have this form (though + // presumably the "Archive member name at" part can be translated): + // + // Archive member name at 746: [...]hello.dll[/][ ]* + // Archive member name at 8C70: [...]hello.lib.obj[/][ ]* + // + size_t n (s.size ()); + + for (; n != 0 && s[n - 1] == ' '; --n) ; // Skip trailing spaces. + + if (n >= 7) // At least ": X.obj" or ": X.dll". + { + --n; + + if (s[n] == '/') // Skip trailing slash if one is there. + --n; + + n -= 3; // Beginning of extension. + + if (s[n] == '.') + { + // Make sure there is ": ". + // + size_t p (s.rfind (':', n - 1)); + + if (p != string::npos && s[p + 1] == ' ') + { + if (s.compare (n + 1, 3, "obj") == 0) // @@ CASE + obj = true; + + if (s.compare (n + 1, 3, "dll") == 0) // @@ CASE + dll = true; + } + } + } + } + + is.close (); // Don't block. + + if (!finish_run (args, false, pr, s)) + return otype::e; + + if (obj && dll) + { + warn << l << " looks like hybrid static/import library, ignoring"; + return otype::e; + } + + if (!obj && !dll) + { + warn << l << " looks like empty static or import library, ignoring"; + return otype::e; + } + + return obj ? otype::a : otype::s; + } + + template <typename T> + static T* + search_library (const path& ld, + const dir_path& d, + prerequisite& p, + otype lt, + const char* pfx, + const char* sfx) + { + // Pretty similar logic to link::search_library(). + // + tracer trace ("cxx::msvc_search_library"); + + // Assemble the file path. + // + path f (d); + + if (*pfx != '\0') + { + f /= pfx; + f += p.name; + } + else + f /= p.name; + + if (*sfx != '\0') + f += sfx; + + const string& e ( + p.ext == nullptr || p.is_a<lib> () // Only for liba/libs. + ? extension_pool.find ("lib") + : *p.ext); + + if (!e.empty ()) + { + f += '.'; + f += e; + } + + // Check if the file exists and is of the expected type. + // + timestamp mt (file_mtime (f)); + + if (mt != timestamp_nonexistent && library_type (ld, f) == lt) + { + // Enter the target. + // + T& t (targets.insert<T> (d, dir_path (), p.name, &e, trace)); + + if (t.path ().empty ()) + t.path (move (f)); + + t.mtime (mt); + return &t; + } + + return nullptr; + } + + liba* + msvc_search_static (const path& ld, const dir_path& d, prerequisite& p) + { + liba* r (nullptr); + + auto search = [&r, &ld, &d, &p] (const char* pf, const char* sf) -> bool + { + r = search_library<liba> (ld, d, p, otype::a, pf, sf); + return r != nullptr; + }; + + // Try: + // foo.lib + // libfoo.lib + // foolib.lib + // foo_static.lib + // + return + search ("", "") || + search ("lib", "") || + search ("", "lib") || + search ("", "_static") ? r : nullptr; + } + + libs* + msvc_search_shared (const path& ld, const dir_path& d, prerequisite& p) + { + libs* r (nullptr); + + auto search = [&r, &ld, &d, &p] (const char* pf, const char* sf) -> bool + { + r = search_library<libs> (ld, d, p, otype::s, pf, sf); + return r != nullptr; + }; + + // Try: + // foo.lib + // libfoo.lib + // foodll.lib + // + return + search ("", "") || + search ("lib", "") || + search ("", "dll") ? r : nullptr; + } + } +} |