aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-07-19 11:10:27 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-07-19 11:10:27 +0200
commit3ec07c196c9ab86db09c77bff7eb11cd5a5a9b1e (patch)
tree64fa1a8ec1e0152898843bb82a775c4ba1f194e0
parent1470c64377bfd29d434261208cd91b001b17c3f4 (diff)
Add support for building DLLs with VC
-rw-r--r--build2/algorithm10
-rw-r--r--build2/algorithm.cxx12
-rw-r--r--build2/buildfile1
-rw-r--r--build2/cxx/compile.cxx26
-rw-r--r--build2/cxx/link.cxx265
-rw-r--r--build2/cxx/msvc.cxx249
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;
+ }
+ }
+}