diff options
Diffstat (limited to 'libbuild2/cc/guess.cxx')
-rw-r--r-- | libbuild2/cc/guess.cxx | 645 |
1 files changed, 564 insertions, 81 deletions
diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index 7b993f0..52c9541 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -4,8 +4,70 @@ #include <libbuild2/cc/guess.hxx> +// Bootstrap build is always performed in the VC's command prompt and thus +// doesn't require the VC search functionality. +// +#if defined(_WIN32) && !defined(BUILD2_BOOTSTRAP) +# include <libbutl/win32-utility.hxx> + +# include <unknwn.h> // IUnknown +# include <stdlib.h> // _MAX_PATH +# include <oleauto.h> // SysFreeString() +# include <guiddef.h> // CLSID, IID +# include <objbase.h> // CoInitializeEx(), CoCreateInstance(), etc. + +// MinGW may lack some macro definitions used in msvc-setup.h (see below), so +// we provide them if that's the case. +// +# ifndef MAXUINT +# define MAXUINT UINT_MAX +# endif + +// MinGW's sal.h (Microsoft's Source Code Annotation Language) may not contain +// all the in/out annotation macros. +// +# ifndef _In_z_ +# define _In_z_ +# endif + +# ifndef _In_opt_z_ +# define _In_opt_z_ +# endif + +# ifndef _Out_opt_ +# define _Out_opt_ +# endif + +# ifndef _Deref_out_opt_ +# define _Deref_out_opt_ +# endif + +# ifndef _Out_writes_to_ +# define _Out_writes_to_(X, Y) +# endif + +# ifndef _Deref_out_range_ +# define _Deref_out_range_(X, Y) +# endif + +# ifndef _Outptr_result_maybenull_ +# define _Outptr_result_maybenull_ +# endif + +# ifndef _Reserved_ +# define _Reserved_ +# endif + +// API for enumerating Visual Studio setup instances and querying information +// about them (see the LICENSE file for details). +// +# include <libbuild2/cc/msvc-setup.h> + +# include <libbuild2/filesystem.hxx> +#endif + #include <map> -#include <cstring> // strlen(), strchr() +#include <cstring> // strlen(), strchr() #include <libbuild2/diagnostics.hxx> @@ -352,6 +414,291 @@ namespace build2 return pre_guess_result {invalid_compiler_type, nullopt, string::npos}; } + // Return the latest MSVC and Platform SDK installation information if + // both are discovered on the system and nullopt otherwise. In particular, + // don't fail on the underlying COM/OS errors returning nullopt instead. + // This way a broken VC setup will be silently ignored. + // + // Note that Visual Studio versions prior to 15.0 are not supported. + // + struct msvc_info + { + dir_path msvc_dir; // VC directory (...\Tools\MSVC\<ver>\). + string psdk_ver; // Platfor SDK directory (...\Windows Kits\<ver>\). + dir_path psdk_dir; // Platfor SDK version (under Include/, Lib/, etc). + }; + +#if defined(_WIN32) && !defined(BUILD2_BOOTSTRAP) + + // We more or less follow the logic in the Clang 'simplementation (see + // MSVC.cpp for details) but don't use the high level APIs (bstr_t, + // com_ptr_t, etc) and the VC extensions (__uuidof(), class uuid + // __declspecs, etc) that are poorly supported by MinGW GCC and Clang. + // + struct com_deleter + { + void operator() (IUnknown* p) const {if (p != nullptr) p->Release ();} + }; + + struct bstr_deleter + { + void operator() (BSTR p) const {if (p != nullptr) SysFreeString (p);} + }; + + // We don't use the __uuidof keyword (see above) and so define the + // class/interface ids manually. + // + static const CLSID msvc_setup_config_clsid { + 0x177F0C4A, 0x1CD3, 0x4DE7, + {0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D}}; + + static const IID msvc_setup_config_iid { + 0x26AAB78C, 0x4A60, 0x49D6, + {0xAF, 0x3B, 0x3C, 0x35, 0xBC, 0x93, 0x36, 0x5D}}; + + static const IID msvc_setup_helper_iid { + 0x42B21B78, 0x6192, 0x463E, + {0x87, 0xBF, 0xD5, 0x77, 0x83, 0x8F, 0x1D, 0x5C}}; + + static optional<msvc_info> + find_msvc () + { + using namespace butl; + + msvc_info r; + + // Try to obtain the latest MSVC directory and version. + // + { + // Initialize the COM library for use by the current thread. + // + if (CoInitializeEx (nullptr /* pvReserved */, + COINIT_APARTMENTTHREADED) != S_OK) + return nullopt; + + auto uninitializer (make_guard ([] () {CoUninitialize ();})); + + // Obtain the VS information retrieval interface. Failed that, assume + // there is no VS installed. + // + unique_ptr<ISetupConfiguration2, com_deleter> sc; + { + ISetupConfiguration2* p; + if (CoCreateInstance (msvc_setup_config_clsid, + nullptr /* pUnkOuter */, + CLSCTX_ALL, + msvc_setup_config_iid, + reinterpret_cast<LPVOID*> (&p)) != S_OK) + return nullopt; + + sc.reset (p); + } + + // Obtain the VS instance enumerator interface. + // + unique_ptr<IEnumSetupInstances, com_deleter> ei; + { + IEnumSetupInstances* p; + if (sc->EnumAllInstances (&p) != S_OK) + return nullopt; + + ei.reset (p); + } + + // Obtain an interface that helps with the VS version parsing. + // + unique_ptr<ISetupHelper, com_deleter> sh; + { + ISetupHelper* p; + if (sc->QueryInterface (msvc_setup_helper_iid, + reinterpret_cast<LPVOID*> (&p)) != S_OK) + return nullopt; + + sh.reset (p); + } + + // Iterate over the VS instances and pick the latest one. Bail out + // if any COM interface function call fails. + // + unsigned long long vs_ver (0); // VS version numeric representation. + unique_ptr<ISetupInstance, com_deleter> vs; + HRESULT hr; + + for (ISetupInstance* p; + (hr = ei->Next (1, &p, nullptr /* pceltFetched */)) == S_OK; ) + { + unique_ptr<ISetupInstance, com_deleter> i (p); + + // Note: we cannot use bstr_t due to the Clang 9.0 bug #42842. + // + BSTR iv; // For example, 16.3.29324.140. + if (i->GetInstallationVersion (&iv) != S_OK) + return nullopt; + + unique_ptr<wchar_t, bstr_deleter> deleter (iv); + + unsigned long long v; + if (sh->ParseVersion (iv, &v) != S_OK) + return nullopt; + + if (vs == nullptr || v > vs_ver) + { + vs = move (i); + vs_ver = v; + } + } + + // Bail out if no VS instance is found or we didn't manage to iterate + // through all of them successfully. + // + if (vs == nullptr || hr != S_FALSE) + return nullopt; + + // Obtain the VC directory path. + // + { + BSTR p; + if (vs->ResolvePath (L"VC", &p) != S_OK) + return nullopt; + + unique_ptr<wchar_t, bstr_deleter> deleter (p); + + // Convert BSTR to the NULL-terminated character string and then to + // a path. Bail out if anything goes wrong. + // + try + { + int n (WideCharToMultiByte (CP_ACP, + 0 /* dwFlags */, + p, + -1, /*cchWideChar */ + nullptr /* lpMultiByteStr */, + 0 /* cbMultiByte */, + 0 /* lpDefaultChar */, + 0 /* lpUsedDefaultChar */)); + + if (n != 0) // Note: must include the terminating NULL character. + { + vector<char> ps (n); + if (WideCharToMultiByte (CP_ACP, + 0, + p, -1, + ps.data (), n, + 0, 0) != 0) + r.msvc_dir = dir_path (ps.data ()); + } + } + catch (const invalid_path&) {} + + if (r.msvc_dir.relative ()) // Also covers the empty directory case. + return nullopt; + } + + // Read the VC version from the file and bail out on error. + // + string vc_ver; // For example, 14.23.28105. + + path vp ( + r.msvc_dir / + path ("Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); + + try + { + ifdstream is (vp); + vc_ver = trim (is.read_text ()); + } + catch (const io_error&) {} + + // Make sure that the VC version directory exists. + // + if (!vc_ver.empty ()) + try + { + ((r.msvc_dir /= "Tools") /= "MSVC") /= vc_ver; + + if (!dir_exists (r.msvc_dir)) + r.msvc_dir.clear (); + } + catch (const invalid_path&) {} + catch (const system_error&) {} + + if (r.msvc_dir.empty ()) + return nullopt; + } + + // Try to obtain the latest Platform SDK directory and version. + // + { + // Read the Platform SDK directory path from the registry. Failed + // that, assume there is no Platform SDK installed. + // + HKEY h; + if (RegOpenKeyExA ( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", + 0 /* ulOptions */, + KEY_READ, + &h) != ERROR_SUCCESS) + return nullopt; + + DWORD t; + + // Reserve space for the terminating NULL character. + // + DWORD n (_MAX_PATH + 1); + char buf[_MAX_PATH + 1]; + + LSTATUS st (RegQueryValueExA (h, + "KitsRoot10", + nullptr, + &t, + reinterpret_cast<LPBYTE> (buf), + &n)); + + // Unlikely to fail, but we can't do much if that's the case. + // + RegCloseKey (h); + + // Note that the value length includes the terminating NULL character + // and so cannot be zero. + // + if (st != ERROR_SUCCESS || t != REG_SZ || n == 0) + return nullopt; + + try + { + r.psdk_dir = dir_path (buf); + + if (r.psdk_dir.relative ()) // Also covers the empty directory case. + return nullopt; + + // Obtain the latest Platform SDK version as the lexicographically + // greatest sub-directory name in the <psdk-dir>/Include directory. + // + for (const dir_entry& de: + dir_iterator (r.psdk_dir / dir_path ("Include"), + false /* ignore_dangling */)) + { + if (de.type () == entry_type::directory) + { + const string& v (de.path ().string ()); + + if (v.compare (0, 3, "10.") == 0 && v > r.psdk_ver) + r.psdk_ver = v; + } + } + } + catch (const invalid_path&) {return nullopt;} + catch (const system_error&) {return nullopt;} + + if (r.psdk_ver.empty ()) + return nullopt; + } + + return move (r); + } +#endif + // Guess the compiler type and variant by running it. If the pre argument // is not empty, then only "confirm" the pre-guess. Return empty result if // unable to guess. @@ -363,6 +710,15 @@ namespace build2 string checksum; process_path path; + // Optional additional information (for example, msvc_info). + // + static void + null_info_deleter (void* p) { assert (p == nullptr); } + + using info_ptr = unique_ptr<void, void (*) (void*)>; + + info_ptr info = {nullptr, null_info_deleter}; + guess_result () = default; guess_result (compiler_id i, string&& s) : id (move (i)), signature (move (s)) {} @@ -388,9 +744,15 @@ namespace build2 using type = compiler_type; const type invalid = invalid_compiler_type; + const type& pt (pre.type); + const optional<string>& pv (pre.variant); + + using info_ptr = guess_result::info_ptr; guess_result r; process_path xp; + info_ptr search_info (nullptr, guess_result::null_info_deleter); + for (;;) // Breakout loop. { auto df = make_diag_frame ( [&xm](const diag_record& dr) @@ -398,36 +760,93 @@ namespace build2 dr << info << "use config." << xm << " to override"; }); + dir_path fb; // Fallback search directory. + +#ifdef _WIN32 // If we are running in the Visual Studio command prompt, add the // potentially bundled Clang directory as a fallback (for some reason // the Visual Studio prompts don't add it to PATH themselves). // - dir_path fallback; - -#ifdef _WIN32 - if (pre.type == type::clang || - (pre.type == type::msvc && pre.variant && *pre.variant == "clang")) + if (xc.simple () && + (pt == type::clang || + (pt == type::msvc && pv && *pv == "clang"))) { if (optional<string> v = getenv ("VCINSTALLDIR")) { try { - fallback = ((dir_path (move (*v)) /= "Tools") /= "Llvm") /= "bin"; + fb = ((dir_path (move (*v)) /= "Tools") /= "Llvm") /= "bin"; } catch (const invalid_path&) { // Ignore it. } + + goto search; + } + } + + // If we pre-guessed MSVC or Clang (including clang-cl) try the search + // and if not found, try to locate the MSVC installation and fallback + // on that. + // + if (xc.simple () && + (pt == type::clang || + (pt == type::msvc && (!pv || *pv == "clang")))) + { + if (!(xp = try_run_search (xc, false, dir_path (), true)).empty ()) + break; + + if (optional<msvc_info> mi = find_msvc ()) + { + try + { + if (pt == type::msvc && !pv) + { + // With MSVC you get a compiler binary per target (i.e., there + // is nothing like -m32/-m64 or /MACHINE). Targeting 64-bit + // seems like as good of a default as any. + // + fb = ((dir_path (mi->msvc_dir) /= "bin") /= "Hostx64") /= "x64"; + + search_info = info_ptr (new msvc_info (move (*mi)), + [] (void* p) + { + delete static_cast<msvc_info*> (p); + }); + } + else + { + // Get to ...\VC\Tools\ from ...\VC\Tools\MSVC\<ver>\. + // + fb = (dir_path (mi->msvc_dir) /= "..") /= ".."; + fb.normalize (); + (fb /= "Llvm") /= "bin"; + + // Note that in this case we drop msvc_info and extract it + // directly from Clang later. + } + } + catch (const invalid_path&) + { + fb.clear (); // Ignore it. + } + + goto search; } } + + search: #endif + // Only search in PATH (specifically, omitting the current // executable's directory on Windows). // xp = run_search (xc, - false /* init (note: result is cached) */, - fallback, - true /* path_only */); + false /* init (note: result is cached) */, + fb, + true /* path_only */); + break; } // Start with -v. This will cover gcc and clang (including clang-cl). @@ -445,13 +864,12 @@ namespace build2 // In fact, if someone renames icpc to g++, there will be no way for // us to detect this. Oh, well, their problem. // - if (r.empty () && ( pre.type == invalid || - pre.type == type::gcc || - pre.type == type::clang || - (pre.type == type::msvc && - pre.variant && *pre.variant == "clang"))) + if (r.empty () && (pt == invalid || + pt == type::gcc || + pt == type::clang || + (pt == type::msvc && pv && *pv == "clang"))) { - auto f = [&xi, &pre] (string& l, bool last) -> guess_result + auto f = [&xi, &pt] (string& l, bool last) -> guess_result { if (xi) { @@ -525,7 +943,7 @@ namespace build2 // if (l.find ("clang ") != string::npos) { - return guess_result (pre.type == type::msvc + return guess_result (pt == type::msvc ? compiler_id {type::msvc, "clang"} : compiler_id {type::clang, ""}, move (l)); @@ -567,7 +985,7 @@ namespace build2 // if (r.id.type == type::clang && r.id.variant == "apple" && - pre.type == type::gcc) + pt == type::gcc) { pre.type = type::clang; pre.variant = "apple"; @@ -578,10 +996,10 @@ namespace build2 // Next try --version to detect icc. As well as obtain signature for // GCC/Clang-like compilers in case -v above didn't work. // - if (r.empty () && (pre.type == invalid || - pre.type == type::icc || - pre.type == type::gcc || - pre.type == type::clang)) + if (r.empty () && (pt == invalid || + pt == type::icc || + pt == type::gcc || + pt == type::clang)) { auto f = [&xi] (string& l, bool) -> guess_result { @@ -617,7 +1035,8 @@ namespace build2 // Finally try to run it without any options to detect msvc. // - if (r.empty () && (pre.type == invalid || pre.type == type::msvc)) + if (r.empty () && (pt == invalid || + pt == type::msvc)) { auto f = [&xi] (string& l, bool) -> guess_result { @@ -669,9 +1088,8 @@ namespace build2 if (!r.empty ()) { - if (pre.type != invalid && - (pre.type != r.id.type || - (pre.variant && *pre.variant != r.id.variant))) + if (pt != invalid && + (pt != r.id.type || (pv && *pv != r.id.variant))) { l4 ([&]{trace << "compiler type guess mismatch" << ", pre-guessed " << pre @@ -685,6 +1103,9 @@ namespace build2 << r.signature << "'";}); r.path = move (xp); + + if (search_info != nullptr && r.info == nullptr) + r.info = move (search_info); } } else @@ -823,6 +1244,82 @@ namespace build2 << "' to runtime version" << endf; } + // Return the MSVC system header search paths (i.e., what the Visual + // Studio command prompt puts into INCLUDE). + // + // Note that currently we don't add any ATL/MFC or WinRT paths (but could + // do that probably first checking if they exist/empty). + // + static dir_paths + msvc_include (const msvc_info& mi) + { + dir_paths r; + + r.push_back (dir_path (mi.msvc_dir) /= "include"); + + // This path structure only appeared in Platform SDK 10 (if anyone wants + // to use anything older, they will just have to use the MSVC command + // prompt). + // + if (!mi.psdk_ver.empty ()) + { + dir_path d ((dir_path (mi.psdk_dir) /= "Include") /= mi.psdk_ver); + + r.push_back (dir_path (d) /= "ucrt" ); + r.push_back (dir_path (d) /= "shared"); + r.push_back (dir_path (d) /= "um" ); + } + + return r; + } + + // Return the MSVC system library search paths (i.e., what the Visual + // Studio command prompt puts into LIB). + // + static dir_paths + msvc_lib (const msvc_info& mi, const char* cpu) + { + dir_paths r; + + r.push_back ((dir_path (mi.msvc_dir) /= "lib") /= cpu); + + // This path structure only appeared in Platform SDK 10 (if anyone wants + // to use anything older, they will just have to use the MSVC command + // prompt). + // + if (!mi.psdk_ver.empty ()) + { + dir_path d ((dir_path (mi.psdk_dir) /= "Lib") /= mi.psdk_ver); + + r.push_back ((dir_path (d) /= "ucrt") /= cpu); + r.push_back ((dir_path (d) /= "um" ) /= cpu); + } + + return r; + } + + // Return the MSVC binutils search paths (i.e., what the Visual Studio + // command prompt puts into PATH). + // + static string + msvc_bin (const msvc_info& mi, const char* cpu) + { + string r; + + // Seeing that we only do 64-bit on Windows, let's always use 64-bit + // MSVC tools (link.exe, etc). In case of the Platform SDK, it's unclear + // what the CPU signifies (host, target, both). + // + r = (((dir_path (mi.msvc_dir) /= "bin") /= "Hostx64") /= cpu). + representation (); + + r += path::traits_type::path_separator; + + r += (((dir_path (mi.psdk_dir) /= "bin") /= mi.psdk_ver) /= cpu). + representation (); + + return r; + } static compiler_info guess_msvc (const char* xm, @@ -990,6 +1487,21 @@ namespace build2 else ot = t = *xt; + // If we have the MSVC installation information, then this means we are + // running out of the Visual Studio command prompt and will have to + // supply PATH/INCLUDE/LIB equivalents ourselves. + // + optional<dir_paths> lib_dirs; + optional<dir_paths> inc_dirs; + string bpat; + + if (const msvc_info* mi = static_cast<msvc_info*> (gr.info.get ())) + { + lib_dirs = msvc_lib (*mi, "x64"); + lib_dirs = msvc_include (*mi); + bpat = msvc_bin (*mi, "x64"); + } + // Derive the toolchain pattern. // // If the compiler name is/starts with 'cl' (e.g., cl.exe, cl-14), @@ -997,7 +1509,9 @@ namespace build2 // etc. // string cpat (pattern (xc, "cl", nullptr, ".-")); - string bpat (cpat); // Binutils pattern is the same as toolchain. + + if (bpat.empty ()) + bpat = cpat; // Binutils pattern is the same as toolchain. // Runtime and standard library. // @@ -1025,8 +1539,8 @@ namespace build2 move (rt), move (csl), move (xsl), - nullopt, - nullopt}; + move (lib_dirs), + move (inc_dirs)}; } static compiler_info @@ -1223,14 +1737,11 @@ namespace build2 nullopt}; } - struct clang_msvc_info + struct clang_msvc_info: msvc_info { string triple; // cc1 -triple value - string msvc_ver; // system version from triple + string msvc_ver; // Compiler version from triple. string msvc_comp_ver; // cc1 -fms-compatibility-version value - dir_path msvc_dir; - string psdk_ver; - dir_path psdk_dir; }; static clang_msvc_info @@ -1645,7 +2156,7 @@ namespace build2 // MSVC's. // optional<dir_paths> lib_dirs; - string bin_pat; + string bpat; if (tt.system == "windows-msvc") { @@ -1686,25 +2197,7 @@ namespace build2 // to extract this from Clang and -print-search-paths would have been // the natural way for Clang to report it. But no luck. // - { - dir_paths ds; - - ds.push_back ((dir_path (mi.msvc_dir) /= "lib") /= cpu); - - // This path structure only appeared in Platform SDK 10 (if anyone - // wants to use anything older, they will just have to use the MSVC - // command prompt). - // - if (!mi.psdk_ver.empty ()) - { - dir_path d ((dir_path (mi.psdk_dir) /= "Lib") /= mi.psdk_ver); - - ds.push_back ((dir_path (d) /= "ucrt") /= cpu); - ds.push_back ((dir_path (d) /= "um" ) /= cpu); - } - - lib_dirs = move (ds); - } + lib_dirs = msvc_lib (mi, cpu); // Binutils search paths. // @@ -1713,17 +2206,7 @@ namespace build2 // lines. However, reliably detecting this and making sure the result // matches Clang's is complex. So let's keep it simple for now. // - // Seeing that we only do 64-bit on Windows, let's always use 64-bit - // MSVC tools (link.exe, etc). In case of the Platform SDK, it's - // unclear what the CPU signifies (host, target, both). - // - bin_pat = (((dir_path (mi.msvc_dir) /= "bin") /= "Hostx64") /= cpu). - representation (); - - bin_pat += path::traits_type::path_separator; - - bin_pat += (((dir_path (mi.psdk_dir) /= "bin") /= mi.psdk_ver) /= cpu). - representation (); + bpat = msvc_bin (mi, cpu); // If this is clang-cl, then use the MSVC compatibility version as its // primary version. @@ -1735,20 +2218,20 @@ namespace build2 } } - // Derive the toolchain pattern. Try clang/clang++, the gcc/g++ alias, - // as well as cc/c++. + // Derive the compiler toolchain pattern. Try clang/clang++, the gcc/g++ + // alias, as well as cc/c++. // - string pat; + string cpat; if (!cl) { - pat = pattern (xc, xl == lang::c ? "clang" : "clang++"); + cpat = pattern (xc, xl == lang::c ? "clang" : "clang++"); - if (pat.empty ()) - pat = pattern (xc, xl == lang::c ? "gcc" : "g++"); + if (cpat.empty ()) + cpat = pattern (xc, xl == lang::c ? "gcc" : "g++"); - if (pat.empty ()) - pat = pattern (xc, xl == lang::c ? "cc" : "c++"); + if (cpat.empty ()) + cpat = pattern (xc, xl == lang::c ? "cc" : "c++"); } // Runtime and standard library. @@ -1829,8 +2312,8 @@ namespace build2 move (gr.checksum), // Calculated on whole -v output. move (t), move (ot), - move (pat), - move (bin_pat), + move (cpat), + move (bpat), move (rt), move (csl), move (xsl), @@ -2154,12 +2637,12 @@ namespace build2 cs.append (static_cast<size_t> (xl)); cs.append (xc.string ()); if (xis != nullptr) cs.append (*xis); - if (c_po != nullptr) hash_options (cs, *c_po); - if (x_po != nullptr) hash_options (cs, *x_po); - if (c_co != nullptr) hash_options (cs, *c_co); - if (x_co != nullptr) hash_options (cs, *x_co); - if (c_lo != nullptr) hash_options (cs, *c_lo); - if (x_lo != nullptr) hash_options (cs, *x_lo); + if (c_po != nullptr) append_options (cs, *c_po); + if (x_po != nullptr) append_options (cs, *x_po); + if (c_co != nullptr) append_options (cs, *c_co); + if (x_co != nullptr) append_options (cs, *x_co); + if (c_lo != nullptr) append_options (cs, *c_lo); + if (x_lo != nullptr) append_options (cs, *x_lo); key = cs.string (); auto i (cache.find (key)); |