diff options
Diffstat (limited to 'libbuild2/cc/module.cxx')
-rw-r--r-- | libbuild2/cc/module.cxx | 495 |
1 files changed, 327 insertions, 168 deletions
diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 8241a01..cf6c6e4 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -11,10 +11,7 @@ #include <libbuild2/bin/target.hxx> -#include <libbuild2/cc/target.hxx> // pc* - #include <libbuild2/config/utility.hxx> -#include <libbuild2/install/utility.hxx> #include <libbuild2/cc/guess.hxx> @@ -30,6 +27,8 @@ namespace build2 { tracer trace (x, "guess_init"); + context& ctx (rs.ctx); + bool cc_loaded (cast_false<bool> (rs["cc.core.guess.loaded"])); // Adjust module priority (compiler). Also order cc module before us @@ -41,7 +40,10 @@ namespace build2 config::save_module (rs, x, 250); - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); // Must already exist. // @@ -55,7 +57,7 @@ namespace build2 // config.x // - strings mode; + strings omode; // Original mode. { // Normally we will have a persistent configuration and computing the // default value every time will be a waste. So try without a default @@ -139,22 +141,37 @@ namespace build2 fail << "invalid path '" << s << "' in " << config_x; } - mode.assign (++v.begin (), v.end ()); + omode.assign (++v.begin (), v.end ()); // Save original path/mode in *.config.path/mode. // rs.assign (x_c_path) = xc; - rs.assign (x_c_mode) = mode; + rs.assign (x_c_mode) = omode; + + // Merge the configured mode options into user-specified (which must + // be done before loading the *.guess module). + // + // In particular, this ability to specify the compiler mode in a + // buildfile is useful in embedded development where the project may + // need to hardcode things like -target, -nostdinc, etc. + // + const strings& mode (cast<strings> (rs.assign (x_mode) += omode)); // Figure out which compiler we are dealing with, its target, etc. // // Note that we could allow guess() to modify mode to support // imaginary options (such as /MACHINE for cl.exe). Though it's not // clear what cc.mode would contain (original or modified). Note that - // we are now folding *.std options into mode options. + // we are now adding *.std options into mode options. + // + // @@ But can't the language standard options alter things like search + // directories? // x_info = &build2::cc::guess ( - x, x_lang, move (xc), + ctx, + x, x_lang, + rs.root_extra->environment_checksum, + move (xc), cast_null<string> (lookup_config (rs, config_x_id)), cast_null<string> (lookup_config (rs, config_x_version)), cast_null<string> (lookup_config (rs, config_x_target)), @@ -178,7 +195,8 @@ namespace build2 if (config_sub) { - ct = run<string> (3, + ct = run<string> (ctx, + 3, *config_sub, xi.target.c_str (), [] (string& l, bool) {return move (l);}); @@ -202,10 +220,24 @@ namespace build2 } } + // Hash the environment (used for change detection). + // + // Note that for simplicity we use the combined checksum for both + // compilation and linking (which may compile, think LTO). + // + { + sha256 cs; + hash_environment (cs, xi.compiler_environment); + hash_environment (cs, xi.platform_environment); + env_checksum = cs.string (); + } + // Assign values to variables that describe the compiler. // - rs.assign (x_path) = process_path_ex (xi.path, x_name, xi.checksum); - const strings& xm (cast<strings> (rs.assign (x_mode) = move (mode))); + // Note: x_mode is dealt with above. + // + rs.assign (x_path) = process_path_ex ( + xi.path, x_name, xi.checksum, env_checksum); rs.assign (x_id) = xi.id.string (); rs.assign (x_id_type) = to_string (xi.id.type); @@ -250,9 +282,9 @@ namespace build2 // if (!cc_loaded) { - // Prepare configuration hints. + // Prepare configuration hints (pretend it belongs to root scope). // - variable_map h (rs.ctx); + variable_map h (rs); // Note that all these variables have already been registered. // @@ -263,8 +295,8 @@ namespace build2 if (!xi.pattern.empty ()) h.assign ("config.cc.pattern") = xi.pattern; - if (!xm.empty ()) - h.assign ("config.cc.mode") = xm; + if (!omode.empty ()) + h.assign ("config.cc.mode") = move (omode); h.assign (c_runtime) = xi.runtime; h.assign (c_stdlib) = xi.c_stdlib; @@ -335,6 +367,8 @@ namespace build2 # ifdef __APPLE__ static const dir_path a_usr_inc ( "/Library/Developer/CommandLineTools/SDKs/MacOSX*.sdk/usr/include"); + static const dir_path a_usr_lib ( + "/Library/Developer/CommandLineTools/SDKs/MacOSX*.sdk/usr/lib"); # endif #endif @@ -344,7 +378,7 @@ namespace build2 struct search_dirs { pair<dir_paths, size_t> lib; - pair<dir_paths, size_t> inc; + pair<dir_paths, size_t> hdr; }; static global_cache<search_dirs> dirs_cache; @@ -357,6 +391,20 @@ namespace build2 const compiler_info& xi (*x_info); const target_triplet& tt (cast<target_triplet> (rs[x_target])); + // Load cc.core.config. + // + if (!cast_false<bool> (rs["cc.core.config.loaded"])) + { + // Prepare configuration hints (pretend it belongs to root scope). + // + variable_map h (rs); + + if (!xi.bin_pattern.empty ()) + h.assign ("config.bin.pattern") = xi.bin_pattern; + + init_module (rs, rs, "cc.core.config", loc, false, h); + } + // Configuration. // using config::lookup_config; @@ -426,20 +474,114 @@ namespace build2 translate_std (xi, tt, rs, mode, v); } - // config.x.translatable_header + // config.x.internal.scope + // + // Note: save omitted. + // + // The effective internal_scope value is chosen based on the following + // priority list: + // + // 1. config.x.internal.scope + // + // 2. config.cc.internal.scope + // + // 3. effective value from bundle amalgamation + // + // 4. x.internal.scope + // + // 5. cc.internal.scope + // + // Note also that we only update x.internal.scope (and not cc.*) to + // reflect the effective value. + // + const string* iscope_str (nullptr); + { + if (lookup l = lookup_config (rs, config_x_internal_scope)) // 1 + { + iscope_str = &cast<string> (l); + + if (*iscope_str == "current") + fail << "'current' value in " << config_x_internal_scope; + } + else if (lookup l = rs["config.cc.internal.scope"]) // 2 + { + iscope_str = &cast<string> (l); + } + else // 3 + { + const scope& as (*rs.bundle_scope ()); + + if (as != rs) + { + // Only use the value if the corresponding module is loaded. + // + bool xl (cast_false<bool> (as[string (x) + ".config.loaded"])); + if (xl) + iscope_str = cast_null<string> (as[x_internal_scope]); + + if (iscope_str == nullptr) + { + if (xl || cast_false<bool> (as["cc.core.config.loaded"])) + iscope_str = cast_null<string> (as["cc.internal.scope"]); + } + + if (iscope_str != nullptr && *iscope_str == "current") + iscope_current = &as; + } + } + + lookup l; + if (iscope_str == nullptr) + { + iscope_str = cast_null<string> (l = rs[x_internal_scope]); // 4 + + if (iscope_str == nullptr) + iscope_str = cast_null<string> (rs["cc.internal.scope"]); // 5 + } + + if (iscope_str != nullptr) + { + const string& s (*iscope_str); + + // Assign effective. + // + if (!l) + rs.assign (x_internal_scope) = s; + + if (s == "current") + { + iscope = internal_scope::current; + + if (iscope_current == nullptr) + iscope_current = &rs; + } + else if (s == "base") iscope = internal_scope::base; + else if (s == "root") iscope = internal_scope::root; + else if (s == "bundle") iscope = internal_scope::bundle; + else if (s == "strong") iscope = internal_scope::strong; + else if (s == "weak") iscope = internal_scope::weak; + else if (s == "global") + ; // Nothing to translate; + else + fail << "invalid " << x_internal_scope << " value '" << s << "'"; + } + } + + // config.x.translate_include // // It's still fuzzy whether specifying (or maybe tweaking) this list in // the configuration will be a common thing to do so for now we use - // omitted. It's also probably too early to think whether we should have - // the cc.* version and what the semantics should be. + // omitted. // - if (x_translatable_headers != nullptr) + if (x_translate_include != nullptr) { - lookup l (lookup_config (rs, *config_x_translatable_headers)); - - // @@ MODHDR: if(modules) ? - // - rs.assign (x_translatable_headers) += cast_null<strings> (l); + if (lookup l = lookup_config (rs, *config_x_translate_include)) + { + // @@ MODHDR: if(modules) ? Yes. + // + rs.assign (x_translate_include).prepend ( + cast<translatable_headers> (l)); + } } // Extract system header/library search paths from the compiler and @@ -448,13 +590,13 @@ namespace build2 // Note that for now module search paths only come from compiler_info. // pair<dir_paths, size_t> lib_dirs; - pair<dir_paths, size_t> inc_dirs; + pair<dir_paths, size_t> hdr_dirs; const optional<pair<dir_paths, size_t>>& mod_dirs (xi.sys_mod_dirs); - if (xi.sys_lib_dirs && xi.sys_inc_dirs) + if (xi.sys_lib_dirs && xi.sys_hdr_dirs) { lib_dirs = *xi.sys_lib_dirs; - inc_dirs = *xi.sys_inc_dirs; + hdr_dirs = *xi.sys_hdr_dirs; } else { @@ -481,27 +623,27 @@ namespace build2 switch (xi.class_) { case compiler_class::gcc: - lib_dirs = gcc_library_search_dirs (xi.path, rs); + lib_dirs = gcc_library_search_dirs (xi, rs); break; case compiler_class::msvc: - lib_dirs = msvc_library_search_dirs (xi.path, rs); + lib_dirs = msvc_library_search_dirs (xi, rs); break; } } - if (xi.sys_inc_dirs) - inc_dirs = *xi.sys_inc_dirs; + if (xi.sys_hdr_dirs) + hdr_dirs = *xi.sys_hdr_dirs; else if (sd != nullptr) - inc_dirs = sd->inc; + hdr_dirs = sd->hdr; else { switch (xi.class_) { case compiler_class::gcc: - inc_dirs = gcc_header_search_dirs (xi.path, rs); + hdr_dirs = gcc_header_search_dirs (xi, rs); break; case compiler_class::msvc: - inc_dirs = msvc_header_search_dirs (xi.path, rs); + hdr_dirs = msvc_header_search_dirs (xi, rs); break; } } @@ -510,17 +652,17 @@ namespace build2 { search_dirs sd; if (!xi.sys_lib_dirs) sd.lib = lib_dirs; - if (!xi.sys_inc_dirs) sd.inc = inc_dirs; + if (!xi.sys_hdr_dirs) sd.hdr = hdr_dirs; dirs_cache.insert (move (key), move (sd)); } } sys_lib_dirs_mode = lib_dirs.second; - sys_inc_dirs_mode = inc_dirs.second; + sys_hdr_dirs_mode = hdr_dirs.second; sys_mod_dirs_mode = mod_dirs ? mod_dirs->second : 0; - sys_lib_dirs_extra = lib_dirs.first.size (); - sys_inc_dirs_extra = inc_dirs.first.size (); + sys_lib_dirs_extra = 0; + sys_hdr_dirs_extra = 0; #ifndef _WIN32 // Add /usr/local/{include,lib}. We definitely shouldn't do this if we @@ -536,11 +678,11 @@ namespace build2 // on the next invocation. // { - auto& is (inc_dirs.first); + auto& hs (hdr_dirs.first); auto& ls (lib_dirs.first); - bool ui (find (is.begin (), is.end (), usr_inc) != is.end ()); - bool uli (find (is.begin (), is.end (), usr_loc_inc) != is.end ()); + bool ui (find (hs.begin (), hs.end (), usr_inc) != hs.end ()); + bool uli (find (hs.begin (), hs.end (), usr_loc_inc) != hs.end ()); #ifdef __APPLE__ // On Mac OS starting from 10.14 there is no longer /usr/include. @@ -563,15 +705,28 @@ namespace build2 // // Is Apple's /usr/include. // - if (!ui && !uli) + // Also, it appears neither Clang nor GCC report MacOSX*.sdk/usr/lib + // with -print-search-dirs but they do search in there. So we add it + // to our list if we see MacOSX*.sdk/usr/include. + // + auto aui (find_if (hs.begin (), hs.end (), + [] (const dir_path& d) + { + return path_match (d, a_usr_inc); + })); + + if (aui != hs.end ()) { - for (const dir_path& d: is) + if (!ui) + ui = true; + + if (find_if (ls.begin (), ls.end (), + [] (const dir_path& d) + { + return path_match (d, a_usr_lib); + }) == ls.end ()) { - if (path_match (d, a_usr_inc)) - { - ui = true; - break; - } + ls.push_back (aui->directory () /= "lib"); } } #endif @@ -579,18 +734,29 @@ namespace build2 { bool ull (find (ls.begin (), ls.end (), usr_loc_lib) != ls.end ()); - // Many platforms don't search in /usr/local/lib by default (but do - // for headers in /usr/local/include). So add it as the last option. + // Many platforms don't search in /usr/local/lib by default but do + // for headers in /usr/local/include. + // + // Note that customarily /usr/local/include is searched before + // /usr/include so we add /usr/local/lib before built-in entries + // (there isn't really a way to add it after since all we can do is + // specify it with -L). // if (!ull && exists (usr_loc_lib, true /* ignore_error */)) - ls.push_back (usr_loc_lib); + { + ls.insert (ls.begin () + sys_lib_dirs_mode, usr_loc_lib); + ++sys_lib_dirs_extra; + } // FreeBSD is at least consistent: it searches in neither. Quoting // its wiki: "FreeBSD can't even find libraries that it installed." // So let's help it a bit. // if (!uli && exists (usr_loc_inc, true /* ignore_error */)) - is.push_back (usr_loc_inc); + { + hs.insert (hs.begin () + sys_hdr_dirs_mode, usr_loc_inc); + ++sys_hdr_dirs_extra; + } } } #endif @@ -632,7 +798,7 @@ namespace build2 if (xi.variant_version) { - dr << " variant: " << '\n' + dr << " variant:" << '\n' << " version " << xi.variant_version->string << '\n' << " major " << xi.variant_version->major << '\n' << " minor " << xi.variant_version->minor << '\n' @@ -667,9 +833,19 @@ namespace build2 } auto& mods (mod_dirs ? mod_dirs->first : dir_paths ()); - auto& incs (inc_dirs.first); + auto& incs (hdr_dirs.first); auto& libs (lib_dirs.first); + if (verb >= 3 && iscope) + { + dr << "\n int scope "; + + if (*iscope == internal_scope::current) + dr << iscope_current->out_path (); + else + dr << *iscope_str; + } + if (verb >= 3 && !mods.empty ()) { dr << "\n mod dirs"; @@ -681,11 +857,14 @@ namespace build2 if (verb >= 3 && !incs.empty ()) { - dr << "\n inc dirs"; + dr << "\n hdr dirs"; for (size_t i (0); i != incs.size (); ++i) { - if (i == sys_inc_dirs_extra) + if ((sys_hdr_dirs_mode != 0 && i == sys_hdr_dirs_mode) || + (sys_hdr_dirs_extra != 0 && + i == sys_hdr_dirs_extra + sys_hdr_dirs_mode)) dr << "\n --"; + dr << "\n " << incs[i]; } } @@ -695,31 +874,36 @@ namespace build2 dr << "\n lib dirs"; for (size_t i (0); i != libs.size (); ++i) { - if (i == sys_lib_dirs_extra) + if ((sys_lib_dirs_mode != 0 && i == sys_lib_dirs_mode) || + (sys_lib_dirs_extra != 0 && + i == sys_lib_dirs_extra + sys_lib_dirs_mode)) dr << "\n --"; + dr << "\n " << libs[i]; } } } rs.assign (x_sys_lib_dirs) = move (lib_dirs.first); - rs.assign (x_sys_inc_dirs) = move (inc_dirs.first); + rs.assign (x_sys_hdr_dirs) = move (hdr_dirs.first); - // Load cc.core.config. - // - if (!cast_false<bool> (rs["cc.core.config.loaded"])) - { - variable_map h (rs.ctx); - - if (!xi.bin_pattern.empty ()) - h.assign ("config.bin.pattern") = xi.bin_pattern; - - init_module (rs, rs, "cc.core.config", loc, false, h); - } + config::save_environment (rs, xi.compiler_environment); + config::save_environment (rs, xi.platform_environment); } + // Global cache of ad hoc importable headers. + // + // The key is a hash of the system header search directories + // (sys_hdr_dirs) where we search for the headers. + // + static map<string, importable_headers> importable_headers_cache; + static mutex importable_headers_mutex; + void module:: - init (scope& rs, const location& loc, const variable_map&) + init (scope& rs, + const location& loc, + const variable_map&, + const compiler_info& xi) { tracer trace (x, "init"); @@ -740,110 +924,82 @@ namespace build2 // load_module (rs, rs, "cc.core", loc); - // Process, sort, and cache (in this->xlate_hdr) translatable headers. - // Keep the cache NULL if unused or empty. - // - // @@ MODHDR TODO: support exclusions entries (e.g., -<stdio.h>)? + // Search include translation headers and groups. // - if (modules && x_translatable_headers != nullptr) + if (modules) { - strings* ih (cast_null<strings> (rs.assign (x_translatable_headers))); + { + sha256 k; + for (const dir_path& d: sys_hdr_dirs) + k.append (d.string ()); + + mlock l (importable_headers_mutex); + importable_headers = &importable_headers_cache[k.string ()]; + } + + auto& hs (*importable_headers); + + ulock ul (hs.mutex); - if (ih != nullptr && !ih->empty ()) + if (hs.group_map.find (header_group_std) == hs.group_map.end ()) + guess_std_importable_headers (xi, sys_hdr_dirs, hs); + + // Process x.translate_include. + // + const variable& var (*x_translate_include); + if (auto* v = cast_null<translatable_headers> (rs[var])) { - // Translate <>-style header names to absolute paths using the - // compiler's include search paths. Otherwise complete and normalize - // since when searching in this list we always use the absolute and - // normalized header target path. - // - for (string& h: *ih) + for (const auto& p: *v) { - if (h.empty ()) - continue; + const string& k (p.first); - path f; - if (h.front () == '<' && h.back () == '>') + if (k.front () == '<' && k.back () == '>') { - h.pop_back (); - h.erase (0, 1); + if (path_pattern (k)) + { + size_t n (hs.insert_angle_pattern (sys_hdr_dirs, k)); - for (const dir_path& d: sys_inc_dirs) + l5 ([&]{trace << "pattern " << k << " searched to " << n + << " headers";}); + } + else { - if (file_exists ((f = d, f /= h), - true /* follow_symlinks */, - true /* ignore_errors */)) - goto found; + // What should we do if not found? While we can fail, this + // could be too drastic if, for example, the header is + // "optional" and may or may not be present/used. So for now + // let's ignore (we could have also removed it from the map as + // an indication). + // + const auto* r (hs.insert_angle (sys_hdr_dirs, k)); + + l5 ([&]{trace << "header " << k << " searched to " + << (r ? r->first.string ().c_str () : "<none>");}); } - - // What should we do if not found? While we can fail, this could - // be too drastic if, for example, the header is "optional" and - // may or may not be present/used. So for now let's restore the - // original form to aid debugging (it can't possibly match any - // absolute path). + } + else if (path_traits::find_separator (k) == string::npos) + { + // Group name. // - h.insert (0, 1, '<'); - h.push_back ('>'); - continue; - - found: - ; // Fall through. + if (k != header_group_all_importable && + k != header_group_std_importable && + k != header_group_all && + k != header_group_std) + fail (loc) << "unknown header group '" << k << "' in " << var; } else { - f = path (move (h)); - - if (f.relative ()) - f.complete (); + // Absolute and normalized header path. + // + if (!path_traits::absolute (k)) + fail (loc) << "relative header path '" << k << "' in " << var; } - - // @@ MODHDR: should we use the more elaborate but robust - // normalize/realize scheme so the we get the same - // path? Feels right. - f.normalize (); - h = move (f).string (); } - - sort (ih->begin (), ih->end ()); - xlate_hdr = ih; } } // Register target types and configure their "installability". // - bool install_loaded (cast_false<bool> (rs["install.loaded"])); - - { - using namespace install; - - rs.insert_target_type (x_src); - - auto insert_hdr = [&rs, install_loaded] (const target_type& tt) - { - rs.insert_target_type (tt); - - // Install headers into install.include. - // - if (install_loaded) - install_path (rs, tt, dir_path ("include")); - }; - - // Note: module (x_mod) is in x_hdr. - // - for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) - insert_hdr (**ht); - - // Also register the C header for C-derived languages. - // - if (*x_hdr != &h::static_type) - insert_hdr (h::static_type); - - rs.insert_target_type<pc> (); - rs.insert_target_type<pca> (); - rs.insert_target_type<pcs> (); - - if (install_loaded) - install_path<pc> (rs, dir_path ("pkgconfig")); - } + load_module (rs, rs, (string (x) += ".types"), loc); // Register rules. // @@ -941,34 +1097,37 @@ namespace build2 // them in case they depend on stuff that we need to install (see the // install rule implementations for details). // - if (install_loaded) + if (cast_false<bool> (rs["install.loaded"])) { + // Note: we rely quite heavily in these rule implementations that + // these are the only target types they are registered for. + const install_rule& ir (*this); - r.insert<exe> (perform_install_id, x_install, ir); - r.insert<exe> (perform_uninstall_id, x_uninstall, ir); + r.insert<exe> (perform_install_id, x_install, ir); + r.insert<exe> (perform_uninstall_id, x_install, ir); - r.insert<liba> (perform_install_id, x_install, ir); - r.insert<liba> (perform_uninstall_id, x_uninstall, ir); + r.insert<liba> (perform_install_id, x_install, ir); + r.insert<liba> (perform_uninstall_id, x_install, ir); if (s) { - r.insert<libs> (perform_install_id, x_install, ir); - r.insert<libs> (perform_uninstall_id, x_uninstall, ir); + r.insert<libs> (perform_install_id, x_install, ir); + r.insert<libs> (perform_uninstall_id, x_install, ir); } const libux_install_rule& lr (*this); - r.insert<libue> (perform_install_id, x_install, lr); - r.insert<libue> (perform_uninstall_id, x_uninstall, lr); + r.insert<libue> (perform_install_id, x_install, lr); + r.insert<libue> (perform_uninstall_id, x_install, lr); - r.insert<libua> (perform_install_id, x_install, lr); - r.insert<libua> (perform_uninstall_id, x_uninstall, lr); + r.insert<libua> (perform_install_id, x_install, lr); + r.insert<libua> (perform_uninstall_id, x_install, lr); if (s) { - r.insert<libus> (perform_install_id, x_install, lr); - r.insert<libus> (perform_uninstall_id, x_uninstall, lr); + r.insert<libus> (perform_install_id, x_install, lr); + r.insert<libus> (perform_uninstall_id, x_install, lr); } } } |