diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-05-20 23:25:45 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-06-11 19:06:40 +0200 |
commit | 4218bfe7668d55e36814d6bdcec2da40454025c1 (patch) | |
tree | 1abe3f38109ba83755adf7307414dbbd1e1a9eb8 | |
parent | 9f56941794837ec63b8732d8d0cae4659528e714 (diff) |
Initial VC++ support (static libraries only)
-rw-r--r-- | build2/bin/guess | 10 | ||||
-rw-r--r-- | build2/bin/guess.cxx | 174 | ||||
-rw-r--r-- | build2/bin/module.cxx | 4 | ||||
-rw-r--r-- | build2/buildfile | 2 | ||||
-rw-r--r-- | build2/cxx/compile.cxx | 427 | ||||
-rw-r--r-- | build2/cxx/guess.cxx | 2 | ||||
-rw-r--r-- | build2/cxx/link | 7 | ||||
-rw-r--r-- | build2/cxx/link.cxx | 396 | ||||
-rw-r--r-- | build2/cxx/utility | 6 | ||||
-rw-r--r-- | build2/cxx/utility.cxx | 60 | ||||
-rw-r--r-- | build2/cxx/utility.ixx | 33 | ||||
-rw-r--r-- | build2/cxx/utility.txx | 54 |
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); - } - } -} |