From cfe4db0858677230a1b8e06e8b965bc580e3c97a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 13 May 2024 14:00:05 +0200 Subject: Issue better diagnostics if standard library modules not supported --- libbuild2/cc/compile-rule.cxx | 292 ++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 142 deletions(-) diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index 1548395..5acd698 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -6151,177 +6151,185 @@ namespace build2 } }; - // Pre-resolve std modules in an ad hoc way for certain compilers. + // Pre-resolve standard library modules (std and std.compat) in an ad + // hoc way. // - // @@ TODO: cache x_stdlib value. + + // Similar logic to check_exact() above. // - if ((ctype == compiler_type::msvc) || - (ctype == compiler_type::clang && - cmaj >= 18 && - cast (rs[x_stdlib]) == "libc++")) + done = true; + + for (size_t i (0); i != n; ++i) { - // Similar logic to check_exact() above. - // - done = true; + module_import& m (imports[i]); - for (size_t i (0); i != n; ++i) + if (m.name == "std" || m.name == "std.compat") { - module_import& m (imports[i]); + otype ot (otype::e); + const target* mt (nullptr); - if (m.name == "std" || m.name == "std.compat") + switch (ctype) { - otype ot (otype::e); - const target* mt (nullptr); - - switch (ctype) + case compiler_type::clang: { - case compiler_type::clang: - { - // Find or insert std*.cppm (similar code to pkgconfig.cxx). - // - // Note: build_install_data is absolute and normalized. - // - mt = &ctx.targets.insert_locked ( - *x_mod, - (dir_path (build_install_data) /= "libbuild2") /= "cc", - dir_path (), - m.name, - string ("cppm"), // For C++14 during bootstrap. - target_decl::implied, - trace).first; - - // Which output type should we use, static or shared? The - // correct way would be to detect whether static or shared - // version of libc++ is to be linked and use the corresponding - // type. And we could do that by looking for -static-libstdc++ - // in loption (and no, it's not -static-libc++). - // - // But, looking at the object file produced from std*.cppm, - // they only contain one symbol, the static object - // initializer. And this is unlikely to change since all - // other non-inline or template symbols should be in - // libc++. So feels like it's not worth the trouble and one - // variant should be good enough for both cases. Let's use the - // shared one for less surprising diagnostics (as in, "why are - // you linking obje{} to a shared library?") - // - // (Of course, theoretically, std*.cppm could detect via a - // macro whether they are being compiled with -fPIC or not and - // do things differently, but this seems far-fetched). - // - ot = otype::s; + // @@ TODO: cache x_stdlib value. + // + if (cast (rs[x_stdlib]) != "libc++") + fail << "standard library module '" << m.name << "' is " + << "currently only supported in libc++" << + info << "try adding -stdlib=libc++ as compiler mode option"; - break; - } - case compiler_type::msvc: + if (cmaj < 18) + fail << "standard library module '" << m.name << "' is " + << "only supported in Clang 18 or later"; + + // Find or insert std*.cppm (similar code to pkgconfig.cxx). + // + // Note: build_install_data is absolute and normalized. + // + mt = &ctx.targets.insert_locked ( + *x_mod, + (dir_path (build_install_data) /= "libbuild2") /= "cc", + dir_path (), + m.name, + string ("cppm"), // For C++14 during bootstrap. + target_decl::implied, + trace).first; + + // Which output type should we use, static or shared? The + // correct way would be to detect whether static or shared + // version of libc++ is to be linked and use the corresponding + // type. And we could do that by looking for -static-libstdc++ + // in loption (and no, it's not -static-libc++). + // + // But, looking at the object file produced from std*.cppm, they + // only contain one symbol, the static object initializer. And + // this is unlikely to change since all other non-inline or + // template symbols should be in libc++. So feels like it's not + // worth the trouble and one variant should be good enough for + // both cases. Let's use the shared one for less surprising + // diagnostics (as in, "why are you linking obje{} to a shared + // library?") + // + // (Of course, theoretically, std*.cppm could detect via a macro + // whether they are being compiled with -fPIC or not and do + // things differently, but this seems far-fetched). + // + ot = otype::s; + + break; + } + case compiler_type::msvc: + { + // For MSVC, the source files std.ixx and std.compat.ixx are + // found in the modules/ subdirectory which is a sibling of + // include/ in the MSVC toolset (and "that is a contract with + // customers" to quote one of the developers). + // + // The problem of course is that there are multiple system + // header search directories (for example, as specified in the + // INCLUDE environment variable) and which one of them is for + // the MSVC toolset is not specified. So what we are going to do + // is search for one of the well-known standard C++ headers and + // assume that the directory where we found it is the one we are + // looking for. Or we could look for something MSVC-specific + // like vcruntime.h. + // + dir_path modules; + if (optional p = find_system_header (path ("vcruntime.h"))) { - // For MSVC, the source files std.ixx and std.compat.ixx are - // found in the modules/ subdirectory which is a sibling of - // include/ in the MSVC toolset (and "that is a contract with - // customers" to quote one of the developers). - // - // The problem of course is that there are multiple system - // header search directories (for example, as specified in the - // INCLUDE environment variable) and which one of them is for - // the MSVC toolset is not specified. So what we are going to - // do is search for one of the well-known standard C++ headers - // and assume that the directory where we found it is the one - // we are looking for. Or we could look for something - // MSVC-specific like vcruntime.h. - // - dir_path modules; - if (optional p = find_system_header (path ("vcruntime.h"))) + p->make_directory (); // Strip vcruntime.h. + if (p->leaf () == path ("include")) // Sanity check. { - p->make_directory (); // Strip vcruntime.h. - if (p->leaf () == path ("include")) // Sanity check. - { - modules = path_cast (move (p->make_directory ())); - modules /= "modules"; - } + modules = path_cast (move (p->make_directory ())); + modules /= "modules"; } + } - if (modules.empty ()) - fail << "unable to locate MSVC standard modules directory"; - - mt = &ctx.targets.insert_locked ( - *x_mod, - move (modules), - dir_path (), - m.name, - string ("ixx"), // For C++14 during bootstrap. - target_decl::implied, - trace).first; + if (modules.empty ()) + fail << "unable to locate MSVC standard modules directory"; - // For MSVC it's easier to detect the runtime being used since - // it's specified with the compile options (/MT[d], /MD[d]). - // - // Similar semantics as in extract_headers() except here we - // use options visible from the root scope. Note that - // find_option_prefixes() looks in reverse, so look in the - // cmode, x_coptions, c_coptions order. - // - initializer_list os {"/MD", "/MT", "-MD", "-MT"}; + mt = &ctx.targets.insert_locked ( + *x_mod, + move (modules), + dir_path (), + m.name, + string ("ixx"), // For C++14 during bootstrap. + target_decl::implied, + trace).first; - const string* o; - if ((o = find_option_prefixes (os, cmode)) != nullptr || - (o = find_option_prefixes (os, rs, x_coptions)) != nullptr || - (o = find_option_prefixes (os, rs, c_coptions)) != nullptr) - { - ot = (*o)[2] == 'D' ? otype::s : otype::a; - } - else - ot = otype::s; // The default is /MD. + // For MSVC it's easier to detect the runtime being used since + // it's specified with the compile options (/MT[d], /MD[d]). + // + // Similar semantics as in extract_headers() except here we use + // options visible from the root scope. Note that + // find_option_prefixes() looks in reverse, so look in the + // cmode, x_coptions, c_coptions order. + // + initializer_list os {"/MD", "/MT", "-MD", "-MT"}; - break; + const string* o; + if ((o = find_option_prefixes (os, cmode)) != nullptr || + (o = find_option_prefixes (os, rs, x_coptions)) != nullptr || + (o = find_option_prefixes (os, rs, c_coptions)) != nullptr) + { + ot = (*o)[2] == 'D' ? otype::s : otype::a; } - case compiler_type::gcc: - case compiler_type::icc: - assert (false); - }; + else + ot = otype::s; // The default is /MD. + + break; + } + case compiler_type::gcc: + case compiler_type::icc: + { + fail << "standard library module '" << m.name << "' is " + << "not yet supported in this compiler"; + } + }; - pair tl ( - this->make_module_sidebuild ( // GCC 4.9 - a, bs, nullptr, ot, *mt, m.name)); + pair tl ( + this->make_module_sidebuild ( // GCC 4.9 + a, bs, nullptr, ot, *mt, m.name)); - if (tl.second.owns_lock ()) + if (tl.second.owns_lock ()) + { + // Special compile options for the std modules. + // + if (ctype == compiler_type::clang) { - // Special compile options for the std modules. - // - if (ctype == compiler_type::clang) - { - value& v (tl.first.append_locked (x_coptions)); + value& v (tl.first.append_locked (x_coptions)); - if (v.null) - v = strings {}; + if (v.null) + v = strings {}; - strings& cops (v.as ()); + strings& cops (v.as ()); - switch (ctype) + switch (ctype) + { + case compiler_type::clang: { - case compiler_type::clang: - { - cops.push_back ("-Wno-reserved-module-identifier"); - break; - } - case compiler_type::msvc: - // It appears nothing special is needed to compile MSVC - // standard modules. - case compiler_type::gcc: - case compiler_type::icc: - assert (false); - }; - } - - tl.second.unlock (); + cops.push_back ("-Wno-reserved-module-identifier"); + break; + } + case compiler_type::msvc: + // It appears nothing special is needed to compile MSVC + // standard modules. + case compiler_type::gcc: + case compiler_type::icc: + assert (false); + }; } - pts[start + i].target = &tl.first; - m.score = match_max (m.name) + 1; - continue; // Scan the rest to detect if all done. + tl.second.unlock (); } - done = false; + pts[start + i].target = &tl.first; + m.score = match_max (m.name) + 1; + continue; // Scan the rest to detect if all done. } + + done = false; } // Go over prerequisites and try to resolve imported modules with them. -- cgit v1.1