You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
17 KiB
523 lines
17 KiB
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt 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(const char* 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(const std::string&, const std::string&)>
|
|
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;
|
|
|
|
const cmCustomCommandLines& 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);
|
|
|
|
const std::string& 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];
|
|
}
|
|
|
|
const char* 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 (const char* location = this->GetArgv0Location(c)) {
|
|
return std::string(location);
|
|
}
|
|
|
|
return this->CommandLines[c][0];
|
|
}
|
|
|
|
static std::string escapeForShellOldStyle(const std::string& 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 (const char* 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 (const char* 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
|
|
{
|
|
const auto& 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(
|
|
const std::string& /*config*/, const std::string& 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
|
|
{
|
|
const char* 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;
|
|
}
|