/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmGlobalFastbuildGenerator.h" #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include "cmFastbuildLinkLineComputer.h" #include "cmFastbuildTargetGenerator.h" // IWYU pragma: keep #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobCacheEntry.h" #include "cmGlobalGenerator.h" #include "cmGlobalGeneratorFactory.h" #include "cmList.h" #include "cmLocalFastbuildGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmState.h" #include "cmStateDirectory.h" #include "cmStateSnapshot.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" #include "cmVersion.h" #include "cmake.h" #if defined(_WIN32) # include # include # include #endif class cmLinkLineComputer; #define FASTBUILD_REBUILD_BFF_TARGET_NAME "rebuild-bff" #define FASTBUILD_GLOB_CHECK_TARGET "glob-check" #define FASTBUILD_ENV_VAR_NAME "LocalEnv" // IDE support #define FASTBUILD_XCODE_BASE_PATH "XCode/Projects" #define FASTBUILD_VS_BASE_PATH "VisualStudio/Projects" #define FASTBUILD_IDE_VS_COMMAND_PREFIX "cd ^$(SolutionDir).. && " #define FASTBUILD_IDE_BUILD_ARGS " -ide -cache -summary -dist " constexpr auto FASTBUILD_CAPTURE_SYSTEM_ENV = "CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV"; constexpr auto FASTBUILD_ENV_OVERRIDES = "CMAKE_FASTBUILD_ENV_OVERRIDES"; // Inherits from "CMAKE_FASTBUILD_VERBOSE_GENERATOR" env variable. constexpr auto FASTBUILD_VERBOSE_GENERATOR = "CMAKE_FASTBUILD_VERBOSE_GENERATOR"; constexpr auto FASTBUILD_CACHE_PATH = "CMAKE_FASTBUILD_CACHE_PATH"; // Compiler settings. constexpr auto FASTBUILD_COMPILER_EXTRA_FILES = "CMAKE_FASTBUILD_COMPILER_EXTRA_FILES"; constexpr auto FASTBUILD_USE_LIGHTCACHE = "CMAKE_FASTBUILD_USE_LIGHTCACHE"; constexpr auto FASTBUILD_USE_RELATIVE_PATHS = "CMAKE_FASTBUILD_USE_RELATIVE_PATHS"; constexpr auto FASTBUILD_USE_DETERMINISTIC_PATHS = "CMAKE_FASTBUILD_USE_DETERMINISTIC_PATHS"; constexpr auto FASTBUILD_SOURCE_MAPPING = "CMAKE_FASTBUILD_SOURCE_MAPPING"; constexpr auto FASTBUILD_CLANG_REWRITE_INCLUDES = "CMAKE_FASTBUILD_CLANG_REWRITE_INCLUDES"; constexpr auto FASTBUILD_CLANG_GCC_UPDATE_XLANG_ARG = "CMAKE_FASTBUILD_CLANG_GCC_UPDATE_XLANG_ARG"; constexpr auto FASTBUILD_ALLOW_RESPONSE_FILE = "CMAKE_FASTBUILD_ALLOW_RESPONSE_FILE"; constexpr auto FASTBUILD_FORCE_RESPONSE_FILE = "CMAKE_FASTBUILD_FORCE_RESPONSE_FILE"; static std::map const compilerIdToFastbuildFamily = { { "MSVC", "msvc" }, { "Clang", "clang" }, { "AppleClang", "clang" }, { "GNU", "gcc" }, { "NVIDIA", "cuda-nvcc" }, { "Clang-cl", "clang-cl" }, }; static std::set const supportedLanguages = { "C", "CXX", "CUDA", "OBJC", "OBJCXX" }; template FastbuildAliasNode generateAlias(std::string const& name, char const* postfix, T const& nodes) { FastbuildAliasNode alias; alias.Name = name + postfix; for (auto const& node : nodes) { alias.PreBuildDependencies.emplace(node.Name); } return alias; } void FastbuildTarget::GenerateAliases() { // -deps this->DependenciesAlias.Name = this->Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX; for (auto const& dep : this->PreBuildDependencies) { if (dep.Type != FastbuildTargetDepType::ORDER_ONLY) { this->DependenciesAlias.PreBuildDependencies.emplace(dep); } } // PRE/POST/REST if (!this->PreBuildExecNodes.PreBuildDependencies.empty()) { this->PreBuildExecNodes.Name = this->Name + FASTBUILD_PRE_BUILD_ALIAS_POSTFIX; } if (!this->PreLinkExecNodes.Nodes.empty()) { this->PreLinkExecNodes.Alias = generateAlias(this->Name, FASTBUILD_PRE_LINK_ALIAS_POSTFIX, this->PreLinkExecNodes.Nodes); } if (!this->PostBuildExecNodes.Alias.PreBuildDependencies.empty()) { this->PostBuildExecNodes.Alias.Name = this->Name + FASTBUILD_POST_BUILD_ALIAS_POSTFIX; } if (!this->ExecNodes.PreBuildDependencies.empty()) { this->ExecNodes.Name = this->Name + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX; } // If we don't have any node that we can build by name (e.g. no static / // dynamic lib or executable) -> create an alias so that we can build this // target by name. if (LinkerNode.empty()) { FastbuildAliasNode alias; alias.Name = this->Name; if (LinkerNode.empty()) { for (FastbuildObjectListNode const& objListNode : ObjectListNodes) { alias.PreBuildDependencies.emplace(objListNode.Name); } } else { for (FastbuildLinkerNode const& linkerNode : LinkerNode) { alias.PreBuildDependencies.emplace(linkerNode.Name); } } AliasNodes.emplace_back(std::move(alias)); } // Link artifacts (should not be added to all // since on Windows it might contain Import Lib and FASTBuild doesn't know // how to create it, so "-all" will fail). AliasNodes.emplace_back(generateAlias( this->Name, FASTBUILD_OBJECTS_ALIAS_POSTFIX, this->ObjectListNodes)); for (auto const& linkerNode : this->LinkerNode) { if (linkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY || linkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY || linkerNode.Type == FastbuildLinkerNode::EXECUTABLE) { std::string postfix = FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX; if (!linkerNode.Arch.empty()) { postfix += cmStrCat('-', linkerNode.Arch); } #ifdef _WIN32 // On Windows DLL and Executables must be linked via Import Lib file // (.lib). if (linkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY || linkerNode.Type == FastbuildLinkerNode::EXECUTABLE) { FastbuildAliasNode linkAlias; linkAlias.Name = this->Name + FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX; linkAlias.PreBuildDependencies.emplace( FASTBUILD_DOLLAR_TAG "TargetOutputImplib" FASTBUILD_DOLLAR_TAG); AliasNodes.emplace_back(std::move(linkAlias)); continue; } #endif FastbuildAliasNode alias; alias.Name = this->Name + postfix; alias.PreBuildDependencies.emplace(linkerNode.LinkerOutput); AliasNodes.emplace_back(std::move(alias)); } } } cmGlobalFastbuildGenerator::cmGlobalFastbuildGenerator(cmake* cm) : cmGlobalCommonGenerator(cm) , BuildFileStream(nullptr) { #ifdef _WIN32 cm->GetState()->SetWindowsShell(true); #endif this->FindMakeProgramFile = "CMakeFastbuildFindMake.cmake"; cm->GetState()->SetFastbuildMake(true); cm->GetState()->SetIsGeneratorMultiConfig(false); } void cmGlobalFastbuildGenerator::ReadCompilerOptions( FastbuildCompiler& compiler, cmMakefile* mf) { if (compiler.CompilerFamily == "custom") { return; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_USE_LIGHTCACHE))) { compiler.UseLightCache = true; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_USE_RELATIVE_PATHS))) { compiler.UseRelativePaths = true; UsingRelativePaths = true; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_USE_DETERMINISTIC_PATHS))) { compiler.UseDeterministicPaths = true; } std::string sourceMapping = mf->GetSafeDefinition(FASTBUILD_SOURCE_MAPPING); if (!sourceMapping.empty()) { compiler.SourceMapping = std::move(sourceMapping); } auto const clangRewriteIncludesDef = mf->GetDefinition(FASTBUILD_CLANG_REWRITE_INCLUDES); if (clangRewriteIncludesDef.IsSet() && clangRewriteIncludesDef.IsOff()) { compiler.ClangRewriteIncludes = false; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_CLANG_GCC_UPDATE_XLANG_ARG))) { compiler.ClangGCCUpdateXLanguageArg = true; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_ALLOW_RESPONSE_FILE))) { compiler.AllowResponseFile = true; } if (cmIsOn(mf->GetSafeDefinition(FASTBUILD_FORCE_RESPONSE_FILE))) { compiler.ForceResponseFile = true; } } void cmGlobalFastbuildGenerator::ProcessEnvironment() { bool const CaptureSystemEnv = !this->GetGlobalSetting(FASTBUILD_CAPTURE_SYSTEM_ENV).IsSet() || this->GetGlobalSetting(FASTBUILD_CAPTURE_SYSTEM_ENV).IsOn(); // On Windows environment is needed for MSVC, but preserve ability to discard // it from the generated file if requested. if (CaptureSystemEnv) { LocalEnvironment = cmSystemTools::GetEnvironmentVariables(); } // FASTBuild strips off "-isysroot" command line option (see : // https://github.com/fastbuild/fastbuild/issues/1066). // If 'SDK_ROOT' is not set via env and '-isysroot' is absent, AppleClang // seems to use MacOS SDK by default (even though FBuild flattens includes // before compiling). It breaks cross-compilation for iOS. Tested in // "RunCMake.Framework" test. std::string const osxRoot = this->GetSafeGlobalSetting("CMAKE_OSX_SYSROOT"); if (!osxRoot.empty()) { LocalEnvironment.emplace_back("SDKROOT=" + osxRoot); } auto const EnvOverrides = this->GetSafeGlobalSetting(FASTBUILD_ENV_OVERRIDES); if (!EnvOverrides.empty()) { auto const overrideEnvVar = [this](std::string const& prefix, std::string val) { auto const iter = std::find_if(LocalEnvironment.begin(), LocalEnvironment.end(), [&prefix](std::string const& value) { return cmSystemTools::StringStartsWith(value.c_str(), prefix.c_str()); }); if (iter != LocalEnvironment.end()) { *iter = std::move(val); } else { LocalEnvironment.emplace_back(std::move(val)); } }; for (auto const& val : cmList{ EnvOverrides }) { auto const pos = val.find('='); if (pos != std::string::npos && ((pos + 1) < val.size())) { overrideEnvVar(val.substr(0, pos + 1), val); } } } // Empty strings are not allowed. LocalEnvironment.erase( std::remove_if(LocalEnvironment.begin(), LocalEnvironment.end(), [](std::string const& s) { return s.empty(); }), LocalEnvironment.end()); } std::unique_ptr cmGlobalFastbuildGenerator::NewFactory() { return std::unique_ptr( new cmGlobalGeneratorSimpleFactory()); } void cmGlobalFastbuildGenerator::EnableLanguage( std::vector const& lang, cmMakefile* mf, bool optional) { this->cmGlobalGenerator::EnableLanguage(lang, mf, optional); for (std::string const& l : lang) { if (l == "NONE") { continue; } this->ResolveLanguageCompiler(l, mf, optional); } } bool cmGlobalFastbuildGenerator::FindMakeProgram(cmMakefile* mf) { if (!cmGlobalGenerator::FindMakeProgram(mf)) { return false; } if (auto fastbuildCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) { this->FastbuildCommand = *fastbuildCommand; std::vector command; command.push_back(this->FastbuildCommand); command.emplace_back("-version"); std::string version; std::string error; if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr, nullptr, cmSystemTools::OUTPUT_NONE)) { mf->IssueMessage(MessageType::FATAL_ERROR, "Running\n '" + cmJoin(command, "' '") + "'\n" "failed with:\n " + error); cmSystemTools::SetFatalErrorOccurred(); return false; } cmsys::RegularExpression versionRegex(R"(^FASTBuild v([0-9]+\.[0-9]+))"); versionRegex.find(version); this->FastbuildVersion = versionRegex.match(1); } return true; } std::unique_ptr cmGlobalFastbuildGenerator::CreateLocalGenerator(cmMakefile* makefile) { return std::unique_ptr( cm::make_unique(this, makefile)); } std::vector cmGlobalFastbuildGenerator::GenerateBuildCommand( std::string const& makeProgram, std::string const& /*projectName*/, std::string const& projectDir, std::vector const& targetNames, std::string const& /*config*/, int /*jobs*/, bool verbose, cmBuildOptions /*buildOptions*/, std::vector const& makeOptions) { GeneratedMakeCommand makeCommand; this->FastbuildCommand = this->SelectMakeProgram(makeProgram); makeCommand.Add(this->FastbuildCommand); // A build command for fastbuild looks like this: // fbuild.exe [make-options] [-config projectName.bff] std::string configFile = cmStrCat(projectDir, '/', FASTBUILD_BUILD_FILE); // Push in the make options makeCommand.Add(makeOptions.begin(), makeOptions.end()); if (!configFile.empty()) { makeCommand.Add("-config", configFile); } // Tested in "RunCMake.SymlinkTrees" test. makeCommand.Add("-continueafterdbmove"); // Tested in RunCMake.LinkWhatYouUse on Linux. (We need to see output of // LinkerStampExe process). // In general, it might be useful to see output of external processes // regardless of their outcome. makeCommand.Add("-showcmdoutput"); // Add the target-config to the command for (auto const& tname : targetNames) { if (!tname.empty()) { makeCommand.Add(tname); } } if (verbose) { makeCommand.Add("-verbose"); } // Make "rebuild-bff" target up-to-date before running the build. std::string output; ExecuteFastbuildTarget(projectDir, FASTBUILD_REBUILD_BFF_TARGET_NAME, output, { "-why" }); // If fbuild.bff was re-generated we need to "restat" it. if (output.find("Need to build") != std::string::npos) { // Let the user know that re-generation happened (and why it // happened). cmSystemTools::Stdout(output); // FASTBuild will consider the target out-of-date in case some of the // inputs have changes after re-generation which might happen if, for // example, configuration depends on some files generated during // the configuration itself. AskCMakeToMakeRebuildBFFUpToDate(projectDir); } return { std::move(makeCommand) }; } void cmGlobalFastbuildGenerator::ComputeTargetObjectDirectory( cmGeneratorTarget* gt) const { // Compute full path to object file directory for this target. std::string dir = cmStrCat(gt->GetSupportDirectory(), '/', this->GetCMakeCFGIntDir(), '/'); gt->ObjectDirectory = std::move(dir); } void cmGlobalFastbuildGenerator::AppendDirectoryForConfig( std::string const& prefix, std::string const& config, std::string const& suffix, std::string& dir) { if (!config.empty() && this->IsMultiConfig()) { dir += cmStrCat(prefix, config, suffix); } } cmDocumentationEntry cmGlobalFastbuildGenerator::GetDocumentation() { return { cmGlobalFastbuildGenerator::GetActualName(), "Generates fbuild.bff files." }; } void cmGlobalFastbuildGenerator::Generate() { // Check minimum Fastbuild version. if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->FastbuildVersion, RequiredFastbuildVersion())) { std::ostringstream msg; msg << "The detected version of Fastbuild (" << this->FastbuildVersion; msg << ") is less than the version of Fastbuild required by CMake ("; msg << this->RequiredFastbuildVersion() << ")."; this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, msg.str()); return; } this->ProcessEnvironment(); this->OpenBuildFileStream(); this->WriteSettings(); this->WriteEnvironment(); // Execute the standard generate process cmGlobalGenerator::Generate(); // Write compilers this->WriteCompilers(); this->WriteTargets(); this->CloseBuildFileStream(); if (cmSystemTools::GetErrorOccurredFlag()) { return; } this->RemoveUnknownClangTidyExportFixesFiles(); if (this->GetCMakeInstance()->GetRegenerateDuringBuild()) { return; } // TODO: figure out how to skip this in TryCompile // Make "rebuild-bff" target up-to-date after the generation. // This is actually a noop, it just asks CMake to touch the generated file // so FASTBuild would consider the target as up-to-date. AskCMakeToMakeRebuildBFFUpToDate( this->GetCMakeInstance()->GetHomeOutputDirectory()); } void cmGlobalFastbuildGenerator::AskCMakeToMakeRebuildBFFUpToDate( std::string const& workingDir) const { // "restat" the generated build file. // The idea here is to mimic what Ninja's "restat" command does. // We need to make the "rebuild.bff" target up-to-date, so the regeneration // will only be triggered when CMake files have actually changed. // Tested in "RunCMake.Configure" test. cmsys::ofstream{ cmStrCat(workingDir, '/', FASTBUILD_RESTAT_FILE).c_str(), std::ios::out | std::ios::binary } << cmStrCat(workingDir, '/', FASTBUILD_BUILD_FILE); std::string output; ExecuteFastbuildTarget(workingDir, FASTBUILD_REBUILD_BFF_TARGET_NAME, output); } void cmGlobalFastbuildGenerator::ExecuteFastbuildTarget( std::string const& dir, std::string const& target, std::string& output, std::vector const& fbuildOptions) const { std::vector command; command.emplace_back(this->FastbuildCommand); command.emplace_back("-config"); std::string const file = cmStrCat(dir, '/', FASTBUILD_BUILD_FILE); command.emplace_back(file); command.emplace_back(target); if (!fbuildOptions.empty()) { command.emplace_back(cmJoin(fbuildOptions, " ")); } int retVal = 0; if (!cmSystemTools::RunSingleCommand(command, &output, nullptr, &retVal, dir.c_str(), cmSystemTools::OUTPUT_NONE) || retVal != 0) { cmSystemTools::Error(cmStrCat("Failed to run FASTBuild command:\n '", cmJoin(command, "' '"), "'\nOutput:\n", output)); cmSystemTools::Stdout(output); std::exit(retVal); } } void cmGlobalFastbuildGenerator::WriteSettings() { // Define some placeholder WriteDivider(); *this->BuildFileStream << "// Helper variables\n\n"; WriteVariable("FB_INPUT_1_PLACEHOLDER", Quote("\"%1\"")); WriteVariable("FB_INPUT_1_0_PLACEHOLDER", Quote("\"%1[0]\"")); WriteVariable("FB_INPUT_1_1_PLACEHOLDER", Quote("\"%1[1]\"")); WriteVariable("FB_INPUT_2_PLACEHOLDER", Quote("\"%2\"")); WriteVariable("FB_INPUT_3_PLACEHOLDER", Quote("\"%3\"")); std::string cacheDir; // If explicitly set from CMake. auto val = this->GetSafeGlobalSetting(FASTBUILD_CACHE_PATH); if (!val.empty()) { cacheDir = std::move(val); cmSystemTools::ConvertToOutputSlashes(cacheDir); } WriteDivider(); *this->BuildFileStream << "// Settings\n\n"; WriteCommand("Settings"); *this->BuildFileStream << "{\n"; if (!cacheDir.empty()) { WriteVariable("CachePath", Quote(cacheDir), 1); } // Concurrency groups. WriteStruct( FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME, { { "ConcurrencyGroupName", Quote(FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME) }, { "ConcurrencyLimit", "1" } }, 1); WriteArray("ConcurrencyGroups", { "." FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME }, 1); *this->BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteEnvironment() { if (!LocalEnvironment.empty()) { WriteArray(FASTBUILD_ENV_VAR_NAME, Wrap(LocalEnvironment), 0); } } void cmGlobalFastbuildGenerator::WriteDivider() { *this->BuildFileStream << "// ======================================" "=======================================\n"; } void cmGlobalFastbuildGenerator::Indent(int count) { for (int i = 0; i < count; ++i) { *this->BuildFileStream << " "; } } void cmGlobalFastbuildGenerator::WriteComment(std::string const& comment, int indent) { if (comment.empty()) { return; } std::string::size_type lpos = 0; std::string::size_type rpos; *this->BuildFileStream << "\n"; Indent(indent); *this->BuildFileStream << "/////////////////////////////////////////////\n"; while ((rpos = comment.find('\n', lpos)) != std::string::npos) { Indent(indent); *this->BuildFileStream << "// " << comment.substr(lpos, rpos - lpos) << "\n"; lpos = rpos + 1; } Indent(indent); *this->BuildFileStream << "// " << comment.substr(lpos) << "\n\n"; } void cmGlobalFastbuildGenerator::WriteVariable(std::string const& key, std::string const& value, int indent) { WriteVariable(key, value, "=", indent); } void cmGlobalFastbuildGenerator::WriteVariable(std::string const& key, std::string const& value, std::string const& op, int indent) { Indent(indent); *this->BuildFileStream << "." << key << " " + op + (value.empty() ? "" : " ") << value << "\n"; } void cmGlobalFastbuildGenerator::WriteCommand(std::string const& command, std::string const& value, int indent) { Indent(indent); *this->BuildFileStream << command; if (!value.empty()) { *this->BuildFileStream << "(" << value << ")"; } *this->BuildFileStream << "\n"; } void cmGlobalFastbuildGenerator::WriteArray( std::string const& key, std::vector const& values, int indent) { WriteArray(key, values, "=", indent); } void cmGlobalFastbuildGenerator::WriteArray( std::string const& key, std::vector const& values, std::string const& op, int indent) { WriteVariable(key, "", op, indent); Indent(indent); *this->BuildFileStream << "{\n"; char const* sep = ""; for (std::string const& value : values) { *this->BuildFileStream << sep; sep = ",\n"; Indent(indent + 1); *this->BuildFileStream << value; } *this->BuildFileStream << "\n"; Indent(indent); *this->BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteStruct( std::string const& name, std::vector> const& variables, int indent) { WriteVariable(name, "", "=", indent); Indent(indent); *this->BuildFileStream << "[\n"; for (auto const& val : variables) { auto const& key = val.first; auto const& value = val.second; WriteVariable(key, value, "=", indent + 1); } Indent(indent); *this->BuildFileStream << "]\n"; } std::string cmGlobalFastbuildGenerator::Quote(std::string const& str, std::string const& quotation) { std::string result = str; cmSystemTools::ReplaceString(result, quotation, "^" + quotation); cmSystemTools::ReplaceString(result, FASTBUILD_DOLLAR_TAG, "$"); return quotation + result + quotation; } std::string cmGlobalFastbuildGenerator::QuoteIfHasSpaces(std::string str) { if (str.find(' ') != std::string::npos) { return '"' + str + '"'; } return str; } struct WrapHelper { std::string Prefix; std::string Suffix; bool EscapeDollar; std::string operator()(std::string in) { // If we have ^ in env variable - need to escape it. cmSystemTools::ReplaceString(in, "^", "^^"); // Those all are considered as line ends by FASTBuild. cmSystemTools::ReplaceString(in, "\n", "\\n"); cmSystemTools::ReplaceString(in, "\r", "\\r"); // Escaping of single quotes tested in "RunCMake.CompilerArgs" test. cmSystemTools::ReplaceString(in, "'", "^'"); std::string result = Prefix + in + Suffix; if (EscapeDollar) { cmSystemTools::ReplaceString(result, "$", "^$"); cmSystemTools::ReplaceString(result, FASTBUILD_DOLLAR_TAG, "$"); } return result; } std::string operator()(FastbuildTargetDep const& in) { return (*this)(in.Name); } }; template std::vector cmGlobalFastbuildGenerator::Wrap( T const& in, std::string const& prefix, std::string const& suffix, bool const escape_dollar) { std::vector result; WrapHelper helper = { prefix, suffix, escape_dollar }; std::transform(in.begin(), in.end(), std::back_inserter(result), helper); return result; } void cmGlobalFastbuildGenerator::TopologicalSort( std::vector& nodes) { std::unordered_map inDegree; std::unordered_map> reverseDeps; std::unordered_map originalIndex; // Track original positions for (std::size_t i = 0; i < nodes.size(); ++i) { auto const& node = nodes[i]; inDegree[node->Name] = 0; originalIndex[node->Name] = i; } // Build reverse dependency graph and in-degree map for (auto const& node : nodes) { for (auto const& dep : node->PreBuildDependencies) { if (inDegree.count(dep.Name)) { reverseDeps[dep.Name].insert(node->Name); ++inDegree[node->Name]; } } } // Min-heap based on original position auto const cmp = [&](std::string const& a, std::string const& b) { return originalIndex[a] > originalIndex[b]; }; std::priority_queue, decltype(cmp)> zeroInDegree(cmp); for (auto const& val : inDegree) { auto const& degree = val.second; auto const& name = val.first; if (degree == 0) { zeroInDegree.push(name); } } std::vector sorted; while (!zeroInDegree.empty()) { std::string node = zeroInDegree.top(); zeroInDegree.pop(); sorted.push_back(node); for (auto const& dep : reverseDeps[node]) { if (--inDegree[dep] == 0) { zeroInDegree.push(dep); } } } if (sorted.size() != nodes.size()) { cmSystemTools::Error("Failed to sort (Cyclic dependency)"); cmSystemTools::Error(cmStrCat("Sorted size: ", sorted.size())); cmSystemTools::Error(cmStrCat("nodes size: ", nodes.size())); for (auto const& node : nodes) { cmSystemTools::Error("Node: " + node->Name); for (auto const& dep : reverseDeps[node->Name]) { cmSystemTools::Error("\tReverse dep: " + dep); } for (auto const& child : node->PreBuildDependencies) { cmSystemTools::Error("\tChild: " + child.Name); } } for (auto const& node : sorted) { cmSystemTools::Error("Sorted: " + node); } for (auto const& node : nodes) { cmSystemTools::Error("In node: " + node->Name); } } // Reconstruct sorted nodes std::vector result; for (auto const& name : sorted) { auto it = std::find_if( nodes.begin(), nodes.end(), [&name](FastbuildTargetPtrT const& node) { return node /* the node might be in moved-from state*/ && node->Name == name; }); if (it != nodes.end()) { result.emplace_back(std::move(*it)); } } std::swap(result, nodes); } void cmGlobalFastbuildGenerator::WriteDisclaimer() { *this->BuildFileStream << "// CMAKE generated file: DO NOT EDIT!\n" << "// Generated by \"" << this->GetName() << "\"" << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "." << cmVersion::GetMinorVersion() << "\n\n"; } void cmGlobalFastbuildGenerator::OpenBuildFileStream() { // Compute Fastbuild's build file path. std::string buildFilePath = this->GetCMakeInstance()->GetHomeOutputDirectory(); buildFilePath += "/"; buildFilePath += FASTBUILD_BUILD_FILE; // Get a stream where to generate things. if (!this->BuildFileStream) { this->BuildFileStream = cm::make_unique( buildFilePath, false, this->GetMakefileEncoding()); if (!this->BuildFileStream) { // An error message is generated by the constructor if it cannot // open the file. return; } } // Write the do not edit header. this->WriteDisclaimer(); // Write a comment about this file. *this->BuildFileStream << "// This file contains all the build statements\n\n"; } void cmGlobalFastbuildGenerator::CloseBuildFileStream() { if (this->BuildFileStream) { this->BuildFileStream.reset(); } else { cmSystemTools::Error("Build file stream was not open."); } } void cmGlobalFastbuildGenerator::WriteCompilers() { WriteDivider(); *this->BuildFileStream << "// Compilers\n\n"; for (auto const& val : Compilers) { auto const& compilerDef = val.second; std::string compilerPath = compilerDef.Executable; // Write out the compiler that has been configured WriteCommand("Compiler", Quote(compilerDef.Name)); *this->BuildFileStream << "{\n"; for (auto const& extra : compilerDef.ExtraVariables) { auto const& extraKey = extra.first; auto const& extraVal = extra.second; WriteVariable(extraKey, Quote(extraVal), 1); } WriteVariable("Executable", Quote(compilerPath), 1); WriteVariable("CompilerFamily", Quote(compilerDef.CompilerFamily), 1); if (compilerDef.UseLightCache && compilerDef.CompilerFamily == "msvc") { WriteVariable("UseLightCache_Experimental", "true", 1); } if (compilerDef.UseRelativePaths) { WriteVariable("UseRelativePaths_Experimental", "true", 1); } if (compilerDef.UseDeterministicPaths) { WriteVariable("UseDeterministicPaths_Experimental", "true", 1); } if (!compilerDef.SourceMapping.empty()) { WriteVariable("SourceMapping_Experimental", Quote(compilerDef.SourceMapping), 1); } auto const isClang = [&compilerDef] { return compilerDef.CompilerFamily == "clang" || compilerDef.CompilerFamily == "clang-cl"; }; if (!compilerDef.ClangRewriteIncludes && isClang()) { WriteVariable("ClangRewriteIncludes", "false", 1); } if (compilerDef.ClangGCCUpdateXLanguageArg && (isClang() || compilerDef.CompilerFamily == "gcc")) { WriteVariable("ClangGCCUpdateXLanguageArg", "true", 1); } if (compilerDef.AllowResponseFile) { WriteVariable("AllowResponseFile", "true", 1); } if (compilerDef.ForceResponseFile) { WriteVariable("ForceResponseFile", "true", 1); } if (compilerDef.DontUseEnv) { LogMessage("Not using system environment"); } else { if (!LocalEnvironment.empty()) { WriteVariable("Environment", "." FASTBUILD_ENV_VAR_NAME, 1); } } if (!compilerDef.ExtraFiles.empty()) { // Do not escape '$' sign, CMAKE_${LANG}_FASTBUILD_EXTRA_FILES might // contain FB variables to be expanded (we do use some internally). // Besides a path cannot contain a '$' WriteArray("ExtraFiles", Wrap(compilerDef.ExtraFiles, "'", "'", false), 1); } *this->BuildFileStream << "}\n"; auto const compilerId = compilerDef.Name; WriteVariable(compilerId, Quote(compilerDef.Name)); *this->BuildFileStream << "\n"; } // We need this because the Library command needs a compiler // even if don't compile anything if (!this->Compilers.empty()) { WriteVariable("Compiler_dummy", Quote(this->Compilers.begin()->second.Name)); } } void cmGlobalFastbuildGenerator::AddCompiler(std::string const& language, cmMakefile* mf) { if (this->Compilers.find(FASTBUILD_COMPILER_PREFIX + language) != this->Compilers.end()) { return; } // Calculate the root location of the compiler std::string const variableString = "CMAKE_" + language + "_COMPILER"; std::string const compilerLocation = mf->GetSafeDefinition(variableString); if (compilerLocation.empty()) { return; } // Calculate the i18n number. std::string i18nNum = "1033"; // Add the language to the compiler's name FastbuildCompiler compilerDef; compilerDef.ExtraVariables["Root"] = cmSystemTools::GetFilenamePath(compilerLocation); compilerDef.Name = FASTBUILD_COMPILER_PREFIX + language; compilerDef.Executable = compilerLocation; compilerDef.CmakeCompilerID = mf->GetSafeDefinition("CMAKE_" + language + "_COMPILER_ID"); if (compilerDef.CmakeCompilerID == "Clang" && mf->GetSafeDefinition("CMAKE_" + language + "_COMPILER_FRONTEND_VARIANT") == "MSVC") { compilerDef.CmakeCompilerID = "Clang-cl"; } compilerDef.CmakeCompilerVersion = mf->GetSafeDefinition("CMAKE_" + language + "_COMPILER_VERSION"); compilerDef.Language = language; cmExpandList(mf->GetSafeDefinition(FASTBUILD_COMPILER_EXTRA_FILES), compilerDef.ExtraFiles); if (supportedLanguages.find(language) != supportedLanguages.end()) { auto const iter = compilerIdToFastbuildFamily.find(compilerDef.CmakeCompilerID); if (iter != compilerIdToFastbuildFamily.end()) { compilerDef.CompilerFamily = iter->second; } } // Has to be called after we determined 'CompilerFamily'. ReadCompilerOptions(compilerDef, mf); // If FASTBUILD_COMPILER_EXTRA_FILES is not set - automatically add extra // files based on compiler (see // https://fastbuild.org/docs/functions/compiler.html) if (compilerDef.ExtraFiles.empty() && (language == "C" || language == "CXX") && compilerDef.CmakeCompilerID == "MSVC") { // https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html // Visual Studio 17 (19.30 to 19.39) // TODO // Visual Studio 16 (19.20 to 19.29) if (cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL, compilerDef.CmakeCompilerVersion, "19.20")) { compilerDef.ExtraFiles.push_back("$Root$/c1.dll"); compilerDef.ExtraFiles.push_back("$Root$/c1xx.dll"); compilerDef.ExtraFiles.push_back("$Root$/c2.dll"); compilerDef.ExtraFiles.push_back( "$Root$/atlprov.dll"); // Only needed if using ATL compilerDef.ExtraFiles.push_back("$Root$/msobj140.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdb140.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdbcore.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdbsrv.exe"); compilerDef.ExtraFiles.push_back("$Root$/mspft140.dll"); compilerDef.ExtraFiles.push_back("$Root$/msvcp140.dll"); compilerDef.ExtraFiles.push_back( "$Root$/msvcp140_atomic_wait.dll"); // Required circa 16.8.3 // (14.28.29333) compilerDef.ExtraFiles.push_back( "$Root$/tbbmalloc.dll"); // Required as of 16.2 (14.22.27905) compilerDef.ExtraFiles.push_back("$Root$/vcruntime140.dll"); compilerDef.ExtraFiles.push_back( "$Root$/vcruntime140_1.dll"); // Required as of 16.5.1 (14.25.28610) compilerDef.ExtraFiles.push_back("$Root$/" + i18nNum + "/clui.dll"); compilerDef.ExtraFiles.push_back( "$Root$/" + i18nNum + "/mspft140ui.dll"); // Localized messages for // static analysis } // Visual Studio 15 (19.10 to 19.19) else if (cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL, compilerDef.CmakeCompilerVersion, "19.10")) { compilerDef.ExtraFiles.push_back("$Root$/c1.dll"); compilerDef.ExtraFiles.push_back("$Root$/c1xx.dll"); compilerDef.ExtraFiles.push_back("$Root$/c2.dll"); compilerDef.ExtraFiles.push_back( "$Root$/atlprov.dll"); // Only needed if using ATL compilerDef.ExtraFiles.push_back("$Root$/msobj140.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdb140.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdbcore.dll"); compilerDef.ExtraFiles.push_back("$Root$/mspdbsrv.exe"); compilerDef.ExtraFiles.push_back("$Root$/mspft140.dll"); compilerDef.ExtraFiles.push_back("$Root$/msvcp140.dll"); compilerDef.ExtraFiles.push_back("$Root$/vcruntime140.dll"); compilerDef.ExtraFiles.push_back("$Root$/" + i18nNum + "/clui.dll"); } } // TODO: Handle Intel compiler this->Compilers[compilerDef.Name] = std::move(compilerDef); } void cmGlobalFastbuildGenerator::AddLauncher(std::string const& prefix, std::string const& launcher, std::string const& language, std::string const& args) { if (this->Compilers.find(prefix + language) != this->Compilers.end()) { return; } LogMessage("Launcher: " + launcher); LogMessage("Launcher args: " + args); FastbuildCompiler compilerDef; compilerDef.Name = prefix + language; compilerDef.Args = args; if (cmSystemTools::FileIsFullPath(launcher)) { compilerDef.Executable = launcher; } else { // FASTBuild needs an absolute path to the executable. compilerDef.Executable = cmSystemTools::FindProgram(launcher); if (compilerDef.Executable.empty()) { cmSystemTools::Error("Failed to find path to " + launcher); return; } } // When CTest is used as a launcher, there is an interesting env variable // "CTEST_LAUNCH_LOGS" which is set by parent CTest process and is expected // to be read from global (sic!) env by the launched CTest process. So we // will need to make this global env available for CTest executable used as a // "launcher". Tested in RunCMake.ctest_labels_for_subprojects test.. compilerDef.DontUseEnv = true; this->Compilers[compilerDef.Name] = std::move(compilerDef); } std::string cmGlobalFastbuildGenerator::ConvertToFastbuildPath( std::string const& path) const { cmLocalGenerator const* root = LocalGenerators[0].get(); return root->MaybeRelativeToWorkDir(cmSystemTools::FileIsFullPath(path) ? cmSystemTools::CollapseFullPath(path) : path); } std::unique_ptr cmGlobalFastbuildGenerator::CreateLinkLineComputer( cmOutputConverter* outputConverter, cmStateDirectory const& /* stateDir */) const { return cm::make_unique( outputConverter, this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this); } void cmGlobalFastbuildGenerator::WriteExec(FastbuildExecNode const& Exec, int indent) { auto const identPlus1 = indent + 1; WriteCommand("Exec", Exec.Name.empty() ? std::string{} : Quote(Exec.Name), indent); Indent(indent); *BuildFileStream << "{\n"; { if (!Exec.PreBuildDependencies.empty()) { WriteArray("PreBuildDependencies", Wrap(Exec.PreBuildDependencies), identPlus1); } WriteVariable("ExecExecutable", Quote(Exec.ExecExecutable), identPlus1); if (!Exec.ExecArguments.empty()) { WriteVariable("ExecArguments", Quote(Exec.ExecArguments), identPlus1); } if (!Exec.ExecWorkingDir.empty()) { WriteVariable("ExecWorkingDir", Quote(Exec.ExecWorkingDir), identPlus1); } if (!Exec.ExecInput.empty()) { WriteArray("ExecInput", Wrap(Exec.ExecInput), identPlus1); } if (Exec.ExecUseStdOutAsOutput) { WriteVariable("ExecUseStdOutAsOutput", "true", identPlus1); } if (!Exec.ExecInputPath.empty()) { WriteArray("ExecInputPath", Wrap(Exec.ExecInputPath), identPlus1); } if (!Exec.ExecInputPattern.empty()) { WriteArray("ExecInputPattern", Wrap(Exec.ExecInputPattern), identPlus1); } WriteVariable("ExecAlwaysShowOutput", "true", identPlus1); WriteVariable("ExecOutput", Quote(Exec.ExecOutput), identPlus1); WriteVariable("ExecAlways", Exec.ExecAlways ? "true" : "false", identPlus1); if (!Exec.ConcurrencyGroupName.empty()) { WriteVariable("ConcurrencyGroupName", Quote(Exec.ConcurrencyGroupName), identPlus1); } } Indent(indent); *BuildFileStream << "}\n"; static bool const verbose = GlobalSettingIsOn(FASTBUILD_VERBOSE_GENERATOR) || cmSystemTools::HasEnv(FASTBUILD_VERBOSE_GENERATOR); // Those aliases are only used for troubleshooting the generated file. if (verbose) { WriteAlias(Exec.OutputsAlias); WriteAlias(Exec.ByproductsAlias); } } void cmGlobalFastbuildGenerator::WriteUnity(FastbuildUnityNode const& Unity) { WriteCommand("Unity", Quote(Unity.Name), 1); Indent(1); *BuildFileStream << "{\n"; { WriteVariable("UnityOutputPath", Quote(Unity.UnityOutputPath), 2); WriteVariable("UnityOutputPattern", Quote(Unity.UnityOutputPattern), 2); WriteArray("UnityInputFiles", Wrap(Unity.UnityInputFiles), 2); if (!Unity.UnityInputIsolatedFiles.empty()) { WriteArray("UnityInputIsolatedFiles", Wrap(Unity.UnityInputIsolatedFiles), 2); } if (UsingRelativePaths) { WriteVariable("UseRelativePaths_Experimental", "true", 2); } } Indent(1); *BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteObjectList( FastbuildObjectListNode const& ObjectList, bool allowDistribution) { WriteCommand("ObjectList", Quote(ObjectList.Name), 1); Indent(1); *BuildFileStream << "{\n"; { if (!allowDistribution) { WriteVariable("AllowDistribution", "false", 2); } if (!ObjectList.PreBuildDependencies.empty()) { WriteArray("PreBuildDependencies", Wrap(ObjectList.PreBuildDependencies), 2); } WriteVariable("Compiler", ObjectList.Compiler, 2); // If only PCH output is present - this node reuses existing PCH. if (!ObjectList.PCHOutputFile.empty()) { WriteVariable("PCHOutputFile", Quote(ObjectList.PCHOutputFile), 2); } // If PCHInputFile and PCHOptions are present - this node creates PCH. if (!ObjectList.PCHInputFile.empty() && !ObjectList.PCHOptions.empty()) { WriteVariable("PCHInputFile", Quote(ObjectList.PCHInputFile), 2); WriteVariable("PCHOptions", Quote(ObjectList.PCHOptions), 2); } WriteVariable("CompilerOptions", Quote(ObjectList.CompilerOptions), 2); WriteVariable("CompilerOutputPath", Quote(ObjectList.CompilerOutputPath), 2); WriteVariable("CompilerOutputExtension", Quote(ObjectList.CompilerOutputExtension), 2); WriteVariable("CompilerOutputKeepBaseExtension", "true", 2); if (!ObjectList.CompilerInputUnity.empty()) { WriteArray("CompilerInputUnity", Wrap(ObjectList.CompilerInputUnity), 2); } if (!ObjectList.CompilerInputFiles.empty()) { WriteArray("CompilerInputFiles", Wrap(ObjectList.CompilerInputFiles), 2); } if (!ObjectList.AllowCaching) { WriteVariable("AllowCaching", "false", 2); } if (!ObjectList.AllowDistribution) { WriteVariable("AllowDistribution", "false", 2); } if (ObjectList.Hidden) { WriteVariable("Hidden", "true", 2); } } Indent(1); *BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteLinker( FastbuildLinkerNode const& LinkerNode, bool allowDistribution) { WriteCommand( LinkerNode.Type == FastbuildLinkerNode::EXECUTABLE ? "Executable" : LinkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY ? "DLL" : "Library", (!LinkerNode.Name.empty() && LinkerNode.Name != LinkerNode.LinkerOutput) ? Quote(LinkerNode.Name) : "", 1); Indent(1); *BuildFileStream << "{\n"; { if (!LinkerNode.PreBuildDependencies.empty()) { WriteArray("PreBuildDependencies", Wrap(LinkerNode.PreBuildDependencies), 2); } if (!allowDistribution) { WriteVariable("AllowDistribution", "false", 2); } if (!LinkerNode.Compiler.empty() && LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY) { WriteVariable("Compiler", LinkerNode.Compiler, 2); WriteVariable("CompilerOptions", Quote(LinkerNode.CompilerOptions), 2); WriteVariable("CompilerOutputPath", Quote("."), 2); } if (!LocalEnvironment.empty()) { WriteVariable("Environment", "." FASTBUILD_ENV_VAR_NAME, 2); } WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY ? "Librarian" : "Linker", Quote(LinkerNode.Linker), 2); WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY ? "LibrarianOptions" : "LinkerOptions", Quote(LinkerNode.LinkerOptions), 2); WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY ? "LibrarianOutput" : "LinkerOutput", Quote(LinkerNode.LinkerOutput), 2); if (!LinkerNode.LibrarianAdditionalInputs.empty()) { WriteArray(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY ? "LibrarianAdditionalInputs" : "Libraries", Wrap(LinkerNode.LibrarianAdditionalInputs), 2); } if (!LinkerNode.Libraries2.empty()) { WriteArray("Libraries2", Wrap(LinkerNode.Libraries2), 2); } if (!LinkerNode.LibrarianAdditionalInputs.empty()) { if (!LinkerNode.LinkerType.empty()) { WriteVariable("LinkerType", Quote(LinkerNode.LinkerType), 2); } } if (LinkerNode.Type == FastbuildLinkerNode::EXECUTABLE || LinkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY) { WriteVariable("LinkerLinkObjects", LinkerNode.LinkerLinkObjects ? "true" : "false", 2); if (!LinkerNode.LinkerStampExe.empty()) { WriteVariable("LinkerStampExe", Quote(LinkerNode.LinkerStampExe), 2); if (!LinkerNode.LinkerStampExeArgs.empty()) { WriteVariable("LinkerStampExeArgs", Quote(LinkerNode.LinkerStampExeArgs), 2); } } } Indent(1); *BuildFileStream << "}\n"; } } void cmGlobalFastbuildGenerator::WriteAlias(FastbuildAliasNode const& Alias, int indent) { if (Alias.PreBuildDependencies.empty()) { return; } auto const identPlus1 = indent + 1; WriteCommand("Alias", Quote(Alias.Name), indent); Indent(indent); *BuildFileStream << "{\n"; WriteArray("Targets", Wrap(Alias.PreBuildDependencies), identPlus1); if (Alias.Hidden) { WriteVariable("Hidden", "true", identPlus1); } Indent(indent); *BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteCopy(FastbuildCopyNode const& Copy) { cmGlobalFastbuildGenerator::WriteCommand( Copy.CopyDir ? "CopyDir" : "Copy", cmGlobalFastbuildGenerator::Quote(Copy.Name), 1); cmGlobalFastbuildGenerator::Indent(1); *BuildFileStream << "{\n"; WriteVariable("PreBuildDependencies", cmGlobalFastbuildGenerator::Quote(Copy.PreBuildDependencies), 2); WriteVariable(Copy.CopyDir ? "SourcePaths" : "Source", cmGlobalFastbuildGenerator::Quote(Copy.Source), 2); WriteVariable("Dest", cmGlobalFastbuildGenerator::Quote(Copy.Dest), 2); cmGlobalFastbuildGenerator::Indent(1); *BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteTarget(FastbuildTarget const& target) { for (auto const& val : target.Variables) { auto const& key = val.first; auto const& value = val.second; WriteVariable(key, cmGlobalFastbuildGenerator::Quote(value), 1); } // add_custom_commands(...) for (auto const& alias : { target.ExecNodes }) { this->WriteAlias(alias); } // -deps Alias. this->WriteAlias(target.DependenciesAlias); // PRE_BUILD. for (auto const& alias : { target.PreBuildExecNodes }) { this->WriteAlias(alias); } // Copy commands. for (FastbuildCopyNode const& node : target.CopyNodes) { this->WriteCopy(node); } // Unity. for (FastbuildUnityNode const& unity : target.UnityNodes) { this->WriteUnity(unity); } // Objects. for (FastbuildObjectListNode const& objectList : target.ObjectListNodes) { this->WriteObjectList(objectList, target.AllowDistribution); } if (!target.PreLinkExecNodes.Nodes.empty()) { for (auto const& exec : target.PreLinkExecNodes.Nodes) { this->WriteExec(exec); } this->WriteAlias(target.PreLinkExecNodes.Alias); } // Libraries / executables. if (!target.LinkerNode.empty()) { for (auto const& linkerNode : target.LinkerNode) { this->WriteLinker(linkerNode, target.AllowDistribution); } } if (!target.PostBuildExecNodes.Nodes.empty()) { for (auto const& exec : target.PostBuildExecNodes.Nodes) { this->WriteExec(exec); } this->WriteAlias(target.PostBuildExecNodes.Alias); } // Aliases (if any). for (FastbuildAliasNode const& alias : target.AliasNodes) { this->WriteAlias(alias); } } void cmGlobalFastbuildGenerator::WriteIDEProjects() { for (auto const& proj : IDEProjects) { (void)proj; // VS #if defined(_WIN32) auto const& VSProj = proj.second.first; WriteCommand("VCXProject", Quote(VSProj.Alias)); *this->BuildFileStream << "{\n"; WriteVariable("ProjectOutput", Quote(VSProj.ProjectOutput), 1); WriteIDEProjectConfig(VSProj.ProjectConfigs); WriteVSBuildCommands(); WriteIDEProjectCommon(VSProj); *this->BuildFileStream << "}\n\n"; // XCode #elif defined(__APPLE__) auto const& XCodeProj = proj.second.second; WriteCommand("XCodeProject", Quote(XCodeProj.Alias), 0); *this->BuildFileStream << "{\n"; WriteVariable("ProjectOutput", Quote(XCodeProj.ProjectOutput), 1); WriteIDEProjectConfig(XCodeProj.ProjectConfigs); WriteXCodeBuildCommands(); WriteIDEProjectCommon(XCodeProj); *this->BuildFileStream << "}\n\n"; #endif } #if defined(_WIN32) this->WriteSolution(); #elif defined(__APPLE__) this->WriteXCodeTopLevelProject(); #endif } void cmGlobalFastbuildGenerator::WriteVSBuildCommands() { WriteVariable("ProjectBuildCommand", Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX + this->FastbuildCommand + FASTBUILD_IDE_BUILD_ARGS " ^$(ProjectName)"), 1); WriteVariable("ProjectRebuildCommand", Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX + this->FastbuildCommand + FASTBUILD_IDE_BUILD_ARGS "-clean ^$(ProjectName)"), 1); WriteVariable("ProjectCleanCommand", Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX + this->FastbuildCommand + " -ide clean"), 1); } void cmGlobalFastbuildGenerator::WriteXCodeBuildCommands() { WriteVariable("XCodeBuildToolPath", Quote(this->FastbuildCommand), 1); WriteVariable("XCodeBuildToolArgs", Quote(FASTBUILD_IDE_BUILD_ARGS "^$(FASTBUILD_TARGET)"), 1); WriteVariable("XCodeBuildWorkingDir", Quote(this->CMakeInstance->GetHomeOutputDirectory()), 1); } void cmGlobalFastbuildGenerator::WriteIDEProjectCommon( IDEProjectCommon const& project) { WriteVariable("ProjectBasePath", Quote(project.ProjectBasePath), 1); // So Fastbuild will pick up files relative to CMakeLists.txt WriteVariable("ProjectInputPaths", Quote(project.ProjectBasePath), 1); } void cmGlobalFastbuildGenerator::WriteIDEProjectConfig( std::vector const& configs, std::string const& keyName) { std::vector allConfigVariables; for (auto const& config : configs) { std::string configName = "Config" + config.Config; WriteVariable(configName, "", 1); Indent(1); *this->BuildFileStream << "[\n"; WriteVariable("Config", Quote(config.Config), 2); if (!config.Target.empty()) { WriteVariable("Target", Quote(config.Target), 2); } if (!config.Platform.empty()) { WriteVariable("Platform", Quote(config.Platform), 2); } Indent(1); *this->BuildFileStream << "]\n"; allConfigVariables.emplace_back(std::move(configName)); } WriteArray(keyName, Wrap(allConfigVariables, ".", ""), 1); } void cmGlobalFastbuildGenerator::AddTargetAll() { FastbuildAliasNode allAliasNode; allAliasNode.Name = FASTBUILD_ALL_TARGET_NAME; for (auto const& targetBase : FastbuildTargets) { if (targetBase->Type == FastbuildTargetType::LINK) { auto const& target = static_cast(*targetBase); // Add non-global and non-excluded targets to "all" if (!target.IsGlobal && !target.ExcludeFromAll) { allAliasNode.PreBuildDependencies.emplace(target.Name); } } else if (targetBase->Type == FastbuildTargetType::ALIAS) { auto const& target = static_cast(*targetBase); if (!target.ExcludeFromAll) { allAliasNode.PreBuildDependencies.emplace(target.Name); } } } if (allAliasNode.PreBuildDependencies.empty()) { allAliasNode.PreBuildDependencies.emplace(FASTBUILD_NOOP_FILE_NAME); } this->AddTarget(std::move(allAliasNode)); } void cmGlobalFastbuildGenerator::AddGlobCheckExec() { // Tested in "RunCMake.file" test. std::string const globScript = this->GetCMakeInstance()->GetGlobVerifyScript(); if (!globScript.empty()) { FastbuildExecNode globCheck; globCheck.Name = FASTBUILD_GLOB_CHECK_TARGET; globCheck.ExecExecutable = cmSystemTools::GetCMakeCommand(); globCheck.ExecArguments = "-P " FASTBUILD_1_INPUT_PLACEHOLDER; globCheck.ExecInput = { this->ConvertToFastbuildPath(globScript) }; globCheck.ExecAlways = false; globCheck.ExecUseStdOutAsOutput = false; auto const cache = this->GetCMakeInstance()->GetGlobCacheEntries(); for (auto const& entry : cache) { auto path = cmSystemTools::GetFilenamePath(entry.Expression); auto expression = cmSystemTools::GetFilenameName(entry.Expression); if (std::find(globCheck.ExecInputPath.begin(), globCheck.ExecInputPath.end(), path) == globCheck.ExecInputPath.end()) { globCheck.ExecInputPath.emplace_back(std::move(path)); } if (std::find(globCheck.ExecInputPattern.begin(), globCheck.ExecInputPattern.end(), expression) == globCheck.ExecInputPattern.end()) { globCheck.ExecInputPattern.emplace_back(std::move(expression)); } } globCheck.ExecOutput = this->ConvertToFastbuildPath( this->GetCMakeInstance()->GetGlobVerifyStamp()); this->AddTarget(std::move(globCheck)); } } void cmGlobalFastbuildGenerator::WriteSolution() { std::string const solutionName = LocalGenerators[0]->GetProjectName(); std::map> VSProjects; std::vector VSProjectsWithoutFolder; for (auto const& IDEProj : IDEProjects) { auto const VSProj = IDEProj.second.first; VSProjects[VSProj.folder].emplace_back(VSProj.Alias); } WriteCommand("VSSolution", Quote("solution")); *this->BuildFileStream << "{\n"; WriteVariable("SolutionOutput", Quote(cmJoin({ "VisualStudio", solutionName + ".sln" }, "/")), 1); auto const& configs = IDEProjects.begin()->second.first.ProjectConfigs; WriteIDEProjectConfig(configs, "SolutionConfigs"); int folderNumber = 0; std::vector folders; for (auto& item : VSProjects) { auto const& pathToFolder = item.first; auto& projectsInFolder = item.second; if (pathToFolder.empty()) { std::move(projectsInFolder.begin(), projectsInFolder.end(), std::back_inserter(VSProjectsWithoutFolder)); } else { std::string folderName = cmStrCat("Folder_", ++folderNumber); WriteStruct( folderName, { { "Path", Quote(pathToFolder) }, { "Projects", cmStrCat("{", cmJoin(Wrap(projectsInFolder), ","), "}") } }, 1); folders.emplace_back(std::move(folderName)); } } if (!folders.empty()) { WriteArray("SolutionFolders ", Wrap(folders, ".", ""), 1); } if (!VSProjectsWithoutFolder.empty()) { WriteArray("SolutionProjects", Wrap(VSProjectsWithoutFolder), 1); } *this->BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::WriteXCodeTopLevelProject() { std::string const projectName = LocalGenerators[0]->GetProjectName(); std::vector XCodeProjects; for (auto const& IDEProj : IDEProjects) { auto const XCodeProj = IDEProj.second.second; XCodeProjects.emplace_back(XCodeProj.Alias); } WriteCommand("XCodeProject", Quote("xcode")); *this->BuildFileStream << "{\n"; WriteVariable( "ProjectOutput", Quote( cmJoin({ "XCode", projectName + ".xcodeproj", "project.pbxproj" }, "/")), 1); WriteVariable("ProjectBasePath", Quote(FASTBUILD_XCODE_BASE_PATH), 1); auto const& configs = IDEProjects.begin()->second.second.ProjectConfigs; WriteIDEProjectConfig(configs); WriteArray("ProjectFiles", Wrap(XCodeProjects), 1); *this->BuildFileStream << "}\n"; } void cmGlobalFastbuildGenerator::LogMessage(std::string const& m) const { static bool const verbose = GlobalSettingIsOn(FASTBUILD_VERBOSE_GENERATOR) || cmSystemTools::HasEnv(FASTBUILD_VERBOSE_GENERATOR); if (verbose) { cmSystemTools::Message(m); } } void cmGlobalFastbuildGenerator::AddFileToClean(std::string const& file) { AllFilesToClean.insert(file); } std::string cmGlobalFastbuildGenerator::GetExternalShellExecutable() { // FindProgram is expensive - touches filesystem and makes syscalls, so cache // it. static std::string const cached = #ifdef _WIN32 cmSystemTools::FindProgram( "cmd.exe", std::vector{ "C:\\Windows\\System32" }); #else cmSystemTools::FindProgram("sh", std::vector{ "/bin" }); #endif return cached; } void cmGlobalFastbuildGenerator::WriteTargetRebuildBFF() { std::vector implicitDeps; for (auto& lg : LocalGenerators) { std::vector const& lf = lg->GetMakefile()->GetListFiles(); for (auto const& dep : lf) { implicitDeps.push_back(this->ConvertToFastbuildPath(dep)); } } auto const* cmake = this->GetCMakeInstance(); std::string outDir = cmake->GetHomeOutputDirectory() + '/'; implicitDeps.push_back(outDir + "CMakeCache.txt"); FastbuildExecNode rebuildBFF; rebuildBFF.Name = FASTBUILD_REBUILD_BFF_TARGET_NAME; if (!this->GetCMakeInstance()->GetGlobVerifyScript().empty()) { implicitDeps.emplace_back(this->GetCMakeInstance()->GetGlobVerifyStamp()); } std::sort(implicitDeps.begin(), implicitDeps.end()); implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()), implicitDeps.end()); std::string args = cmStrCat("--regenerate-during-build", (this->GetCMakeInstance()->GetIgnoreCompileWarningAsError() ? " --compile-no-warning-as-error" : ""), (this->GetCMakeInstance()->GetIgnoreLinkWarningAsError() ? " --link-no-warning-as-error" : ""), " -S", QuoteIfHasSpaces(cmake->GetHomeDirectory()), " -B", QuoteIfHasSpaces(cmake->GetHomeOutputDirectory())); rebuildBFF.ExecArguments = std::move(args); rebuildBFF.ExecInput = implicitDeps; rebuildBFF.ExecExecutable = cmSystemTools::GetCMakeCommand(); rebuildBFF.ExecWorkingDir = outDir; rebuildBFF.ExecOutput = outDir + FASTBUILD_BUILD_FILE; this->WriteExec(rebuildBFF, 0); } void cmGlobalFastbuildGenerator::WriteCleanScript() { std::string const path = cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', FASTBUILD_CLEAN_SCRIPT_NAME); cmsys::ofstream scriptFile(path.c_str(), std::ios::out | std::ios::binary); if (!scriptFile.is_open()) { cmSystemTools::Error("Failed to open: " FASTBUILD_CLEAN_SCRIPT_NAME); return; } for (std::string const& file : AllFilesToClean) { #if defined(_WIN32) scriptFile << "del /f /q " << cmSystemTools::ConvertToWindowsOutputPath(file) << "\n"; #else scriptFile << "rm -f " << file << '\n'; #endif } } void cmGlobalFastbuildGenerator::WriteTargetClean() { if (AllFilesToClean.empty()) { FastbuildAliasNode clean; clean.Name = FASTBUILD_CLEAN_TARGET_NAME; clean.PreBuildDependencies.emplace(FASTBUILD_CLEAN_FILE_NAME); WriteAlias(clean, 0); return; } WriteCleanScript(); FastbuildExecNode clean; clean.Name = FASTBUILD_CLEAN_TARGET_NAME; clean.ExecExecutable = GetExternalShellExecutable(); clean.ExecArguments = FASTBUILD_SCRIPT_FILE_ARG FASTBUILD_1_INPUT_PLACEHOLDER; clean.ExecInput = { FASTBUILD_CLEAN_SCRIPT_NAME }; clean.ExecAlways = true; clean.ExecUseStdOutAsOutput = true; clean.ExecOutput = FASTBUILD_CLEAN_FILE_NAME; clean.ExecWorkingDir = this->GetCMakeInstance()->GetHomeOutputDirectory(); WriteExec(clean, 0); } void cmGlobalFastbuildGenerator::WriteTargets() { std::string const outputDir = this->CMakeInstance->GetHomeOutputDirectory(); LogMessage("GetHomeOutputDirectory: " + outputDir); // Noop file that 'all' can alias to if we don't have any other targets... // The exact location of the "noop" file is verified in one of the tests in // "RunCMake.CMakePresetsPackage" test suite. cmSystemTools::Touch(cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), '/', FASTBUILD_NOOP_FILE_NAME), true); cmSystemTools::Touch(cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), '/', FASTBUILD_CLEAN_FILE_NAME), true); // Add "all" utility target before sorting, so we can correctly sort // targets that depend on it AddTargetAll(); TopologicalSort(FastbuildTargets); AddGlobCheckExec(); for (auto const& targetBase : FastbuildTargets) { this->WriteComment("Target definition: " + targetBase->Name); // Target start. *BuildFileStream << "{\n"; if (targetBase->Type == FastbuildTargetType::EXEC) { this->WriteExec(static_cast(*targetBase)); } else if (targetBase->Type == FastbuildTargetType::ALIAS) { this->WriteAlias(static_cast(*targetBase)); } else if (targetBase->Type == FastbuildTargetType::LINK) { auto const& target = static_cast(*targetBase); this->WriteTarget(target); } // Target end. *BuildFileStream << "}\n"; } if (!this->GetCMakeInstance()->GetIsInTryCompile()) { if (!IDEProjects.empty()) { this->WriteIDEProjects(); } } this->WriteTargetClean(); this->WriteTargetRebuildBFF(); } std::string cmGlobalFastbuildGenerator::GetTargetName( cmGeneratorTarget const* GeneratorTarget) const { std::string targetName = GeneratorTarget->GetLocalGenerator()->GetCurrentBinaryDirectory(); targetName += "/"; targetName += GeneratorTarget->GetName(); targetName = this->ConvertToFastbuildPath(targetName); return targetName; } cm::optional cmGlobalFastbuildGenerator::GetTargetByOutputName( std::string const& output) const { for (auto const& targetBase : FastbuildTargets) { if (targetBase->Type == FastbuildTargetType::LINK) { auto const& target = static_cast(*targetBase); if (std::any_of(target.LinkerNode.begin(), target.LinkerNode.end(), [&output](FastbuildLinkerNode const& target_) { return target_.LinkerOutput == output; })) { return target; } } } return cm::nullopt; } void cmGlobalFastbuildGenerator::AddIDEProject(FastbuildTarget const& target, std::string const& config) { auto const& configs = GetConfigNames(); if (std::find(configs.begin(), configs.end(), config) == configs.end()) { LogMessage("Config " + config + " doesn't exist, IDE projest for " + target.Name + " won't be generated"); return; } auto& IDEProject = IDEProjects[target.BaseName]; auto const relativeSubdir = cmSystemTools::RelativePath( this->GetCMakeInstance()->GetHomeDirectory(), target.BasePath); // VS auto& VSProject = IDEProject.first; VSProject.Alias = target.BaseName + "-vcxproj"; VSProject.ProjectOutput = cmStrCat("VisualStudio/Projects/", relativeSubdir, '/', target.BaseName + ".vcxproj"); VSProject.ProjectBasePath = target.BasePath; VSProject.folder = relativeSubdir; // XCode auto& XCodeProject = IDEProject.second; XCodeProject.Alias = target.BaseName + "-xcodeproj"; XCodeProject.ProjectOutput = cmStrCat("XCode/Projects/", relativeSubdir, '/', target.BaseName + ".xcodeproj/project.pbxproj"); XCodeProject.ProjectBasePath = target.BasePath; IDEProjectConfig VSConfig; VSConfig.Platform = "X64"; IDEProjectConfig XCodeConfig; VSConfig.Target = XCodeConfig.Target = target.Name; VSConfig.Config = XCodeConfig.Config = config.empty() ? "DEFAULT" : config; VSProject.ProjectConfigs.emplace_back(std::move(VSConfig)); XCodeProject.ProjectConfigs.emplace_back(std::move(XCodeConfig)); } bool cmGlobalFastbuildGenerator::IsExcluded(cmGeneratorTarget* target) { return cmGlobalGenerator::IsExcluded(LocalGenerators[0].get(), target); } std::vector const& cmGlobalFastbuildGenerator::GetConfigNames() const { return static_cast( this->LocalGenerators.front().get()) ->GetConfigNames(); } bool cmGlobalFastbuildGenerator::Open(std::string const& bindir, std::string const& projectName, bool dryRun) { #ifdef _WIN32 std::string sln = bindir + "/VisualStudio/" + projectName + ".sln"; if (dryRun) { return cmSystemTools::FileExists(sln, true); } sln = cmSystemTools::ConvertToOutputPath(sln); auto OpenSolution = [](std::string pathToSolution) { HRESULT comInitialized = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (FAILED(comInitialized)) { return false; } HINSTANCE hi = ShellExecuteA(NULL, "open", pathToSolution.c_str(), NULL, NULL, SW_SHOWNORMAL); CoUninitialize(); return reinterpret_cast(hi) > 32; }; return std::async(std::launch::async, OpenSolution, sln).get(); #else return cmGlobalCommonGenerator::Open(bindir, projectName, dryRun); #endif }