blob: b652d598add8f559e551e98e3f29ea019cd8b34f [file] [log] [blame] [edit]
/* 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 <algorithm>
#include <cstdlib>
#include <initializer_list>
#include <iterator>
#include <queue>
#include <sstream>
#include <cm/memory>
#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 <future>
# include <objbase.h>
# include <shellapi.h>
#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<std::string, std::string> const compilerIdToFastbuildFamily = {
{ "MSVC", "msvc" }, { "Clang", "clang" }, { "AppleClang", "clang" },
{ "GNU", "gcc" }, { "NVIDIA", "cuda-nvcc" }, { "Clang-cl", "clang-cl" },
};
static std::set<std::string> const supportedLanguages = { "C", "CXX", "CUDA",
"OBJC", "OBJCXX" };
template <class T>
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<cmGlobalGeneratorFactory>
cmGlobalFastbuildGenerator::NewFactory()
{
return std::unique_ptr<cmGlobalGeneratorFactory>(
new cmGlobalGeneratorSimpleFactory<cmGlobalFastbuildGenerator>());
}
void cmGlobalFastbuildGenerator::EnableLanguage(
std::vector<std::string> 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<std::string> 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<cmLocalGenerator>
cmGlobalFastbuildGenerator::CreateLocalGenerator(cmMakefile* makefile)
{
return std::unique_ptr<cmLocalGenerator>(
cm::make_unique<cmLocalFastbuildGenerator>(this, makefile));
}
std::vector<cmGlobalGenerator::GeneratedMakeCommand>
cmGlobalFastbuildGenerator::GenerateBuildCommand(
std::string const& makeProgram, std::string const& /*projectName*/,
std::string const& projectDir, std::vector<std::string> const& targetNames,
std::string const& /*config*/, int /*jobs*/, bool verbose,
cmBuildOptions /*buildOptions*/, std::vector<std::string> 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] <target>
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<std::string> const& fbuildOptions) const
{
std::vector<std::string> 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<std::string> const& values, int indent)
{
WriteArray(key, values, "=", indent);
}
void cmGlobalFastbuildGenerator::WriteArray(
std::string const& key, std::vector<std::string> 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<std::pair<std::string, std::string>> 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 <class T>
std::vector<std::string> cmGlobalFastbuildGenerator::Wrap(
T const& in, std::string const& prefix, std::string const& suffix,
bool const escape_dollar)
{
std::vector<std::string> 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<FastbuildTargetPtrT>& nodes)
{
std::unordered_map<std::string, int> inDegree;
std::unordered_map<std::string, std::set<std::string>> reverseDeps;
std::unordered_map<std::string, std::size_t> 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<std::string, std::vector<std::string>, 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<std::string> 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<FastbuildTargetPtrT> 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<cmGeneratedFileStream>(
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<cmLinkLineComputer>
cmGlobalFastbuildGenerator::CreateLinkLineComputer(
cmOutputConverter* outputConverter,
cmStateDirectory const& /* stateDir */) const
{
return cm::make_unique<cmFastbuildLinkLineComputer>(
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<IDEProjectConfig> const& configs, std::string const& keyName)
{
std::vector<std::string> 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<FastbuildTarget const&>(*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<FastbuildAliasNode const&>(*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<std::string /*folder*/, std::vector<std::string>> VSProjects;
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string>{ "C:\\Windows\\System32" });
#else
cmSystemTools::FindProgram("sh", std::vector<std::string>{ "/bin" });
#endif
return cached;
}
void cmGlobalFastbuildGenerator::WriteTargetRebuildBFF()
{
std::vector<std::string> implicitDeps;
for (auto& lg : LocalGenerators) {
std::vector<std::string> 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<FastbuildExecNode const&>(*targetBase));
} else if (targetBase->Type == FastbuildTargetType::ALIAS) {
this->WriteAlias(static_cast<FastbuildAliasNode const&>(*targetBase));
} else if (targetBase->Type == FastbuildTargetType::LINK) {
auto const& target = static_cast<FastbuildTarget const&>(*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<FastbuildTarget>
cmGlobalFastbuildGenerator::GetTargetByOutputName(
std::string const& output) const
{
for (auto const& targetBase : FastbuildTargets) {
if (targetBase->Type == FastbuildTargetType::LINK) {
auto const& target = static_cast<FastbuildTarget const&>(*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<std::string> const& cmGlobalFastbuildGenerator::GetConfigNames()
const
{
return static_cast<cmLocalFastbuildGenerator const*>(
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<intptr_t>(hi) > 32;
};
return std::async(std::launch::async, OpenSolution, sln).get();
#else
return cmGlobalCommonGenerator::Open(bindir, projectName, dryRun);
#endif
}