| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmLocalNinjaGenerator.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstdio> | |
| #include <memory> | |
| #include <sstream> | |
| #include <utility> | |
| #include <cm/unordered_set> | |
| #include <cmext/string_view> | |
| #include "cmsys/FStream.hxx" | |
| #include "cm_codecvt_Encoding.hxx" | |
| #include "cmCryptoHash.h" | |
| #include "cmCustomCommand.h" | |
| #include "cmCustomCommandGenerator.h" | |
| #include "cmDiagnostics.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmGlobalNinjaGenerator.h" | |
| #include "cmInstrumentation.h" | |
| #include "cmInstrumentationQuery.h" | |
| #include "cmList.h" | |
| #include "cmListFileCache.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmMakefile.h" | |
| #include "cmNinjaTargetGenerator.h" | |
| #include "cmNinjaTypes.h" | |
| #include "cmPolicies.h" | |
| #include "cmRulePlaceholderExpander.h" | |
| #include "cmSourceFile.h" | |
| #include "cmState.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmTargetTypes.h" | |
| #include "cmValue.h" | |
| #include "cmake.h" | |
| cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg, | |
| cmMakefile* mf) | |
| : cmLocalCommonGenerator(gg, mf) | |
| { | |
| } | |
| // Virtual public methods. | |
| std::unique_ptr<cmRulePlaceholderExpander> | |
| cmLocalNinjaGenerator::CreateRulePlaceholderExpander( | |
| cmBuildStep buildStep) const | |
| { | |
| auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander(buildStep); | |
| ret->SetTargetImpLib("$TARGET_IMPLIB"); | |
| return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret)); | |
| } | |
| cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default; | |
| void cmLocalNinjaGenerator::Generate() | |
| { | |
| // Compute the path to use when referencing the current output | |
| // directory from the top output directory. | |
| this->HomeRelativeOutputPath = | |
| this->MaybeRelativeToTopBinDir(this->GetObjectOutputRoot()); | |
| if (this->HomeRelativeOutputPath == ".") { | |
| this->HomeRelativeOutputPath.clear(); | |
| } | |
| if (this->GetGlobalGenerator()->IsMultiConfig()) { | |
| for (auto const& config : this->GetConfigNames()) { | |
| this->WriteProcessedMakefile(this->GetImplFileStream(config)); | |
| } | |
| } | |
| this->WriteProcessedMakefile(this->GetCommonFileStream()); | |
| #ifdef NINJA_GEN_VERBOSE_FILES | |
| this->WriteProcessedMakefile(this->GetRulesFileStream()); | |
| #endif | |
| // We do that only once for the top CMakeLists.txt file. | |
| if (this->IsRootMakefile()) { | |
| this->WriteBuildFileTop(); | |
| this->WritePools(this->GetRulesFileStream()); | |
| std::string const& showIncludesPrefix = | |
| this->GetMakefile()->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX"); | |
| if (!showIncludesPrefix.empty()) { | |
| cmGlobalNinjaGenerator::WriteComment(this->GetRulesFileStream(), | |
| "localized /showIncludes string"); | |
| this->GetRulesFileStream() << "msvc_deps_prefix = "; | |
| // 'cl /showIncludes' encodes output in the console output code page. | |
| // It may differ from the encoding used for file paths in 'build.ninja'. | |
| // Ninja matches the showIncludes prefix using its raw byte sequence. | |
| this->GetRulesFileStream().WriteAltEncoding( | |
| showIncludesPrefix, cmGeneratedFileStream::Encoding::ConsoleOutput); | |
| this->GetRulesFileStream() << "\n\n"; | |
| } | |
| } | |
| for (auto const& target : this->GetGeneratorTargets()) { | |
| if (!target->IsInBuildSystem()) { | |
| continue; | |
| } | |
| auto tg = cmNinjaTargetGenerator::New(target.get()); | |
| if (tg) { | |
| if (target->Target->IsPerConfig()) { | |
| for (auto const& config : this->GetConfigNames()) { | |
| tg->Generate(config); | |
| if (target->GetType() == cm::TargetType::GLOBAL_TARGET && | |
| this->GetGlobalGenerator()->IsMultiConfig()) { | |
| cmNinjaBuild phonyAlias("phony"); | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact); | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.ExplicitDeps, config, | |
| DependOnTargetArtifact); | |
| this->GetGlobalNinjaGenerator()->WriteBuild( | |
| *this->GetGlobalNinjaGenerator()->GetConfigFileStream(config), | |
| phonyAlias); | |
| } | |
| } | |
| if (target->GetType() == cm::TargetType::GLOBAL_TARGET && | |
| this->GetGlobalGenerator()->IsMultiConfig()) { | |
| if (!this->GetGlobalNinjaGenerator()->GetDefaultConfigs().empty()) { | |
| cmNinjaBuild phonyAlias("phony"); | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact); | |
| for (auto const& config : | |
| this->GetGlobalNinjaGenerator()->GetDefaultConfigs()) { | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.ExplicitDeps, config, | |
| DependOnTargetArtifact); | |
| } | |
| this->GetGlobalNinjaGenerator()->WriteBuild( | |
| *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(), | |
| phonyAlias); | |
| } | |
| cmNinjaBuild phonyAlias("phony"); | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.Outputs, "all", DependOnTargetArtifact); | |
| for (auto const& config : this->GetConfigNames()) { | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs( | |
| target.get(), phonyAlias.ExplicitDeps, config, | |
| DependOnTargetArtifact); | |
| } | |
| this->GetGlobalNinjaGenerator()->WriteBuild( | |
| *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(), | |
| phonyAlias); | |
| } | |
| } else { | |
| tg->Generate(""); | |
| } | |
| } | |
| } | |
| for (auto const& config : this->GetConfigNames()) { | |
| this->WriteCustomCommandBuildStatements(config); | |
| this->AdditionalCleanFiles(config); | |
| } | |
| } | |
| std::string cmLocalNinjaGenerator::GetObjectOutputRoot( | |
| cmStateEnums::IntermediateDirKind kind) const | |
| { | |
| if (this->UseShortObjectNames(kind)) { | |
| return cmStrCat(this->GetBinaryDirectory(), '/', | |
| this->GetGlobalGenerator()->GetShortBinaryOutputDir()); | |
| } | |
| return cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles"); | |
| } | |
| // Non-virtual public methods. | |
| cmGlobalNinjaGenerator const* cmLocalNinjaGenerator::GetGlobalNinjaGenerator() | |
| const | |
| { | |
| return static_cast<cmGlobalNinjaGenerator const*>( | |
| this->GetGlobalGenerator()); | |
| } | |
| cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator() | |
| { | |
| return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator()); | |
| } | |
| std::string const& cmLocalNinjaGenerator::GetWorkingDirectory() const | |
| { | |
| return this->GetState()->GetBinaryDirectory(); | |
| } | |
| std::string cmLocalNinjaGenerator::MaybeRelativeToWorkDir( | |
| std::string const& path) const | |
| { | |
| return this->GetGlobalNinjaGenerator()->NinjaOutputPath( | |
| this->MaybeRelativeToTopBinDir(path)); | |
| } | |
| std::string cmLocalNinjaGenerator::GetLinkDependencyFile( | |
| cmGeneratorTarget* target, std::string const& config) const | |
| { | |
| return cmStrCat(target->GetSupportDirectory(), | |
| this->GetGlobalNinjaGenerator()->ConfigDirectory(config), | |
| "/link.d"); | |
| } | |
| // Virtual protected methods. | |
| std::string cmLocalNinjaGenerator::ConvertToIncludeReference( | |
| std::string const& path, cmOutputConverter::OutputFormat format) | |
| { | |
| return this->ConvertToOutputFormat(path, format); | |
| } | |
| // Private methods. | |
| cmGeneratedFileStream& cmLocalNinjaGenerator::GetImplFileStream( | |
| std::string const& config) const | |
| { | |
| return *this->GetGlobalNinjaGenerator()->GetImplFileStream(config); | |
| } | |
| cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const | |
| { | |
| return *this->GetGlobalNinjaGenerator()->GetCommonFileStream(); | |
| } | |
| cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const | |
| { | |
| return *this->GetGlobalNinjaGenerator()->GetRulesFileStream(); | |
| } | |
| cmake const* cmLocalNinjaGenerator::GetCMakeInstance() const | |
| { | |
| return this->GetGlobalGenerator()->GetCMakeInstance(); | |
| } | |
| cmake* cmLocalNinjaGenerator::GetCMakeInstance() | |
| { | |
| return this->GetGlobalGenerator()->GetCMakeInstance(); | |
| } | |
| void cmLocalNinjaGenerator::WriteBuildFileTop() | |
| { | |
| this->WriteProjectHeader(this->GetCommonFileStream()); | |
| if (this->GetGlobalGenerator()->IsMultiConfig()) { | |
| for (auto const& config : this->GetConfigNames()) { | |
| auto& stream = this->GetImplFileStream(config); | |
| this->WriteProjectHeader(stream); | |
| this->WriteNinjaRequiredVersion(stream); | |
| this->WriteNinjaConfigurationVariable(stream, config); | |
| this->WriteNinjaFilesInclusionConfig(stream); | |
| } | |
| } else { | |
| this->WriteNinjaRequiredVersion(this->GetCommonFileStream()); | |
| this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(), | |
| this->GetConfigNames().front()); | |
| } | |
| this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream()); | |
| this->WriteNinjaWorkDir(this->GetCommonFileStream()); | |
| // For the rule file. | |
| this->WriteProjectHeader(this->GetRulesFileStream()); | |
| } | |
| void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| os << "# Project: " << this->GetProjectName() | |
| << "\n" | |
| "# Configurations: " | |
| << cmJoin(this->GetConfigNames(), ", ") << '\n'; | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| } | |
| void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os) | |
| { | |
| // Default required version | |
| std::string requiredVersion = cmGlobalNinjaGenerator::RequiredNinjaVersion(); | |
| // Ninja generator uses the 'console' pool if available (>= 1.5) | |
| if (this->GetGlobalNinjaGenerator()->SupportsDirectConsole()) { | |
| requiredVersion = | |
| cmGlobalNinjaGenerator::RequiredNinjaVersionForConsolePool(); | |
| } | |
| // The Ninja generator writes rules which require support for restat | |
| // when rebuilding build.ninja manifest (>= 1.8) | |
| if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() && | |
| this->GetCMakeInstance()->DoWriteGlobVerifyTarget() && | |
| !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn( | |
| "CMAKE_SUPPRESS_REGENERATION")) { | |
| requiredVersion = | |
| cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat(); | |
| } | |
| cmGlobalNinjaGenerator::WriteComment( | |
| os, "Minimal version of Ninja required by this file"); | |
| os << "ninja_required_version = " << requiredVersion << "\n\n"; | |
| } | |
| void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable( | |
| std::ostream& os, std::string const& config) | |
| { | |
| cmGlobalNinjaGenerator::WriteVariable( | |
| os, "CONFIGURATION", config, | |
| "Set configuration variable for custom commands."); | |
| } | |
| void cmLocalNinjaGenerator::WritePools(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| cmValue jobpools = | |
| this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS"); | |
| if (!jobpools) { | |
| jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS"); | |
| } | |
| if (jobpools) { | |
| cmGlobalNinjaGenerator::WriteComment( | |
| os, "Pools defined by global property JOB_POOLS"); | |
| cmList pools{ *jobpools }; | |
| for (std::string const& pool : pools) { | |
| std::string::size_type const eq = pool.find('='); | |
| unsigned int jobs; | |
| if (eq != std::string::npos && | |
| sscanf(pool.c_str() + eq, "=%u", &jobs) == 1) { | |
| os << "pool " << pool.substr(0, eq) << "\n depth = " << jobs | |
| << "\n\n"; | |
| } else { | |
| cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': " + | |
| pool); | |
| } | |
| } | |
| } | |
| } | |
| void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| os << "# Include auxiliary files.\n\n"; | |
| cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator(); | |
| std::string const ninjaCommonFile = | |
| ng->NinjaOutputPath(cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE); | |
| std::string const commonFilePath = ng->EncodePath(ninjaCommonFile); | |
| cmGlobalNinjaGenerator::WriteInclude(os, commonFilePath, | |
| "Include common file."); | |
| os << '\n'; | |
| } | |
| void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| os << "# Include auxiliary files.\n\n"; | |
| cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator(); | |
| std::string const ninjaRulesFile = | |
| ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE); | |
| std::string const rulesFilePath = ng->EncodePath(ninjaRulesFile); | |
| cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath, | |
| "Include rules file."); | |
| os << '\n'; | |
| } | |
| void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| cmGlobalNinjaGenerator::WriteComment( | |
| os, "Logical path to working directory; prefix for absolute paths."); | |
| cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator(); | |
| std::string ninja_workdir = this->GetBinaryDirectory(); | |
| ng->StripNinjaOutputPathPrefixAsSuffix(ninja_workdir); // Also appends '/'. | |
| os << "cmake_ninja_workdir = " << ng->EncodePath(ninja_workdir) << "\n"; | |
| } | |
| void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os) | |
| { | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| os << "# Write statements declared in CMakeLists.txt:\n" | |
| "# " | |
| << this->Makefile->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE") << '\n'; | |
| if (this->IsRootMakefile()) { | |
| os << "# Which is the root file.\n"; | |
| } | |
| cmGlobalNinjaGenerator::WriteDivider(os); | |
| os << '\n'; | |
| } | |
| void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target, | |
| cmNinjaDeps& outputs, | |
| std::string const& config) | |
| { | |
| this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs, config, | |
| DependOnTargetArtifact); | |
| } | |
| void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target, | |
| cmNinjaDeps& outputs, | |
| std::string const& config, | |
| std::string const& fileConfig, | |
| cmNinjaTargetDepends depends) | |
| { | |
| this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config, | |
| fileConfig, depends); | |
| } | |
| void cmLocalNinjaGenerator::AppendCustomCommandDeps( | |
| cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps, | |
| std::string const& config) | |
| { | |
| for (std::string const& i : ccg.GetDepends()) { | |
| std::string dep; | |
| if (this->GetRealDependency(i, config, dep, | |
| ccg.GetCC().GetCMP0212Status())) { | |
| ninjaDeps.push_back( | |
| this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep)); | |
| } | |
| } | |
| } | |
| std::string cmLocalNinjaGenerator::WriteCommandScript( | |
| std::vector<std::string> const& cmdLines, std::string const& outputConfig, | |
| std::string const& commandConfig, std::string const& customStep, | |
| cmGeneratorTarget const* target) const | |
| { | |
| std::string scriptPath; | |
| if (target) { | |
| scriptPath = target->GetSupportDirectory(); | |
| } else { | |
| scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles"); | |
| } | |
| scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig); | |
| cmSystemTools::MakeDirectory(scriptPath); | |
| scriptPath += '/'; | |
| scriptPath += customStep; | |
| if (this->GlobalGenerator->IsMultiConfig()) { | |
| scriptPath = cmStrCat(std::move(scriptPath), '-', commandConfig); | |
| } | |
| #ifdef _WIN32 | |
| scriptPath += ".bat"; | |
| #else | |
| scriptPath += ".sh"; | |
| #endif | |
| cmsys::ofstream script(scriptPath.c_str()); | |
| #ifdef _WIN32 | |
| script << "@echo off\n"; | |
| int line = 1; | |
| #else | |
| script << "set -e\n\n"; | |
| #endif | |
| for (auto const& i : cmdLines) { | |
| std::string cmd = i; | |
| // The command line was built assuming it would be written to | |
| // the build.ninja file, so it uses '$$' for '$'. Remove this | |
| // for the raw shell script. | |
| cmSystemTools::ReplaceString(cmd, "$$", "$"); | |
| #ifdef _WIN32 | |
| script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)" | |
| << '\n'; | |
| #else | |
| script << cmd << '\n'; | |
| #endif | |
| } | |
| #ifdef _WIN32 | |
| script << "goto :EOF\n\n" | |
| ":ABORT\n" | |
| "set ERROR_CODE=%ERRORLEVEL%\n" | |
| "echo Batch file failed at line %FAIL_LINE% " | |
| "with errorcode %ERRORLEVEL%\n" | |
| "exit /b %ERROR_CODE%"; | |
| #elif !defined(CMAKE_BOOTSTRAP) | |
| if (this->GetCMakeInstance()->GetInstrumentation()->HasOption( | |
| cmInstrumentationQuery::Option::ProcessMetrics)) { | |
| // Prevent shell exec optimization from passing accumulated child resource | |
| // usage to the final instrumentation wrapper. | |
| script << ":\n"; | |
| } | |
| #endif | |
| return scriptPath; | |
| } | |
| #ifdef _WIN32 | |
| namespace { | |
| bool RuleNeedsCMD(std::string const& cmd) | |
| { | |
| std::vector<std::string> args; | |
| cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args); | |
| auto it = std::find_if(args.cbegin(), args.cend(), | |
| [](std::string const& arg) -> bool { | |
| // FIXME: Detect more windows shell operators. | |
| return cmHasPrefix(arg, '>'); | |
| }); | |
| return it != args.cend(); | |
| } | |
| } | |
| #endif | |
| std::string cmLocalNinjaGenerator::BuildCommandLine( | |
| std::vector<std::string> const& cmdLines, std::string const& outputConfig, | |
| std::string const& commandConfig, std::string const& customStep, | |
| cmGeneratorTarget const* target, CommandLineContext context) const | |
| { | |
| // If we have no commands but we need to build a command anyway, use noop. | |
| // This happens when building a POST_BUILD value for link targets that | |
| // don't use POST_BUILD. | |
| if (cmdLines.empty()) { | |
| return cmGlobalNinjaGenerator::SHELL_NOOP; | |
| } | |
| // If this is a custom step then we will have no '$VAR' ninja placeholders. | |
| // This means we can deal with long command sequences by writing to a script. | |
| // Do this if the command lines are on the scale of the OS limit. | |
| if (!customStep.empty()) { | |
| size_t cmdLinesTotal = 0; | |
| for (std::string const& cmd : cmdLines) { | |
| cmdLinesTotal += cmd.length() + 6; | |
| } | |
| if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) { | |
| std::string const scriptPath = this->WriteCommandScript( | |
| cmdLines, outputConfig, commandConfig, customStep, target); | |
| std::string cmd | |
| #ifndef _WIN32 | |
| = "/bin/sh " | |
| #endif | |
| ; | |
| cmd += this->ConvertToOutputFormat( | |
| this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath), | |
| cmOutputConverter::SHELL); | |
| // Add an unused argument based on script content so that Ninja | |
| // knows when the command lines change. | |
| cmd += " "; | |
| cmCryptoHash hash(cmCryptoHash::AlgoSHA256); | |
| cmd += hash.HashFile(scriptPath).substr(0, 16); | |
| return cmd; | |
| } | |
| } | |
| std::ostringstream cmd; | |
| #ifdef _WIN32 | |
| (void)context; | |
| cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator(); | |
| bool const needCMD = | |
| cmdLines.size() > 1 || (customStep.empty() && RuleNeedsCMD(cmdLines[0])); | |
| for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) { | |
| if (li != cmdLines.begin()) { | |
| cmd << " && "; | |
| } else if (needCMD) { | |
| cmd << gg->GetComspec() << " /C \""; | |
| } | |
| // Put current cmdLine in brackets if it contains "||" because it has | |
| // higher precedence than "&&" in cmd.exe | |
| if (li->find("||") != std::string::npos) { | |
| cmd << "( " << *li << " )"; | |
| } else { | |
| cmd << *li; | |
| } | |
| } | |
| if (needCMD) { | |
| cmd << "\""; | |
| } | |
| #else | |
| for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) { | |
| if (li != cmdLines.begin()) { | |
| cmd << " && "; | |
| } | |
| cmd << *li; | |
| } | |
| # ifndef CMAKE_BOOTSTRAP | |
| if (context == CommandLineContext::Build && | |
| this->GetCMakeInstance()->GetInstrumentation()->HasOption( | |
| cmInstrumentationQuery::Option::ProcessMetrics)) { | |
| // Prevent shell exec optimization from passing accumulated child resource | |
| // usage to the final instrumentation wrapper. Keep this build-only shell | |
| // syntax out of compile_commands.json, for compiler tooling support | |
| cmd << " && :"; | |
| } | |
| # else | |
| (void)context; | |
| # endif | |
| #endif | |
| return cmd.str(); | |
| } | |
| void cmLocalNinjaGenerator::AppendCustomCommandLines( | |
| cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines) | |
| { | |
| auto* gg = this->GetGlobalNinjaGenerator(); | |
| if (ccg.GetNumberOfCommands() > 0) { | |
| std::string wd = ccg.GetWorkingDirectory(); | |
| if (wd.empty()) { | |
| wd = this->GetCurrentBinaryDirectory(); | |
| } | |
| std::ostringstream cdCmd; | |
| #ifdef _WIN32 | |
| std::string cdStr = "cd /D "; | |
| #else | |
| std::string cdStr = "cd "; | |
| #endif | |
| cdCmd << cdStr | |
| << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL); | |
| cmdLines.push_back(cdCmd.str()); | |
| } | |
| std::string launcher = this->MakeCustomLauncher(ccg); | |
| for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) { | |
| std::string c = ccg.GetCommand(i); | |
| if (c.empty()) { | |
| continue; | |
| } | |
| cmdLines.push_back(launcher + | |
| this->ConvertToOutputFormat( | |
| c, | |
| gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI | |
| : cmOutputConverter::SHELL)); | |
| std::string& cmd = cmdLines.back(); | |
| ccg.AppendArguments(i, cmd); | |
| } | |
| } | |
| void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement( | |
| cmCustomCommand const* cc, std::set<cmGeneratorTarget*> const& targets, | |
| std::string const& fileConfig) | |
| { | |
| cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator(); | |
| if (gg->SeenCustomCommand(cc, fileConfig)) { | |
| return; | |
| } | |
| auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig); | |
| for (cmCustomCommandGenerator const& ccg : ccgs) { | |
| if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) { | |
| // Generator expressions evaluate to no output for this config. | |
| continue; | |
| } | |
| std::unordered_set<std::string> orderOnlyDeps; | |
| if (!cc->GetDependsExplicitOnly()) { | |
| // A custom command may appear on multiple targets. However, some build | |
| // systems exist where the target dependencies on some of the targets are | |
| // overspecified, leading to a dependency cycle. If we assume all target | |
| // dependencies are a superset of the true target dependencies for this | |
| // custom command, we can take the set intersection of all target | |
| // dependencies to obtain a correct dependency list. | |
| // | |
| // FIXME: This won't work in certain obscure scenarios involving indirect | |
| // dependencies. | |
| auto j = targets.begin(); | |
| assert(j != targets.end()); | |
| this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( | |
| *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); | |
| ++j; | |
| for (; j != targets.end(); ++j) { | |
| std::unordered_set<std::string> jDeps; | |
| this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( | |
| *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); | |
| cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) { | |
| return jDeps.find(dep) == jDeps.end(); | |
| }); | |
| } | |
| } | |
| std::vector<std::string> const& outputs = ccg.GetOutputs(); | |
| std::vector<std::string> const& byproducts = ccg.GetByproducts(); | |
| bool symbolic = false; | |
| for (std::string const& output : outputs) { | |
| if (cmSourceFile* sf = this->Makefile->GetSource(output)) { | |
| if (sf->GetPropertyAsBool("SYMBOLIC")) { | |
| symbolic = true; | |
| break; | |
| } | |
| } | |
| } | |
| cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg); | |
| ccOutputs.Add(outputs); | |
| ccOutputs.Add(byproducts); | |
| std::string mainOutput = ccOutputs.ExplicitOuts[0]; | |
| cmNinjaDeps ninjaDeps; | |
| this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig); | |
| std::vector<std::string> cmdLines; | |
| this->AppendCustomCommandLines(ccg, cmdLines); | |
| cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(), | |
| orderOnlyDeps.end()); | |
| std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end()); | |
| if (cmdLines.empty()) { | |
| cmNinjaBuild build("phony"); | |
| build.Comment = cmStrCat("Phony custom command for ", mainOutput); | |
| build.Outputs = std::move(ccOutputs.ExplicitOuts); | |
| build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts); | |
| build.ExplicitDeps = std::move(ninjaDeps); | |
| build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps); | |
| gg->WriteBuild(this->GetImplFileStream(fileConfig), build); | |
| } else { | |
| std::string customStep = cmSystemTools::GetFilenameName(mainOutput); | |
| if (this->GlobalGenerator->IsMultiConfig()) { | |
| customStep += '-'; | |
| customStep += fileConfig; | |
| customStep += '-'; | |
| customStep += ccg.GetOutputConfig(); | |
| } | |
| // Hash full path to make unique. | |
| customStep += '-'; | |
| cmCryptoHash hash(cmCryptoHash::AlgoSHA256); | |
| customStep += hash.HashString(mainOutput).substr(0, 7); | |
| std::string depfile = ccg.GetDepfile(); | |
| if (!depfile.empty()) { | |
| switch (cc->GetCMP0116Status()) { | |
| case cmPolicies::WARN: | |
| if (this->GetCurrentBinaryDirectory() != | |
| this->GetBinaryDirectory() || | |
| this->Makefile->PolicyOptionalWarningEnabled( | |
| "CMAKE_POLICY_WARNING_CMP0116")) { | |
| this->GetCMakeInstance()->IssueDiagnostic( | |
| cmDiagnostics::CMD_POLICY, | |
| cmPolicies::GetPolicyWarning(cmPolicies::CMP0116), | |
| cc->GetBacktrace()); | |
| } | |
| CM_FALLTHROUGH; | |
| case cmPolicies::OLD: | |
| break; | |
| case cmPolicies::NEW: | |
| depfile = ccg.GetInternalDepfile(); | |
| break; | |
| } | |
| } | |
| std::string comment = cmStrCat("Custom command for ", mainOutput); | |
| gg->WriteCustomCommandBuild( | |
| this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig, | |
| customStep), | |
| this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(), | |
| cc->GetUsesTerminal(), | |
| /*restat*/ !symbolic || !byproducts.empty(), fileConfig, | |
| std::move(ccOutputs), std::move(ninjaDeps), | |
| std::move(sortedOrderOnlyDeps)); | |
| } | |
| } | |
| } | |
| bool cmLocalNinjaGenerator::HasUniqueByproducts( | |
| std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt) | |
| { | |
| cmGeneratorExpression ge(*this->GetCMakeInstance(), bt); | |
| for (std::string const& p : byproducts) { | |
| if (cmGeneratorExpression::Find(p) == std::string::npos) { | |
| return false; | |
| } | |
| std::set<std::string> seen; | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p); | |
| for (std::string const& config : this->GetConfigNames()) { | |
| for (std::string const& b : | |
| this->ExpandCustomCommandOutputPaths(*cge, config)) { | |
| if (!seen.insert(b).second) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| namespace { | |
| bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs) | |
| { | |
| std::set<std::string> allOutputs; | |
| std::set<std::string> allByproducts; | |
| for (cmCustomCommandGenerator const& ccg : ccgs) { | |
| for (std::string const& output : ccg.GetOutputs()) { | |
| if (!allOutputs.insert(output).second) { | |
| return false; | |
| } | |
| } | |
| for (std::string const& byproduct : ccg.GetByproducts()) { | |
| if (!allByproducts.insert(byproduct).second) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| std::string cmLocalNinjaGenerator::CreateUtilityOutput( | |
| std::string const& targetName, std::vector<std::string> const& byproducts, | |
| cmListFileBacktrace const& bt) | |
| { | |
| // In Ninja Multi-Config, we can only produce cross-config utility | |
| // commands if all byproducts are per-config. | |
| if (!this->GetGlobalGenerator()->IsMultiConfig() || | |
| !this->HasUniqueByproducts(byproducts, bt)) { | |
| return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts, | |
| bt); | |
| } | |
| std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(), | |
| "/CMakeFiles/", targetName, '-'); | |
| // The output is not actually created so mark it symbolic. | |
| for (std::string const& config : this->GetConfigNames()) { | |
| std::string const force = cmStrCat(base, config); | |
| if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) { | |
| sf->SetProperty("SYMBOLIC", "1"); | |
| } else { | |
| cmSystemTools::Error("Could not get source file entry for " + force); | |
| } | |
| } | |
| this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName); | |
| return cmStrCat(base, "$<CONFIG>"_s); | |
| } | |
| std::vector<cmCustomCommandGenerator> | |
| cmLocalNinjaGenerator::MakeCustomCommandGenerators( | |
| cmCustomCommand const& cc, std::string const& fileConfig) | |
| { | |
| cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator(); | |
| bool transformDepfile = false; | |
| switch (cc.GetCMP0116Status()) { | |
| case cmPolicies::WARN: | |
| CM_FALLTHROUGH; | |
| case cmPolicies::OLD: | |
| break; | |
| case cmPolicies::NEW: | |
| transformDepfile = true; | |
| break; | |
| } | |
| // Start with the build graph's configuration. | |
| std::vector<cmCustomCommandGenerator> ccgs; | |
| ccgs.emplace_back(cc, fileConfig, this, transformDepfile); | |
| // Consider adding cross configurations. | |
| if (!gg->EnableCrossConfigBuild()) { | |
| return ccgs; | |
| } | |
| // Outputs and byproducts must be expressed using generator expressions. | |
| for (std::string const& output : cc.GetOutputs()) { | |
| if (cmGeneratorExpression::Find(output) == std::string::npos) { | |
| return ccgs; | |
| } | |
| } | |
| for (std::string const& byproduct : cc.GetByproducts()) { | |
| if (cmGeneratorExpression::Find(byproduct) == std::string::npos) { | |
| return ccgs; | |
| } | |
| } | |
| // Tentatively add the other cross configurations. | |
| for (std::string const& config : gg->GetCrossConfigs(fileConfig)) { | |
| if (fileConfig != config) { | |
| ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config); | |
| } | |
| } | |
| // If outputs and byproducts are not unique to each configuration, | |
| // drop the cross configurations. | |
| if (!HasUniqueOutputs(ccgs)) { | |
| ccgs.erase(ccgs.begin() + 1, ccgs.end()); | |
| } | |
| return ccgs; | |
| } | |
| void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc, | |
| cmGeneratorTarget* target) | |
| { | |
| CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>()); | |
| std::pair<CustomCommandTargetMap::iterator, bool> ins = | |
| this->CustomCommandTargets.insert(v); | |
| if (ins.second) { | |
| this->CustomCommands.push_back(cc); | |
| } | |
| ins.first->second.insert(target); | |
| } | |
| void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements( | |
| std::string const& fileConfig) | |
| { | |
| for (cmCustomCommand const* customCommand : this->CustomCommands) { | |
| auto i = this->CustomCommandTargets.find(customCommand); | |
| assert(i != this->CustomCommandTargets.end()); | |
| this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig); | |
| } | |
| } | |
| std::string cmLocalNinjaGenerator::MakeCustomLauncher( | |
| cmCustomCommandGenerator const& ccg) | |
| { | |
| cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM"); | |
| if (!cmNonempty(property_value)) { | |
| return std::string(); | |
| } | |
| // Expand rule variables referenced in the given launcher command. | |
| cmRulePlaceholderExpander::RuleVariables vars; | |
| std::string output; | |
| std::vector<std::string> const& outputs = ccg.GetOutputs(); | |
| for (std::string const& ccgOut : outputs) { | |
| output = cmStrCat( | |
| std::move(output), | |
| this->ConvertToOutputFormat(ccg.GetWorkingDirectory().empty() | |
| ? this->MaybeRelativeToCurBinDir(ccgOut) | |
| : ccgOut, | |
| cmOutputConverter::SHELL), | |
| ','); | |
| } | |
| if (!output.empty()) { | |
| output.pop_back(); | |
| } | |
| vars.Output = output.c_str(); | |
| std::string filePathWithOutput = ccg.StoreContentToFile(output); | |
| vars.FilePathWithOutput = filePathWithOutput.c_str(); | |
| vars.Role = ccg.GetCC().GetRole().c_str(); | |
| vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); | |
| vars.Config = ccg.GetOutputConfig().c_str(); | |
| auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander(); | |
| std::string launcher = *property_value; | |
| rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars); | |
| if (!launcher.empty()) { | |
| launcher += " "; | |
| } | |
| return launcher; | |
| } | |
| void cmLocalNinjaGenerator::AdditionalCleanFiles(std::string const& config) | |
| { | |
| if (cmValue prop_value = | |
| this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) { | |
| cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this, | |
| config) }; | |
| std::string const& binaryDir = this->GetCurrentBinaryDirectory(); | |
| cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator(); | |
| for (auto const& cleanFile : cleanFiles) { | |
| // Support relative paths | |
| gg->AddAdditionalCleanFile( | |
| cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config); | |
| } | |
| } | |
| } |