| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCustomCommandGenerator.h" | |
| #include <cstddef> | |
| #include <memory> | |
| #include <utility> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include <cmext/algorithm> | |
| #include <cmext/string_view> | |
| #include "cmCryptoHash.h" | |
| #include "cmCustomCommand.h" | |
| #include "cmCustomCommandLines.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmList.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmMakefile.h" | |
| #include "cmStateTypes.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTransformDepfile.h" | |
| #include "cmValue.h" | |
| namespace { | |
| std::string EvaluateSplitConfigGenex( | |
| cm::string_view input, cmGeneratorExpression const& ge, cmLocalGenerator* lg, | |
| bool useOutputConfig, std::string const& outputConfig, | |
| std::string const& commandConfig, cmGeneratorTarget const* target, | |
| std::set<BT<std::pair<std::string, bool>>>* utils = nullptr) | |
| { | |
| std::string result; | |
| while (!input.empty()) { | |
| // Copy non-genex content directly to the result. | |
| std::string::size_type pos = input.find("$<"); | |
| result += input.substr(0, pos); | |
| if (pos == std::string::npos) { | |
| break; | |
| } | |
| input = input.substr(pos); | |
| // Find the balanced end of this regex. | |
| size_t nestingLevel = 1; | |
| for (pos = 2; pos < input.size(); ++pos) { | |
| cm::string_view cur = input.substr(pos); | |
| if (cmHasLiteralPrefix(cur, "$<")) { | |
| ++nestingLevel; | |
| ++pos; | |
| continue; | |
| } | |
| if (cmHasLiteralPrefix(cur, ">")) { | |
| --nestingLevel; | |
| if (nestingLevel == 0) { | |
| ++pos; | |
| break; | |
| } | |
| } | |
| } | |
| // Split this genex from following input. | |
| cm::string_view genex = input.substr(0, pos); | |
| input = input.substr(pos); | |
| // Convert an outer COMMAND_CONFIG or OUTPUT_CONFIG to the matching config. | |
| std::string const* config = | |
| useOutputConfig ? &outputConfig : &commandConfig; | |
| if (nestingLevel == 0) { | |
| static cm::string_view const COMMAND_CONFIG = "$<COMMAND_CONFIG:"_s; | |
| static cm::string_view const OUTPUT_CONFIG = "$<OUTPUT_CONFIG:"_s; | |
| if (cmHasPrefix(genex, COMMAND_CONFIG)) { | |
| genex.remove_prefix(COMMAND_CONFIG.size()); | |
| genex.remove_suffix(1); | |
| useOutputConfig = false; | |
| config = &commandConfig; | |
| } else if (cmHasPrefix(genex, OUTPUT_CONFIG)) { | |
| genex.remove_prefix(OUTPUT_CONFIG.size()); | |
| genex.remove_suffix(1); | |
| useOutputConfig = true; | |
| config = &outputConfig; | |
| } | |
| } | |
| // Evaluate this genex in the selected configuration. | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = | |
| ge.Parse(std::string(genex)); | |
| result += cge->Evaluate(lg, *config, target); | |
| // Record targets referenced by the genex. | |
| if (utils) { | |
| // Use a cross-dependency if we referenced the command config. | |
| bool const cross = !useOutputConfig; | |
| for (cmGeneratorTarget* gt : cge->GetTargets()) { | |
| utils->emplace(BT<std::pair<std::string, bool>>( | |
| { gt->GetName(), cross }, cge->GetBacktrace())); | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths, | |
| cmGeneratorExpression const& ge, | |
| cmLocalGenerator* lg, | |
| std::string const& outputConfig, | |
| std::string const& commandConfig) | |
| { | |
| std::vector<std::string> depends; | |
| for (std::string const& p : paths) { | |
| std::string const& ep = | |
| EvaluateSplitConfigGenex(p, ge, lg, /*useOutputConfig=*/true, | |
| /*outputConfig=*/outputConfig, | |
| /*commandConfig=*/commandConfig, | |
| /*target=*/nullptr); | |
| cm::append(depends, cmList{ ep }); | |
| } | |
| for (std::string& p : depends) { | |
| if (cmSystemTools::FileIsFullPath(p)) { | |
| p = cmSystemTools::CollapseFullPath(p); | |
| } else { | |
| cmSystemTools::ConvertToUnixSlashes(p); | |
| } | |
| } | |
| return depends; | |
| } | |
| std::vector<std::string> EvaluateOutputs(std::vector<std::string> const& paths, | |
| cmGeneratorExpression const& ge, | |
| cmLocalGenerator* lg, | |
| std::string const& config) | |
| { | |
| std::vector<std::string> outputs; | |
| for (std::string const& p : paths) { | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p); | |
| cm::append(outputs, lg->ExpandCustomCommandOutputPaths(*cge, config)); | |
| } | |
| return outputs; | |
| } | |
| std::string EvaluateDepfile(std::string const& path, | |
| cmGeneratorExpression const& ge, | |
| cmLocalGenerator* lg, std::string const& config) | |
| { | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(path); | |
| return cge->Evaluate(lg, config); | |
| } | |
| std::string EvaluateComment(char const* comment, | |
| cmGeneratorExpression const& ge, | |
| cmLocalGenerator* lg, std::string const& config) | |
| { | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(comment); | |
| return cge->Evaluate(lg, config); | |
| } | |
| } | |
| cmCustomCommandGenerator::cmCustomCommandGenerator( | |
| cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg, | |
| bool transformDepfile, cm::optional<std::string> crossConfig, | |
| std::function<std::string(std::string const&, std::string const&)> | |
| computeInternalDepfile) | |
| : CC(&cc) | |
| , OutputConfig(crossConfig ? *crossConfig : config) | |
| , CommandConfig(std::move(config)) | |
| , Target(cc.GetTarget()) | |
| , LG(lg) | |
| , OldStyle(cc.GetEscapeOldStyle()) | |
| , MakeVars(cc.GetEscapeAllowMakeVars()) | |
| , EmulatorsWithArguments(cc.GetCommandLines().size()) | |
| , ComputeInternalDepfile(std::move(computeInternalDepfile)) | |
| { | |
| cmGeneratorExpression ge(*lg->GetCMakeInstance(), cc.GetBacktrace()); | |
| cmGeneratorTarget const* target{ lg->FindGeneratorTargetToUse( | |
| this->Target) }; | |
| bool const distinctConfigs = this->OutputConfig != this->CommandConfig; | |
| cmCustomCommandLines const& cmdlines = this->CC->GetCommandLines(); | |
| for (cmCustomCommandLine const& cmdline : cmdlines) { | |
| cmCustomCommandLine argv; | |
| // For the command itself, we default to the COMMAND_CONFIG. | |
| bool useOutputConfig = false; | |
| for (std::string const& clarg : cmdline) { | |
| std::string parsed_arg = EvaluateSplitConfigGenex( | |
| clarg, ge, this->LG, useOutputConfig, this->OutputConfig, | |
| this->CommandConfig, target, &this->Utilities); | |
| if (this->CC->GetCommandExpandLists()) { | |
| cm::append(argv, cmList{ parsed_arg }); | |
| } else { | |
| argv.push_back(std::move(parsed_arg)); | |
| } | |
| if (distinctConfigs) { | |
| // For remaining arguments, we default to the OUTPUT_CONFIG. | |
| useOutputConfig = true; | |
| } | |
| } | |
| if (!argv.empty()) { | |
| // If the command references an executable target by name, | |
| // collect the target to add a target-level dependency on it. | |
| cmGeneratorTarget* gt = this->LG->FindGeneratorTargetToUse(argv.front()); | |
| if (gt && gt->GetType() == cmStateEnums::EXECUTABLE) { | |
| // GetArgv0Location uses the command config, so use a cross-dependency. | |
| bool const cross = true; | |
| this->Utilities.emplace(BT<std::pair<std::string, bool>>( | |
| { gt->GetName(), cross }, cc.GetBacktrace())); | |
| } | |
| } else { | |
| // Later code assumes at least one entry exists, but expanding | |
| // lists on an empty command may have left this empty. | |
| // FIXME: Should we define behavior for removing empty commands? | |
| argv.emplace_back(); | |
| } | |
| this->CommandLines.push_back(std::move(argv)); | |
| } | |
| if (transformDepfile && !this->CommandLines.empty() && | |
| !cc.GetDepfile().empty() && | |
| this->LG->GetGlobalGenerator()->DepfileFormat()) { | |
| cmCustomCommandLine argv; | |
| argv.push_back(cmSystemTools::GetCMakeCommand()); | |
| argv.emplace_back("-E"); | |
| argv.emplace_back("cmake_transform_depfile"); | |
| argv.push_back(this->LG->GetGlobalGenerator()->GetName()); | |
| switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) { | |
| case cmDepfileFormat::GccDepfile: | |
| argv.emplace_back("gccdepfile"); | |
| break; | |
| case cmDepfileFormat::MakeDepfile: | |
| argv.emplace_back("makedepfile"); | |
| break; | |
| case cmDepfileFormat::MSBuildAdditionalInputs: | |
| argv.emplace_back("MSBuildAdditionalInputs"); | |
| break; | |
| } | |
| argv.push_back(this->LG->GetSourceDirectory()); | |
| argv.push_back(this->LG->GetCurrentSourceDirectory()); | |
| argv.push_back(this->LG->GetBinaryDirectory()); | |
| argv.push_back(this->LG->GetCurrentBinaryDirectory()); | |
| argv.push_back(this->GetFullDepfile()); | |
| argv.push_back(this->GetInternalDepfile()); | |
| this->CommandLines.push_back(std::move(argv)); | |
| } | |
| this->Outputs = | |
| EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->OutputConfig); | |
| this->Byproducts = | |
| EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->OutputConfig); | |
| this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG, | |
| this->OutputConfig, this->CommandConfig); | |
| std::string const& workingdirectory = this->CC->GetWorkingDirectory(); | |
| if (!workingdirectory.empty()) { | |
| this->WorkingDirectory = EvaluateSplitConfigGenex( | |
| workingdirectory, ge, this->LG, true, this->OutputConfig, | |
| this->CommandConfig, target); | |
| // Convert working directory to a full path. | |
| if (!this->WorkingDirectory.empty()) { | |
| std::string const& build_dir = this->LG->GetCurrentBinaryDirectory(); | |
| this->WorkingDirectory = | |
| cmSystemTools::CollapseFullPath(this->WorkingDirectory, build_dir); | |
| } | |
| } | |
| this->FillEmulatorsWithArguments(); | |
| } | |
| unsigned int cmCustomCommandGenerator::GetNumberOfCommands() const | |
| { | |
| return static_cast<unsigned int>(this->CommandLines.size()); | |
| } | |
| void cmCustomCommandGenerator::FillEmulatorsWithArguments() | |
| { | |
| if (!this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) { | |
| return; | |
| } | |
| cmGeneratorExpression ge(*this->LG->GetCMakeInstance(), | |
| this->CC->GetBacktrace()); | |
| for (unsigned int c = 0; c < this->GetNumberOfCommands(); ++c) { | |
| // If the command is the plain name of an executable target, | |
| // launch it with its emulator. | |
| std::string const& argv0 = this->CommandLines[c][0]; | |
| cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0); | |
| if (target && target->GetType() == cmStateEnums::EXECUTABLE && | |
| !target->IsImported()) { | |
| cmValue emulator_property = | |
| target->GetProperty("CROSSCOMPILING_EMULATOR"); | |
| if (!emulator_property) { | |
| continue; | |
| } | |
| // Plain target names are replaced by GetArgv0Location with the | |
| // path to the executable artifact in the command config, so | |
| // evaluate the launcher's location in the command config too. | |
| std::string const emulator = | |
| ge.Parse(*emulator_property)->Evaluate(this->LG, this->CommandConfig); | |
| cmExpandList(emulator, this->EmulatorsWithArguments[c]); | |
| } | |
| } | |
| } | |
| std::vector<std::string> cmCustomCommandGenerator::GetCrossCompilingEmulator( | |
| unsigned int c) const | |
| { | |
| if (c >= this->EmulatorsWithArguments.size()) { | |
| return std::vector<std::string>(); | |
| } | |
| return this->EmulatorsWithArguments[c]; | |
| } | |
| char const* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const | |
| { | |
| // If the command is the plain name of an executable target, we replace it | |
| // with the path to the executable artifact in the command config. | |
| std::string const& argv0 = this->CommandLines[c][0]; | |
| cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0); | |
| if (target && target->GetType() == cmStateEnums::EXECUTABLE && | |
| (target->IsImported() || | |
| target->GetProperty("CROSSCOMPILING_EMULATOR") || | |
| !this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING"))) { | |
| return target->GetLocation(this->CommandConfig).c_str(); | |
| } | |
| return nullptr; | |
| } | |
| bool cmCustomCommandGenerator::HasOnlyEmptyCommandLines() const | |
| { | |
| for (cmCustomCommandLine const& ccl : this->CommandLines) { | |
| for (std::string const& cl : ccl) { | |
| if (!cl.empty()) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const | |
| { | |
| std::vector<std::string> emulator = this->GetCrossCompilingEmulator(c); | |
| if (!emulator.empty()) { | |
| return emulator[0]; | |
| } | |
| if (char const* location = this->GetArgv0Location(c)) { | |
| return std::string(location); | |
| } | |
| return this->CommandLines[c][0]; | |
| } | |
| static std::string escapeForShellOldStyle(std::string const& str) | |
| { | |
| std::string result; | |
| #if defined(_WIN32) && !defined(__CYGWIN__) | |
| // if there are spaces | |
| std::string temp = str; | |
| if (temp.find(" ") != std::string::npos && | |
| temp.find("\"") == std::string::npos) { | |
| result = cmStrCat('"', str, '"'); | |
| return result; | |
| } | |
| return str; | |
| #else | |
| for (char const* ch = str.c_str(); *ch != '\0'; ++ch) { | |
| if (*ch == ' ') { | |
| result += '\\'; | |
| } | |
| result += *ch; | |
| } | |
| return result; | |
| #endif | |
| } | |
| void cmCustomCommandGenerator::AppendArguments(unsigned int c, | |
| std::string& cmd) const | |
| { | |
| unsigned int offset = 1; | |
| std::vector<std::string> emulator = this->GetCrossCompilingEmulator(c); | |
| if (!emulator.empty()) { | |
| for (unsigned j = 1; j < emulator.size(); ++j) { | |
| cmd += " "; | |
| if (this->OldStyle) { | |
| cmd += escapeForShellOldStyle(emulator[j]); | |
| } else { | |
| cmd += | |
| this->LG->EscapeForShell(emulator[j], this->MakeVars, false, false, | |
| this->MakeVars && this->LG->IsNinjaMulti()); | |
| } | |
| } | |
| offset = 0; | |
| } | |
| cmCustomCommandLine const& commandLine = this->CommandLines[c]; | |
| for (unsigned int j = offset; j < commandLine.size(); ++j) { | |
| std::string arg; | |
| if (char const* location = j == 0 ? this->GetArgv0Location(c) : nullptr) { | |
| // GetCommand returned the emulator instead of the argv0 location, | |
| // so transform the latter now. | |
| arg = location; | |
| } else { | |
| arg = commandLine[j]; | |
| } | |
| cmd += " "; | |
| if (this->OldStyle) { | |
| cmd += escapeForShellOldStyle(arg); | |
| } else { | |
| cmd += | |
| this->LG->EscapeForShell(arg, this->MakeVars, false, false, | |
| this->MakeVars && this->LG->IsNinjaMulti()); | |
| } | |
| } | |
| } | |
| std::string cmCustomCommandGenerator::GetDepfile() const | |
| { | |
| auto const& depfile = this->CC->GetDepfile(); | |
| if (depfile.empty()) { | |
| return ""; | |
| } | |
| cmGeneratorExpression ge(*this->LG->GetCMakeInstance(), | |
| this->CC->GetBacktrace()); | |
| return EvaluateDepfile(depfile, ge, this->LG, this->OutputConfig); | |
| } | |
| std::string cmCustomCommandGenerator::GetFullDepfile() const | |
| { | |
| std::string depfile = this->GetDepfile(); | |
| if (depfile.empty()) { | |
| return ""; | |
| } | |
| if (!cmSystemTools::FileIsFullPath(depfile)) { | |
| depfile = cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', depfile); | |
| } | |
| return cmSystemTools::CollapseFullPath(depfile); | |
| } | |
| std::string cmCustomCommandGenerator::GetInternalDepfileName( | |
| std::string const& /*config*/, std::string const& depfile) const | |
| { | |
| cmCryptoHash hash(cmCryptoHash::AlgoSHA256); | |
| std::string extension; | |
| switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) { | |
| case cmDepfileFormat::GccDepfile: | |
| case cmDepfileFormat::MakeDepfile: | |
| extension = ".d"; | |
| break; | |
| case cmDepfileFormat::MSBuildAdditionalInputs: | |
| extension = ".AdditionalInputs"; | |
| break; | |
| } | |
| return cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/d/", | |
| hash.HashString(depfile), extension); | |
| } | |
| std::string cmCustomCommandGenerator::GetInternalDepfile() const | |
| { | |
| std::string depfile = this->GetFullDepfile(); | |
| if (depfile.empty()) { | |
| return ""; | |
| } | |
| if (this->ComputeInternalDepfile) { | |
| return this->ComputeInternalDepfile(this->OutputConfig, depfile); | |
| } | |
| return this->GetInternalDepfileName(this->OutputConfig, depfile); | |
| } | |
| cm::optional<std::string> cmCustomCommandGenerator::GetComment() const | |
| { | |
| char const* comment = this->CC->GetComment(); | |
| if (!comment) { | |
| return cm::nullopt; | |
| } | |
| if (!*comment) { | |
| return std::string(); | |
| } | |
| cmGeneratorExpression ge(*this->LG->GetCMakeInstance(), | |
| this->CC->GetBacktrace()); | |
| return EvaluateComment(comment, ge, this->LG, this->OutputConfig); | |
| } | |
| std::string cmCustomCommandGenerator::GetWorkingDirectory() const | |
| { | |
| return this->WorkingDirectory; | |
| } | |
| std::vector<std::string> const& cmCustomCommandGenerator::GetOutputs() const | |
| { | |
| return this->Outputs; | |
| } | |
| std::vector<std::string> const& cmCustomCommandGenerator::GetByproducts() const | |
| { | |
| return this->Byproducts; | |
| } | |
| std::vector<std::string> const& cmCustomCommandGenerator::GetDepends() const | |
| { | |
| return this->Depends; | |
| } | |
| std::set<BT<std::pair<std::string, bool>>> const& | |
| cmCustomCommandGenerator::GetUtilities() const | |
| { | |
| return this->Utilities; | |
| } |