aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-05-20 23:25:45 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-06-11 19:06:40 +0200
commit4218bfe7668d55e36814d6bdcec2da40454025c1 (patch)
tree1abe3f38109ba83755adf7307414dbbd1e1a9eb8
parent9f56941794837ec63b8732d8d0cae4659528e714 (diff)
Initial VC++ support (static libraries only)
-rw-r--r--build2/bin/guess10
-rw-r--r--build2/bin/guess.cxx174
-rw-r--r--build2/bin/module.cxx4
-rw-r--r--build2/buildfile2
-rw-r--r--build2/cxx/compile.cxx427
-rw-r--r--build2/cxx/guess.cxx2
-rw-r--r--build2/cxx/link7
-rw-r--r--build2/cxx/link.cxx396
-rw-r--r--build2/cxx/utility6
-rw-r--r--build2/cxx/utility.cxx60
-rw-r--r--build2/cxx/utility.ixx33
-rw-r--r--build2/cxx/utility.txx54
12 files changed, 840 insertions, 335 deletions
diff --git a/build2/bin/guess b/build2/bin/guess
index 005235d..a80539e 100644
--- a/build2/bin/guess
+++ b/build2/bin/guess
@@ -14,6 +14,14 @@ namespace build2
{
// ar/ranlib information.
//
+ // Currently recognized ar/ranlib and their ids:
+ //
+ // gnu GNU binutils
+ // llvm LLVM
+ // bsd FreeBSD (and maybe other BSDs)
+ // msvc Microsoft's lib.exe
+ // generic Generic/unrecognized
+ //
// The signature is normally the --version/-V line.
//
// The checksum is used to detect ar/ranlib changes. It is calculated in
@@ -22,9 +30,11 @@ namespace build2
//
struct bin_info
{
+ string ar_id;
string ar_signature;
string ar_checksum;
+ string ranlib_id;
string ranlib_signature;
string ranlib_checksum;
};
diff --git a/build2/bin/guess.cxx b/build2/bin/guess.cxx
index 1cdb40d..d036f81 100644
--- a/build2/bin/guess.cxx
+++ b/build2/bin/guess.cxx
@@ -12,48 +12,67 @@ namespace build2
{
namespace bin
{
+ struct guess_result
+ {
+ string id;
+ string signature;
+ string checksum;
+
+ bool
+ empty () const {return id.empty ();}
+ };
+
bin_info
- guess (const path& ar, const path& ranlib)
+ guess (const path& ar, const path& rl)
{
tracer trace ("bin::guess");
- bin_info r;
- string& as (r.ar_signature);
+ guess_result arr, rlr;
// Binutils, LLVM, and FreeBSD ar/ranlib all recognize the --version
- // option, so start with that.
+ // option. While Microsoft's lib.exe doesn't support --version, it only
+ // issues a warning and exits with zero status, printing its usual
+ // banner before that (running lib.exe without any options result in
+ // non-zero exit status -- go figure). So we are going to start with
+ // that.
//
{
- auto f = [] (string& l) -> string
+ auto f = [] (string& l) -> guess_result
{
// Binutils ar --version output has a line that starts with
// "GNU ar ".
//
if (l.compare (0, 7, "GNU ar ") == 0)
- return move (l);
+ return guess_result {"gnu", move (l), ""};
// LLVM ar --version output has a line that starts with
// "LLVM version ".
//
if (l.compare (0, 13, "LLVM version ") == 0)
- return move (l);
+ return guess_result {"llvm", move (l), ""};
// FreeBSD ar --verison output starts with "BSD ar ".
//
if (l.compare (0, 7, "BSD ar ") == 0)
- return move (l);
+ return guess_result {"bsd", move (l), ""};
+
+ // Microsoft lib.exe output starts with "Microsoft (R) ".
+ //
+ if (l.compare (0, 14, "Microsoft (R) ") == 0)
+ return guess_result {"msvc", move (l), ""};
- return string ();
+ return guess_result ();
};
// Suppress all the errors because we may be trying an unsupported
- // option.
+ // option. Note that in case of lib.exe we will hash the warning
+ // (yes, it goes to stdout) but that seems harmless.
//
sha256 cs;
- as = run<string> (ar, "--version", f, false, false, &cs);
+ arr = run<guess_result> (ar, "--version", f, false, false, &cs);
- if (!as.empty ())
- r.ar_checksum = cs.string ();
+ if (!arr.empty ())
+ arr.checksum = cs.string ();
}
// On Mac OS X (and probably also older BSDs) ar/ranlib doesn't have an
@@ -61,94 +80,95 @@ namespace build2
// it dumps usage and exist with an error status. So we will have to use
// that.
//
- if (as.empty ())
+ if (arr.empty ())
{
- auto f = [] (string& l) -> string
+ auto f = [] (string& l) -> guess_result
{
- return l.find (" ar ") == string::npos ? string () : move (l);
+ return l.find (" ar ") != string::npos
+ ? guess_result {"generic", move (l), ""}
+ : guess_result ();
};
// Redirect STDERR to STDOUT and ignore exit status.
//
sha256 cs;
- as = run<string> (ar, f, false, true, &cs);
+ arr = run<guess_result> (ar, f, false, true, &cs);
- if (!as.empty ())
+ if (!arr.empty ())
{
- l4 ([&]{trace << "generic ar signature '" << as << "'";});
-
- r.ar_signature = "Generic ar";
- r.ar_checksum = cs.string ();
+ l4 ([&]{trace << "generic ar '" << arr.signature << "'";});
+ arr.checksum = cs.string ();
}
}
- if (as.empty ())
+ if (arr.empty ())
fail << "unable to guess " << ar << " signature";
// Now repeat pretty much the same steps for ranlib if requested.
//
- if (ranlib.empty ())
- return r;
-
- string& rs (r.ranlib_signature);
-
- // Binutils, LLVM, and FreeBSD.
- //
+ if (!rl.empty ())
{
- auto f = [] (string& l) -> string
- {
- // "GNU ranlib ".
- //
- if (l.compare (0, 11, "GNU ranlib ") == 0)
- return move (l);
-
- // "LLVM version ".
- //
- if (l.compare (0, 13, "LLVM version ") == 0)
- return move (l);
-
- // "ranlib " (note: not "BSD ranlib " for some reason).
- //
- if (l.compare (0, 7, "ranlib ") == 0)
- return move (l);
-
- return string ();
- };
-
- sha256 cs;
- rs = run<string> (ranlib, "--version", f, false, false, &cs);
-
- if (!rs.empty ())
- r.ranlib_checksum = cs.string ();
- }
-
- // Mac OS X (and probably also older BSDs).
- //
- if (rs.empty ())
- {
- auto f = [] (string& l) -> string
+ // Binutils, LLVM, and FreeBSD.
+ //
{
- return l.find ("ranlib") == string::npos ? string () : move (l);
- };
+ auto f = [] (string& l) -> guess_result
+ {
+ // "GNU ranlib ".
+ //
+ if (l.compare (0, 11, "GNU ranlib ") == 0)
+ return guess_result {"gnu", move (l), ""};
+
+ // "LLVM version ".
+ //
+ if (l.compare (0, 13, "LLVM version ") == 0)
+ return guess_result {"llvm", move (l), ""};
+
+ // On FreeBSD we get "ranlib" rather than "BSD ranlib" for some
+ // reason. Which means we can't really call it 'bsd' for sure.
+ //
+ //if (l.compare (0, 7, "ranlib ") == 0)
+ // return guess_result {"bsd", move (l), ""};
+
+ return guess_result ();
+ };
+
+ sha256 cs;
+ rlr = run<guess_result> (rl, "--version", f, false, false, &cs);
+
+ if (!rlr.empty ())
+ rlr.checksum = cs.string ();
+ }
- // Redirect STDERR to STDOUT and ignore exit status.
+ // Mac OS X (and probably also older BSDs).
//
- sha256 cs;
- rs = run<string> (ranlib, f, false, true, &cs);
-
- if (!rs.empty ())
+ if (rlr.empty ())
{
- l4 ([&]{trace << "generic ranlib signature '" << rs << "'";});
-
- r.ranlib_signature = "Generic ranlib";
- r.ranlib_checksum = cs.string ();
+ auto f = [] (string& l) -> guess_result
+ {
+ return l.find ("ranlib") != string::npos
+ ? guess_result {"generic", move (l), ""}
+ : guess_result ();
+ };
+
+ // Redirect STDERR to STDOUT and ignore exit status.
+ //
+ sha256 cs;
+ rlr = run<guess_result> (rl, f, false, true, &cs);
+
+ if (!rlr.empty ())
+ {
+ l4 ([&]{trace << "generic ranlib '" << rlr.signature << "'";});
+ rlr.checksum = cs.string ();
+ }
}
- }
- if (rs.empty ())
- fail << "unable to guess " << ranlib << " signature";
+ if (rlr.empty ())
+ fail << "unable to guess " << rl << " signature";
+ }
- return r;
+ return bin_info {
+ move (arr.id), move (arr.signature), move (arr.checksum),
+ move (rlr.id), move (rlr.signature), move (rlr.checksum)};
}
}
}
diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx
index 866bab2..edb3c53 100644
--- a/build2/bin/module.cxx
+++ b/build2/bin/module.cxx
@@ -185,22 +185,26 @@ namespace build2
//@@ Print project out root or name? See cxx.
text << ar << ":\n"
+ << " id " << bi.ar_id << "\n"
<< " signature " << bi.ar_signature << "\n"
<< " checksum " << bi.ar_checksum;
if (!ranlib.empty ())
{
text << ranlib << ":\n"
+ << " id " << bi.ranlib_id << "\n"
<< " signature " << bi.ranlib_signature << "\n"
<< " checksum " << bi.ranlib_checksum;
}
}
+ r.assign<string> ("bin.ar.id") = move (bi.ar_id);
r.assign<string> ("bin.ar.signature") = move (bi.ar_signature);
r.assign<string> ("bin.ar.checksum") = move (bi.ar_checksum);
if (!ranlib.empty ())
{
+ r.assign<string> ("bin.ranlib.id") = move (bi.ranlib_id);
r.assign<string> ("bin.ranlib.signature") =
move (bi.ranlib_signature);
r.assign<string> ("bin.ranlib.checksum") = move (bi.ranlib_checksum);
diff --git a/build2/buildfile b/build2/buildfile
index da545d1..e1b0b39 100644
--- a/build2/buildfile
+++ b/build2/buildfile
@@ -49,7 +49,7 @@ exe{b}: \
cxx/{hxx cxx}{ link } \
cxx/{hxx cxx}{ module } \
cxx/{hxx cxx}{ target } \
- cxx/{hxx txx cxx}{ utility } \
+ cxx/{hxx ixx cxx}{ utility } \
dist/{hxx cxx}{ module } \
dist/{hxx cxx}{ operation } \
dist/{hxx cxx}{ rule } \
diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx
index a0b4f3a..bfeaeb9 100644
--- a/build2/cxx/compile.cxx
+++ b/build2/cxx/compile.cxx
@@ -68,10 +68,17 @@ namespace build2
path_target& t (static_cast<path_target&> (xt));
+ scope& rs (t.root_scope ());
+ const string& cid (cast<string> (rs["cxx.id"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
+
// Derive file name from target name.
//
if (t.path ().empty ())
- t.derive_path ("o", nullptr, (t.is_a<objso> () ? "-so" : nullptr));
+ {
+ const char* ext (cid == "msvc" ? "obj" : "o");
+ t.derive_path (ext, nullptr, (t.is_a<objso> () ? "-so" : nullptr));
+ }
// Inject dependency on the output directory.
//
@@ -83,9 +90,7 @@ namespace build2
// When cleaning, ignore prerequisites that are not in the same
// or a subdirectory of our project root.
//
- scope& rs (t.root_scope ());
-
- link::search_paths_cache lib_paths; // Extract lazily.
+ optional<dir_paths> lib_paths; // Extract lazily.
for (prerequisite_member p: group_prerequisite_members (a, t))
{
@@ -130,8 +135,6 @@ namespace build2
//
if (a == perform_update_id)
{
- const string& sys (cast<string> (rs["cxx.target.system"]));
-
// The cached prerequisite target should be the same as what is in
// t.prerequisite_targets since we used standard search() and match()
// above.
@@ -187,11 +190,13 @@ namespace build2
hash_options (cs, t, "cxx.poptions");
hash_options (cs, t, "cxx.coptions");
- hash_std (cs, t);
+ hash_std (cs, rs, cid, t);
if (t.is_a<objso> ())
{
- if (sys != "darwin")
+ // On Darwin -fPIC is the default.
+ //
+ if (tclass == "linux" || tclass == "freebsd")
cs.append ("-fPIC");
}
@@ -289,6 +294,8 @@ namespace build2
{
// -I can either be in the "-Ifoo" or "-I foo" form.
//
+ // @@ VC: should we also handle /I?
+ //
dir_path d;
if (*i == "-I")
{
@@ -398,7 +405,7 @@ namespace build2
// following prerequisite or l.size() if there are none left.
//
static string
- next (const string& l, size_t& p)
+ next_make (const string& l, size_t& p)
{
size_t n (l.size ());
@@ -435,6 +442,122 @@ namespace build2
return r;
}
+ // Extract the include path from the VC++ /showIncludes output line.
+ // Return empty string if the line is not an include note or include
+ // error. Set the good_error flag if it is an include error (which means
+ // the process will terminate with the error status that needs to be
+ // ignored).
+ //
+ static string
+ next_show (const string& l, bool& good_error)
+ {
+ // The include error should be the last line that we handle.
+ //
+ assert (!good_error);
+
+ // VC++ /showIncludes output. The first line is the file being
+ // compiled. Then we have the list of headers, one per line, in this
+ // form (text can presumably be translated):
+ //
+ // Note: including file: C:\Program Files (x86)\[...]\iostream
+ //
+ // Finally, if we hit a non-existent header, then we end with an error
+ // line in this form:
+ //
+ // x.cpp(3): fatal error C1083: Cannot open include file: 'd/h.hpp':
+ // No such file or directory
+ //
+
+ // Distinguishing between the include note and the include error is
+ // easy: we can just check for C1083. Distinguising between the note and
+ // other errors/warnings is harder: an error could very well end with
+ // what looks like a path so we cannot look for the note but rather have
+ // to look for an error. Here we assume that a line containing ' CNNNN:'
+ // is an error. Should be robust enough in the face of language
+ // translation, etc.
+ //
+ size_t p (l.find (':'));
+ size_t n (l.size ());
+
+ for (; p != string::npos; p = ++p != n ? l.find (':', p) : string::npos)
+ {
+ auto isnum = [](char c) {return c >= '0' && c <= '9';};
+
+ if (p > 5 &&
+ l[p - 6] == ' ' &&
+ l[p - 5] == 'C' &&
+ isnum (l[p - 4]) &&
+ isnum (l[p - 3]) &&
+ isnum (l[p - 2]) &&
+ isnum (l[p - 1]))
+ {
+ p -= 4; // Start of the error code.
+ break;
+ }
+ }
+
+ if (p == string::npos)
+ {
+ // Include note. We assume the path is always at the end but
+ // need to handle both absolute Windows and POSIX ones.
+ //
+ size_t p (l.rfind (':'));
+
+ if (p != string::npos)
+ {
+ // See if this one is part of the Windows drive letter.
+ //
+ auto isalpha = [](char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');};
+
+ if (p > 1 && p + 1 < n && // 2 chars before, 1 after.
+ l[p - 2] == ' ' &&
+ isalpha (l[p - 1]) &&
+ path::traits::is_separator (l[p + 1]))
+ p = l.rfind (':', p - 2);
+ }
+
+ if (p != string::npos)
+ {
+ // VC uses indentation to indicate the include nesting so there
+ // could be any number of spaces after ':'. Skip them.
+ //
+ p = l.find_first_not_of (' ', p + 1);
+ }
+
+ if (p == string::npos)
+ fail << "unable to parse /showIncludes include note line";
+
+ return string (l, p);
+ }
+ else if (l.compare (p, 4, "1083") == 0)
+ {
+ // Include error. The path is conveniently quoted with ''.
+ //
+ size_t p2 (l.rfind ('\''));
+
+ if (p2 != string::npos && p2 != 0)
+ {
+ size_t p1 (l.rfind ('\'', p2 - 1));
+
+ if (p1 != string::npos)
+ {
+ good_error = true;
+ return string (l, p1 + 1 , p2 - p1 - 1);
+ }
+ }
+
+ error << "unable to parse /showIncludes include error line";
+ throw failed ();
+ }
+ else
+ {
+ // Some other error.
+ //
+ return string ();
+ }
+ }
+
static void
inject_prerequisites (action a, target& t, cxx& s, scope& ds, depdb& dd)
{
@@ -453,16 +576,17 @@ namespace build2
}));
scope& rs (t.root_scope ());
+ const string& cid (cast<string> (rs["cxx.id"]));
// Initialize lazily, only if required.
//
cstrings args;
string cxx_std; // Storage.
- auto init_args = [&t, &s, &rs, &args, &cxx_std] ()
+ auto init_args = [&t, &s, &rs, &cid, &args, &cxx_std] ()
{
const path& cxx (cast<path> (rs["config.cxx"]));
- const string& sys (cast<string> (rs["cxx.target.system"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
args.push_back (cxx.string ().c_str ());
@@ -482,19 +606,30 @@ namespace build2
// Some C++ options (e.g., -std, -m) affect the preprocessor.
//
append_options (args, t, "cxx.coptions");
-
- append_std (args, t, cxx_std);
+ append_std (args, rs, cid, t, cxx_std);
if (t.is_a<objso> ())
{
- if (sys != "darwin") // fPIC by default.
+ // On Darwin -fPIC is the default.
+ //
+ if (tclass == "linux" || tclass == "freebsd")
args.push_back ("-fPIC");
}
- args.push_back ("-M"); // Note: -MM -MG skips missing <>-included.
- args.push_back ("-MG"); // Treat missing headers as generated.
- args.push_back ("-MQ"); // Quoted target name.
- args.push_back ("*"); // Old versions can't do empty target name.
+ if (cid == "msvc")
+ {
+ args.push_back ("/nologo");
+ args.push_back ("/EP"); // Preprocess to stdout.
+ args.push_back ("/TP"); // Preprocess as C++.
+ args.push_back ("/showIncludes"); // Goes to sterr becasue of /EP.
+ }
+ else
+ {
+ args.push_back ("-M"); // Note: -MM -MG skips missing <>-included.
+ args.push_back ("-MG"); // Treat missing headers as generated.
+ args.push_back ("-MQ"); // Quoted target name.
+ args.push_back ("*"); // Old versions can't do empty target name.
+ }
// We are using absolute source file path in order to get absolute
// paths in the result. Any relative paths in the result are non-
@@ -513,8 +648,8 @@ namespace build2
prefix_map pm;
// If any prerequisites that we have extracted changed, then we have to
- // redo the whole thing. The reason for this is auto- generated headers:
- // the updated header may now include a yet- non-existent header. Unless
+ // redo the whole thing. The reason for this is auto-generated headers:
+ // the updated header may now include a yet-non-existent header. Unless
// we discover this and generate it (which, BTW, will trigger another
// restart since that header, in turn, can also include auto-generated
// headers), we will end up with an error during compilation proper.
@@ -811,69 +946,154 @@ namespace build2
if (verb >= 3)
print_process (args);
- process pr (args.data (), 0, -1); // Open pipe to stdout.
- ifdstream is (pr.in_ofd);
+ // For VC with /EP we need a pipe to stderr and stdout should go
+ // to /dev/null.
+ //
+ process pr (args.data (),
+ 0,
+ cid == "msvc" ? -2 : -1,
+ cid == "msvc" ? -1 : 2);
+
+ ifdstream is (cid == "msvc" ? pr.in_efd : pr.in_ofd,
+ fdtranslate::text);
+
+ // In some cases we may need to ignore the error return
+ // status. The good_error flag keeps track of that. Similarly
+ // we sometimes expect the error return status based on the
+ // output we see. The bad_error flag is for that.
+ //
+ bool good_error (false), bad_error (false);
size_t skip (skip_count);
- for (bool first (true), second (true); !(restart || is.eof ()); )
+ for (bool first (true), second (false); !(restart || is.eof ()); )
{
string l;
getline (is, l);
- if (is.fail () && !is.eof ())
- fail << "error reading C++ compiler -M output";
-
- size_t pos (0);
-
- if (first)
+ if (is.fail ())
{
- // Empty output should mean the wait() call below will return
- // false.
- //
- if (l.empty ())
+ if (is.eof ()) // Trailing newline.
break;
- assert (l[0] == '*' && l[1] == ':' && l[2] == ' ');
+ fail << "unable to read C++ compiler header dependency output";
+ }
- first = false;
+ l6 ([&]{trace << "header dependency line '" << l << "'";});
- // While normally we would have the source file on the first
- // line, if too long, it will be moved to the next line and
- // all we will have on this line is "*: \".
- //
- if (l.size () == 4 && l[3] == '\\')
+ // Parse different dependency output formats.
+ //
+ if (cid == "msvc")
+ {
+ if (first)
+ {
+ // The first line should be the file we are compiling. If it
+ // is not, then something went wrong even before we could
+ // compile anything (e.g., file does not exist). In this
+ // case the first line (and everything after it) is
+ // presumably diagnostics.
+ //
+ if (l != s.path ().leaf ().string ())
+ {
+ text << l;
+ bad_error = true;
+ break;
+ }
+
+ first = false;
continue;
- else
- pos = 3; // Skip "*: ".
-
- // Fall through to the 'second' block.
- }
+ }
- if (second)
- {
- second = false;
- next (l, pos); // Skip the source file.
- }
+ string f (next_show (l, good_error));
- while (pos != l.size ())
- {
- string f (next (l, pos));
+ if (f.empty ()) // Some other diagnostics.
+ {
+ text << l;
+ bad_error = true;
+ break;
+ }
// Skip until where we left off.
//
if (skip != 0)
{
+ // We can't be skipping over a non-existent header.
+ //
+ assert (!good_error);
skip--;
- continue;
}
+ else
+ {
+ restart = add (path (move (f)), false);
+ skip_count++;
- restart = add (path (move (f)), false);
- skip_count++;
+ // If the header does not exist, we better restart.
+ //
+ assert (!good_error || restart);
- if (restart)
+ if (restart)
+ l6 ([&]{trace << "restarting";});
+ }
+ }
+ else
+ {
+ // Make dependency declaration.
+ //
+ size_t pos (0);
+
+ if (first)
{
- l6 ([&]{trace << "restarting";});
- break;
+ // Empty output should mean the wait() call below will
+ // return false.
+ //
+ if (l.empty ())
+ {
+ bad_error = true;
+ break;
+ }
+
+ assert (l[0] == '*' && l[1] == ':' && l[2] == ' ');
+
+ first = false;
+ second = true;
+
+ // While normally we would have the source file on the first
+ // line, if too long, it will be moved to the next line and
+ // all we will have on this line is "*: \".
+ //
+ if (l.size () == 4 && l[3] == '\\')
+ continue;
+ else
+ pos = 3; // Skip "*: ".
+
+ // Fall through to the 'second' block.
+ }
+
+ if (second)
+ {
+ second = false;
+ next_make (l, pos); // Skip the source file.
+ }
+
+ while (pos != l.size ())
+ {
+ string f (next_make (l, pos));
+
+ // Skip until where we left off.
+ //
+ if (skip != 0)
+ {
+ skip--;
+ continue;
+ }
+
+ restart = add (path (move (f)), false);
+ skip_count++;
+
+ if (restart)
+ {
+ l6 ([&]{trace << "restarting";});
+ break;
+ }
}
}
}
@@ -885,24 +1105,29 @@ namespace build2
// complains, loudly (broken pipe). So now we are going to skip
// until the end.
//
+ // Also, in case of VC++, we are parsing stderr and if things go
+ // south, we need to copy the diagnostics for the user to see.
+ //
if (!is.eof ())
- is.ignore (numeric_limits<streamsize>::max ());
+ {
+ if (cid == "msvc" && bad_error)
+ *diag_stream << is.rdbuf ();
+ else
+ is.ignore (numeric_limits<streamsize>::max ());
+ }
+
is.close ();
// We assume the child process issued some diagnostics.
//
if (!pr.wait ())
{
- // In case of a restarts, we closed our end of the pipe early
- // which might have caused the other end to fail. So far we
- // experienced this on Fedora 23 with GCC 5.3.1 and there were
- // no diagnostics issued, just the non-zero exit status. If we
- // do get diagnostics, then we will have to read and discard the
- // output until eof.
- //
- if (!restart)
+ if (!good_error) // Ignore expected errors (restart).
throw failed ();
}
+ else if (bad_error)
+ fail << "expected error exist status from C++ compiler";
+
}
catch (const process_error& e)
{
@@ -938,7 +1163,8 @@ namespace build2
scope& rs (t.root_scope ());
const path& cxx (cast<path> (rs["config.cxx"]));
- const string& sys (cast<string> (rs["cxx.target.system"]));
+ const string& cid (cast<string> (rs["cxx.id"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
cstrings args {cxx.string ().c_str ()};
@@ -956,20 +1182,53 @@ namespace build2
append_options (args, t, "cxx.poptions");
append_options (args, t, "cxx.coptions");
- string std; // Storage.
- append_std (args, t, std);
+ string std, out; // Storage.
- if (t.is_a<objso> ())
+ append_std (args, rs, cid, t, std);
+
+ if (cid == "msvc")
{
- if (sys != "darwin") // fPIC by default.
- args.push_back ("-fPIC");
+ uint64_t cver (cast<uint64_t> (rs["cxx.version.major"]));
+
+ if (verb < 3)
+ args.push_back ("/nologo");
+
+ //@@ VC: What is the default value for /MP, should we override?
+
+ // The /Fo: option (object file name) only became available in
+ // VS2013/12.0.
+ //
+ if (cver >= 18)
+ {
+ args.push_back ("/Fo:");
+ args.push_back (relo.string ().c_str ());
+ }
+ else
+ {
+ out = "/Fo" + relo.string ();
+ args.push_back (out.c_str ());
+ }
+
+ args.push_back ("/c"); // Compile only.
+ args.push_back ("/TP"); // Compile as C++.
+ args.push_back (rels.string ().c_str ());
}
+ else
+ {
+ if (t.is_a<objso> ())
+ {
+ // On Darwin -fPIC is the default.
+ //
+ if (tclass == "linux" || tclass == "freebsd")
+ args.push_back ("-fPIC");
+ }
- args.push_back ("-o");
- args.push_back (relo.string ().c_str ());
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
- args.push_back ("-c");
- args.push_back (rels.string ().c_str ());
+ args.push_back ("-c");
+ args.push_back (rels.string ().c_str ());
+ }
args.push_back (nullptr);
@@ -980,7 +1239,17 @@ namespace build2
try
{
- process pr (args.data ());
+ // @@ 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).
+ //
+
+ // VC++ cl.exe sends diagnostics to stdout. To fix this (and any other
+ // insane compilers that may want to do something like this) we are
+ // going to always redirect stdout to stderr. For sane compilers this
+ // should be harmless.
+ //
+ process pr (args.data (), 0, 2);
if (!pr.wait ())
throw failed ();
diff --git a/build2/cxx/guess.cxx b/build2/cxx/guess.cxx
index b55fb27..11a832c 100644
--- a/build2/cxx/guess.cxx
+++ b/build2/cxx/guess.cxx
@@ -865,7 +865,7 @@ namespace build2
// 19.00 140/14.0 VS2015
// 18.00 120/12.0 VS2013
// 17.00 110/11.0 VS2012
- // 16.00 110/10.0 VS2010
+ // 16.00 100/10.0 VS2010
// 15.00 90/9.0 VS2008
// 14.00 80/8.0 VS2005
// 13.10 71/7.1 VS2003
diff --git a/build2/cxx/link b/build2/cxx/link
index cfa6343..d0584de 100644
--- a/build2/cxx/link
+++ b/build2/cxx/link
@@ -53,13 +53,10 @@ namespace build2
private:
friend class compile;
- using search_paths = vector<dir_path>;
- using search_paths_cache = optional<search_paths>;
-
static target*
- search_library (search_paths_cache&, prerequisite&);
+ search_library (optional<dir_paths>&, prerequisite&);
- static search_paths
+ static dir_paths
extract_library_paths (scope&);
};
}
diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx
index 539a606..3d0255e 100644
--- a/build2/cxx/link.cxx
+++ b/build2/cxx/link.cxx
@@ -82,50 +82,20 @@ namespace build2
return *r;
}
- link::search_paths link::
- extract_library_paths (scope& bs)
+ // Extract system library search paths from GCC or compatible (Clang,
+ // Intel C++) using the -print-search-dirs option.
+ //
+ static void
+ gcc_library_search_paths (scope& bs, const string& cid, dir_paths& r)
{
- search_paths r;
scope& rs (*bs.root_scope ());
- // Extract user-supplied search paths (i.e., -L).
- //
- if (auto l = bs["cxx.loptions"])
- {
- const auto& v (cast<strings> (l));
-
- for (auto i (v.begin ()), e (v.end ()); i != e; ++i)
- {
- // -L can either be in the "-Lfoo" or "-L foo" form.
- //
- dir_path d;
- if (*i == "-L")
- {
- if (++i == e)
- break; // Let the compiler complain.
-
- d = dir_path (*i);
- }
- else if (i->compare (0, 2, "-L") == 0)
- d = dir_path (*i, 2, string::npos);
- else
- continue;
-
- // Ignore relative paths. Or maybe we should warn?
- //
- if (!d.relative ())
- r.push_back (move (d));
- }
- }
-
- // Extract system search paths.
- //
cstrings args;
string std_storage;
args.push_back (cast<path> (rs["config.cxx"]).string ().c_str ());
append_options (args, bs, "cxx.coptions");
- append_std (args, bs, std_storage);
+ append_std (args, rs, cid, bs, std_storage);
append_options (args, bs, "cxx.loptions");
args.push_back ("-print-search-dirs");
args.push_back (nullptr);
@@ -172,15 +142,13 @@ namespace build2
if (l.empty ())
fail << "unable to extract C++ compiler system library paths";
- // Now the fun part: figuring out which delimiter is used.
- // Normally it is ':' but on Windows it is ';' (or can be;
- // who knows for sure). Also note that these paths are
- // absolute (or should be). So here is what we are going
- // to do: first look for ';'. If found, then that's the
- // delimiter. If not found, then there are two cases:
- // it is either a single Windows path or the delimiter
- // is ':'. To distinguish these two cases we check if
- // the path starts with a Windows drive.
+ // Now the fun part: figuring out which delimiter is used. Normally it
+ // is ':' but on Windows it is ';' (or can be; who knows for sure). Also
+ // note that these paths are absolute (or should be). So here is what we
+ // are going to do: first look for ';'. If found, then that's the
+ // delimiter. If not found, then there are two cases: it is either a
+ // single Windows path or the delimiter is ':'. To distinguish these two
+ // cases we check if the path starts with a Windows drive.
//
char d (';');
string::size_type e (l.find (d));
@@ -192,8 +160,8 @@ namespace build2
e = l.find (d);
}
- // Now chop it up. We already have the position of the
- // first delimiter (if any).
+ // Now chop it up. We already have the position of the first delimiter
+ // (if any).
//
for (string::size_type b (0);; e = l.find (d, (b = e + 1)))
{
@@ -203,12 +171,80 @@ namespace build2
if (e == string::npos)
break;
}
+ }
+
+ // 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.
+ //
+ 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..?
+ }
+
+ dir_paths link::
+ extract_library_paths (scope& bs)
+ {
+ dir_paths r;
+ scope& rs (*bs.root_scope ());
+ const string& cid (cast<string> (rs["cxx.id"]));
+
+ // Extract user-supplied search paths (i.e., -L, /LIBPATH).
+ //
+ if (auto l = bs["cxx.loptions"])
+ {
+ const auto& v (cast<strings> (l));
+
+ for (auto i (v.begin ()), e (v.end ()); i != e; ++i)
+ {
+ dir_path d;
+
+ if (cid == "msvc")
+ {
+ // /LIBPATH:<dir>
+ //
+ if (i->compare (0, 9, "/LIBPATH:") == 0 ||
+ i->compare (0, 9, "-LIBPATH:") == 0)
+ d = dir_path (*i, 9, string::npos);
+ else
+ continue;
+ }
+ else
+ {
+ // -L can either be in the "-L<dir>" or "-L <dir>" form.
+ //
+ if (*i == "-L")
+ {
+ if (++i == e)
+ break; // Let the compiler complain.
+
+ d = dir_path (*i);
+ }
+ else if (i->compare (0, 2, "-L") == 0)
+ d = dir_path (*i, 2, string::npos);
+ else
+ continue;
+ }
+
+ // Ignore relative paths. Or maybe we should warn?
+ //
+ if (!d.relative ())
+ r.push_back (move (d));
+ }
+ }
+
+ if (cid == "msvc")
+ msvc_library_search_paths (bs, cid, r);
+ else
+ gcc_library_search_paths (bs, cid, r);
return r;
}
target* link::
- search_library (search_paths_cache& spc, prerequisite& p)
+ search_library (optional<dir_paths>& spc, prerequisite& p)
{
tracer trace ("cxx::link::search_library");
@@ -218,6 +254,7 @@ namespace build2
return p.target;
scope& rs (*p.scope.root_scope ());
+ const string& cid (cast<string> (rs["cxx.id"]));
const string& sys (cast<string> (rs["cxx.target.system"]));
bool l (p.is_a<lib> ());
@@ -234,12 +271,25 @@ namespace build2
if (l || p.is_a<liba> ())
{
// We are trying to find a library in the search paths extracted from
- // the compiler. It would only be natural if we use the library
- // prefix/extension that correspond to this compiler's target.
+ // the compiler. It would only be natural if we used the library
+ // prefix/extension that correspond to this compiler and/or its
+ // target.
//
- an = path ("lib" + p.name);
+ const char* e ("");
+
+ if (cid == "msvc")
+ {
+ an = path (p.name);
+ e = "lib";
+ }
+ else
+ {
+ an = path ("lib" + p.name);
+ e = "a";
+ }
+
ae = ext == nullptr
- ? &extension_pool.find ("a")
+ ? &extension_pool.find (e)
: ext;
if (!ae->empty ())
@@ -256,6 +306,9 @@ namespace build2
if (l || p.is_a<libso> ())
{
+ // @@ VC TODO
+ //
+
sn = path ("lib" + p.name);
if (ext == nullptr)
@@ -430,7 +483,7 @@ namespace build2
//
if (seen_c && !seen_cxx && hint < "cxx")
{
- l4 ([&]{trace << "c prerequisite(s) without c++ or hint";});
+ l4 ([&]{trace << "C prerequisite(s) without C++ or hint";});
return nullptr;
}
@@ -445,7 +498,7 @@ namespace build2
if (t.group != nullptr)
t.group->prerequisite_targets.clear (); // lib{}'s
- search_paths_cache lib_paths; // Extract lazily.
+ optional<dir_paths> lib_paths; // Extract lazily.
for (prerequisite_member p: group_prerequisite_members (a, t))
{
@@ -486,12 +539,16 @@ namespace build2
path_target& t (static_cast<path_target&> (xt));
scope& rs (t.root_scope ());
- const string& sys (cast<string> (rs["cxx.target.system"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
type lt (link_type (t));
- bool so (lt == type::so);
order lo (link_order (t));
+ // Some targets have all object files the same.
+ //
+ bool so (lt == type::so);
+ bool oso (so && tclass != "macosx" && tclass != "windows");
+
// Derive file name from target name.
//
if (t.path ().empty ())
@@ -501,7 +558,7 @@ namespace build2
case type::e:
{
const char* e;
- if (sys == "mingw32")
+ if (tclass == "windows")
e = "exe";
else
e = "";
@@ -513,19 +570,33 @@ namespace build2
case type::so:
{
auto l (t["bin.libprefix"]);
- const char* p (l ? cast<string> (l).c_str () : "lib");
- const char* e;
+ const char* p (l ? cast<string> (l).c_str () : nullptr);
+ const char* e (nullptr);
+
if (lt == type::a)
{
- e = "a";
+ // To be anally precise, let's use the ar id to decide how to
+ // name the library in case, for example, someone wants to
+ // archive VC-compiled object files with MINGW ar.
+ //
+ if (cast<string> (rs["bin.ar.id"]) == "msvc")
+ {
+ e = "lib";
+ }
+ else
+ {
+ e = "a";
+ if (p == nullptr) p = "lib";
+ }
}
else
{
- if (sys == "darwin")
- e = "dylib";
- else
- e = "so";
+ //@@ VC: DLL name.
+
+ if (tclass == "macosx") e = "dylib";
+ else e = "so";
+ if (p == nullptr) p = "lib";
}
t.derive_path (e, p);
@@ -540,7 +611,7 @@ namespace build2
//
inject_parent_fsdir (a, t);
- search_paths_cache lib_paths; // Extract lazily.
+ optional<dir_paths> lib_paths; // Extract lazily.
// Process prerequisites: do rule chaining for C and C++ source
// files as well as search and match.
@@ -550,7 +621,6 @@ namespace build2
//
for (prerequisite_member p: group_prerequisite_members (a, t))
{
- bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
target* pt (nullptr);
if (!p.is_a<c> () && !p.is_a<cxx> ())
@@ -573,11 +643,12 @@ namespace build2
//
if (obj* o = pt->is_a<obj> ())
{
- pt = so ? static_cast<target*> (o->so) : o->a;
+ pt = oso ? static_cast<target*> (o->so) : o->a;
if (pt == nullptr)
- pt = &search (so ? objso::static_type : obja::static_type,
+ pt = &search (oso ? objso::static_type : obja::static_type,
p.key ());
+
}
else if (lib* l = pt->is_a<lib> ())
{
@@ -594,11 +665,16 @@ namespace build2
// altogether. So we are going to use the target's project.
//
+ // @@ Why are we creating the obj{} group if the source came from a
+ // group?
+ //
+ bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
+
const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key.
- const target_type& o_type (
+ const target_type& otype (
group
? obj::static_type
- : (so ? objso::static_type : obja::static_type));
+ : (oso ? objso::static_type : obja::static_type));
// Come up with the obj*{} target. The c(xx){} prerequisite directory
// can be relative (to the scope) or absolute. If it is relative, then
@@ -617,7 +693,7 @@ namespace build2
{
if (!cpd.sub (rs.src_path ()))
fail << "out of project prerequisite " << cp <<
- info << "specify corresponding " << o_type.name << "{} "
+ info << "specify corresponding " << otype.name << "{} "
<< "target explicitly";
d = rs.out_path () / cpd.leaf (rs.src_path ());
@@ -627,7 +703,7 @@ namespace build2
// obj*{} is always in the out tree.
//
target& ot (
- search (o_type, d, dir_path (), *cp.tk.name, nullptr, cp.scope));
+ search (otype, d, dir_path (), *cp.tk.name, nullptr, cp.scope));
// If we are cleaning, check that this target is in the same or
// a subdirectory of our project root.
@@ -648,10 +724,10 @@ namespace build2
if (group)
{
obj& o (static_cast<obj&> (ot));
- pt = so ? static_cast<target*> (o.so) : o.a;
+ pt = oso ? static_cast<target*> (o.so) : o.a;
if (pt == nullptr)
- pt = &search (so ? objso::static_type : obja::static_type,
+ pt = &search (oso ? objso::static_type : obja::static_type,
o.dir, o.out, o.name, o.ext, nullptr);
}
else
@@ -703,7 +779,7 @@ namespace build2
<< "be incompatible with existing target " << *pt <<
info << "existing prerequisite " << p1 << " does not match "
<< cp <<
- info << "specify corresponding " << o_type.name << "{} target "
+ info << "specify corresponding " << otype.name << "{} target "
<< "explicitly";
found = true;
@@ -807,7 +883,13 @@ namespace build2
bool up (execute_prerequisites (a, t, t.mtime ()));
scope& rs (t.root_scope ());
- const string& sys (cast<string> (rs["cxx.target.system"]));
+
+ const string& cid (cast<string> (rs["cxx.id"]));
+ const string& tclass (cast<string> (rs["cxx.target.class"]));
+
+ const string& aid (lt == type::a
+ ? cast<string> (rs["bin.ar.id"])
+ : string ());
// Check/update the dependency database.
//
@@ -864,32 +946,44 @@ namespace build2
if (lt == type::a)
{
- // If the user asked for ranlib, don't try to do its function with -s.
- // Some ar implementations (e.g., the LLVM one) doesn't support
- // leading '-'.
- //
- args.push_back (ranlib ? "rc" : "rcs");
+ if (aid == "msvc")
+ {
+ // Translate the compiler target CPU to the /MACHINE option value.
+ //
+ const string& tcpu (cast<string> (rs["cxx.target.cpu"]));
+
+ const char* m (tcpu == "i386" || tcpu == "i686" ? "/MACHINE:x86" :
+ tcpu == "x86_64" ? "/MACHINE:x64" :
+ tcpu == "arm" ? "/MACHINE:ARM" :
+ tcpu == "arm64" ? "/MACHINE:ARM64" :
+ nullptr);
+
+ if (m == nullptr)
+ fail << "unable to translate CPU " << tcpu << " to /MACHINE";
+
+ args.push_back (m);
+ }
+ else
+ {
+ // If the user asked for ranlib, don't try to do its function with -s.
+ // Some ar implementations (e.g., the LLVM one) doesn't support
+ // leading '-'.
+ //
+ args.push_back (ranlib ? "rc" : "rcs");
+ }
}
else
{
append_options (args, t, "cxx.coptions");
- append_std (args, t, std);
-
- if (so)
- {
- if (sys == "darwin")
- args.push_back ("-dynamiclib");
- else
- args.push_back ("-shared");
- }
+ append_std (args, rs, cid, t, std);
// Set soname.
//
- if (so)
+ if (so && cid != "msvc")
{
const string& leaf (t.path ().leaf ().string ());
- if (sys == "darwin")
+ if (tclass == "macosx")
{
// With Mac OS 10.5 (Leopard) Apple finally caved in and gave us
// a way to emulate vanilla -rpath.
@@ -922,28 +1016,33 @@ namespace build2
// rpath of the imported libraries (i.e., we assume the are also
// installed).
//
- for (target* pt: t.prerequisite_targets)
+ // @@ VC TODO: emulate own rpath somehow and complain on user's.
+ //
+ if (cid != "msvc")
{
- if (libso* ls = pt->is_a<libso> ())
+ for (target* pt: t.prerequisite_targets)
{
- if (a.outer_operation () != install_id)
+ if (libso* ls = pt->is_a<libso> ())
{
- sargs.push_back ("-Wl,-rpath," +
- ls->path ().directory ().string ());
+ if (a.outer_operation () != install_id)
+ {
+ sargs.push_back ("-Wl,-rpath," +
+ ls->path ().directory ().string ());
+ }
+ // Use -rpath-link on targets that support it (Linux, FreeBSD).
+ // Since with this option the paths are not stored in the
+ // library, we have to do this recursively (in fact, we don't
+ // really need it for top-level libraries).
+ //
+ else if (tclass == "linux" || tclass == "freebsd")
+ append_rpath_link (sargs, *ls);
}
- // Use -rpath-link on targets that support it (Linux, FreeBSD).
- // Since with this option the paths are not stored in the library,
- // we have to do this recursively (in fact, we don't really need
- // it for top-level libraries).
- //
- else if (sys != "darwin")
- append_rpath_link (sargs, *ls);
}
- }
- if (auto l = t["bin.rpath"])
- for (const dir_path& p: cast<dir_paths> (l))
- sargs.push_back ("-Wl,-rpath," + p.string ());
+ if (auto l = t["bin.rpath"])
+ for (const dir_path& p: cast<dir_paths> (l))
+ sargs.push_back ("-Wl,-rpath," + p.string ());
+ }
}
// All the options should now be in. Hash them and compare with the db.
@@ -969,7 +1068,7 @@ namespace build2
// Should we capture actual files or their checksum? The only good
// reason for capturing actual files is diagnostics: we will be able
// to pinpoint exactly what is causing the update. On the other hand,
- // the checksum is faster and simpler.
+ // the checksum is faster and simpler. And we like simple.
//
{
sha256 cs;
@@ -1020,6 +1119,7 @@ namespace build2
// Ok, so we are updating. Finish building the command line.
//
+ string out; // Storage.
// Translate paths to relative (to working directory) ones. This results
// in easier to read diagnostics.
@@ -1028,14 +1128,64 @@ namespace build2
if (lt == type::a)
{
+ //@@ VC: what are /LIBPATH, /NODEFAULTLIB for?
+ //
+
args[0] = cast<path> (rs["config.bin.ar"]).string ().c_str ();
- args.push_back (relt.string ().c_str ());
+
+ if (aid == "msvc")
+ {
+ 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 ());
}
else
{
args[0] = cast<path> (rs["config.cxx"]).string ().c_str ();
- args.push_back ("-o");
- args.push_back (relt.string ().c_str ());
+
+ if (cid == "msvc")
+ {
+ uint64_t cver (cast<uint64_t> (rs["cxx.version.major"]));
+
+ if (verb < 3)
+ args.push_back ("/nologo");
+
+ //@@ VC TODO: DLL building (names via /link?)
+
+ // The /Fe: option (executable file name) only became available in
+ // VS2013/12.0.
+ //
+ if (cver >= 18)
+ {
+ args.push_back ("/Fe:");
+ args.push_back (relt.string ().c_str ());
+ }
+ else
+ {
+ out = "/Fe" + relt.string ();
+ args.push_back (out.c_str ());
+ }
+ }
+ else
+ {
+ // Add the option that triggers building a shared library.
+ //
+ if (so)
+ {
+ if (tclass == "macosx")
+ args.push_back ("-dynamiclib");
+ else
+ args.push_back ("-shared");
+ }
+
+ args.push_back ("-o");
+ args.push_back (relt.string ().c_str ());
+ }
}
size_t oend (sargs.size ()); // Note the end of options in sargs.
@@ -1061,13 +1211,19 @@ namespace build2
}
}
- // Copy sargs to args. Why not do it as we go along pusing into sargs?
+ // Copy sargs to args. Why not do it as we go along pushing into sargs?
// Because of potential realocations.
//
for (size_t i (0); i != sargs.size (); ++i)
{
if (lt != type::a && i == oend)
- append_options (args, t, "cxx.loptions");
+ {
+ //@@ VC: TMP, until we use link.exe directly (would need to
+ // prefix them with /link otherwise).
+ //
+ if (cid != "msvc")
+ append_options (args, t, "cxx.loptions");
+ }
args.push_back (sargs[i].c_str ());
}
@@ -1084,7 +1240,17 @@ namespace build2
try
{
- process pr (args.data ());
+ //@@ VC: I think it prints each object file being added.
+ //
+ // Not for lib.exe
+ //
+
+ // VC++ (cl.exe, lib.exe, and link.exe) sends diagnostics to
+ // stdout. To fix this (and any other insane compilers that may want
+ // to do something like this) we are going to always redirect stdout
+ // to stderr. For sane compilers this should be harmless.
+ //
+ process pr (args.data (), 0, 2);
if (!pr.wait ())
throw failed ();
diff --git a/build2/cxx/utility b/build2/cxx/utility
index 307e1b4..e924ee8 100644
--- a/build2/cxx/utility
+++ b/build2/cxx/utility
@@ -18,11 +18,11 @@ namespace build2
//
template <typename T>
void
- append_std (cstrings&, T&, string& storage);
+ append_std (cstrings&, scope& rs, const string& cid, T&, string& storage);
template <typename T>
void
- hash_std (sha256&, T&);
+ hash_std (sha256&, scope& rs, const string& cid, T&);
// Append or hash library options from one of the cxx.export.* variables
// recursively, prerequisite libraries first.
@@ -35,6 +35,6 @@ namespace build2
}
}
-#include <build2/cxx/utility.txx>
+#include <build2/cxx/utility.ixx>
#endif // BUILD2_CXX_UTILITY
diff --git a/build2/cxx/utility.cxx b/build2/cxx/utility.cxx
index 15980d4..0f4eb08 100644
--- a/build2/cxx/utility.cxx
+++ b/build2/cxx/utility.cxx
@@ -12,6 +12,66 @@ namespace build2
{
namespace cxx
{
+ // Return true if there is an option (stored in s).
+ //
+ bool
+ translate_std (scope& rs, const string& cid, const value& val, string& s)
+ {
+ const string& v (cast<string> (val));
+
+ if (cid == "msvc")
+ {
+ // C++ standard-wise, with VC++ you get what you get. The question is
+ // whether we should verify that the requested standard is provided by
+ // this VC++ version. And if so, from which version should we say VC++
+ // supports 11, 14, and 17? We should probably be as loose as possible
+ // here since the author will always be able to tighten (but not
+ // loosen) this in the buildfile (i.e., detect unsupported versions).
+ //
+ // For now we are not going to bother doing this for C++03.
+ //
+ if (v != "98" && v != "03")
+ {
+ uint64_t cver (cast<uint64_t> (rs["cxx.version.major"]));
+
+ // @@ Is mapping for 14 and 17 correct? Maybe Update 2 for 14?
+ //
+ if ((v == "11" && cver <= 16) || // C++11 since VS2010/10.0.
+ (v == "14" && cver <= 19) || // C++14 since VS2015/14.0.
+ (v == "17" && cver <= 20)) // C++17 since VS20??/15.0.
+ {
+ fail << "C++" << v << " is not supported by "
+ << cast<string> (rs["cxx.signature"]) <<
+ info << "required by " << rs.out_path ();
+ }
+ }
+
+ return false;
+ }
+ else
+ {
+ // Translate 11 to 0x, 14 to 1y, and 17 to 1z for compatibility with
+ // older versions of the compilers.
+ //
+ s = "-std=";
+
+ if (v == "98")
+ s += "c++98";
+ else if (v == "03")
+ s += "c++03";
+ else if (v == "11")
+ s += "c++0x";
+ else if (v == "14")
+ s += "c++1y";
+ else if (v == "17")
+ s += "c++1z";
+ else
+ s += v; // In case the user specifies something like 'gnu++17'.
+
+ return true;
+ }
+ }
+
void
append_lib_options (cstrings& args, target& l, const char* var)
{
diff --git a/build2/cxx/utility.ixx b/build2/cxx/utility.ixx
new file mode 100644
index 0000000..c624e87
--- /dev/null
+++ b/build2/cxx/utility.ixx
@@ -0,0 +1,33 @@
+// file : build2/cxx/utility.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ namespace cxx
+ {
+ bool
+ translate_std (scope&, const string&, const value&, string&);
+
+ template <typename T>
+ inline void
+ append_std (cstrings& args, scope& rs, const string& cid, T& t, string& s)
+ {
+ if (auto l = t["cxx.std"])
+ if (translate_std (rs, cid, *l, s))
+ args.push_back (s.c_str ());
+ }
+
+ template <typename T>
+ inline void
+ hash_std (sha256& csum, scope& rs, const string& cid, T& t)
+ {
+ if (auto l = t["cxx.std"])
+ {
+ string s;
+ if (translate_std (rs, cid, *l, s))
+ csum.append (s);
+ }
+ }
+ }
+}
diff --git a/build2/cxx/utility.txx b/build2/cxx/utility.txx
deleted file mode 100644
index 4d38513..0000000
--- a/build2/cxx/utility.txx
+++ /dev/null
@@ -1,54 +0,0 @@
-// file : build2/cxx/utility.txx -*- C++ -*-
-// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-using namespace std;
-
-namespace build2
-{
- namespace cxx
- {
- template <typename T>
- bool
- translate_std (T& t, string& s)
- {
- if (auto l = t["cxx.std"])
- {
- const string& v (cast<string> (l));
-
- // Translate 11 to 0x and 14 to 1y for compatibility with older
- // versions of the compiler.
- //
- s = "-std=c++";
-
- if (v == "11")
- s += "0x";
- else if (v == "14")
- s += "1y";
- else
- s += v;
-
- return true;
- }
-
- return false;
- }
-
- template <typename T>
- inline void
- append_std (cstrings& args, T& t, string& s)
- {
- if (translate_std (t, s))
- args.push_back (s.c_str ());
- }
-
- template <typename T>
- inline void
- hash_std (sha256& csum, T& t)
- {
- string s;
- if (translate_std (t, s))
- csum.append (s);
- }
- }
-}