CMake/Source/cmCustomCommandGenerator.cxx
Ben Boeckel fd0ba705ce add_custom_command: check if a relative path should be an in-source path
This still is broken for dependencies on generated paths where they get
generated to the source directory rather than the build directory
however, but there's no way to determine that is the case.

Fixes: #20194
2020-01-16 13:54:26 -05:00

260 lines
7.7 KiB
C++

/* 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 <cmext/algorithm>
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
namespace {
void AppendPaths(const std::vector<std::string>& inputs,
cmGeneratorExpression const& ge, cmLocalGenerator* lg,
std::string const& config, std::vector<std::string>& output)
{
for (std::string const& in : inputs) {
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(in);
std::vector<std::string> result =
cmExpandedList(cge->Evaluate(lg, config));
for (std::string& it : result) {
cmSystemTools::ConvertToUnixSlashes(it);
if (cmSystemTools::FileIsFullPath(it)) {
it = cmSystemTools::CollapseFullPath(
it, lg->GetMakefile()->GetHomeOutputDirectory());
}
}
cm::append(output, result);
}
}
}
cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
std::string config,
cmLocalGenerator* lg)
: CC(cc)
, Config(std::move(config))
, LG(lg)
, OldStyle(cc.GetEscapeOldStyle())
, MakeVars(cc.GetEscapeAllowMakeVars())
, EmulatorsWithArguments(cc.GetCommandLines().size())
{
cmGeneratorExpression ge(cc.GetBacktrace());
const cmCustomCommandLines& cmdlines = this->CC.GetCommandLines();
for (cmCustomCommandLine const& cmdline : cmdlines) {
cmCustomCommandLine argv;
for (std::string const& clarg : cmdline) {
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(clarg);
std::string parsed_arg = cge->Evaluate(this->LG, this->Config);
if (this->CC.GetCommandExpandLists()) {
cm::append(argv, cmExpandedList(parsed_arg));
} else {
argv.push_back(std::move(parsed_arg));
}
}
// 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?
if (argv.empty()) {
argv.emplace_back();
}
this->CommandLines.push_back(std::move(argv));
}
AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config,
this->Byproducts);
AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends);
const std::string& workingdirectory = this->CC.GetWorkingDirectory();
if (!workingdirectory.empty()) {
std::unique_ptr<cmCompiledGeneratorExpression> cge =
ge.Parse(workingdirectory);
this->WorkingDirectory = cge->Evaluate(this->LG, this->Config);
// 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->CC.GetCommandLines().size());
}
void cmCustomCommandGenerator::FillEmulatorsWithArguments()
{
if (!this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
return;
}
for (unsigned int c = 0; c < this->GetNumberOfCommands(); ++c) {
std::string const& argv0 = this->CommandLines[c][0];
cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
if (target && target->GetType() == cmStateEnums::EXECUTABLE &&
!target->IsImported()) {
const char* emulator_property =
target->GetProperty("CROSSCOMPILING_EMULATOR");
if (!emulator_property) {
continue;
}
cmExpandList(emulator_property, 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
{
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->Config);
}
return nullptr;
}
bool cmCustomCommandGenerator::HasOnlyEmptyCommandLines() const
{
for (size_t i = 0; i < this->CommandLines.size(); ++i) {
for (size_t j = 0; j < this->CommandLines[i].size(); ++j) {
if (!this->CommandLines[i][j].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];
}
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());
}
}
}
const char* cmCustomCommandGenerator::GetComment() const
{
return this->CC.GetComment();
}
std::string cmCustomCommandGenerator::GetWorkingDirectory() const
{
return this->WorkingDirectory;
}
std::vector<std::string> const& cmCustomCommandGenerator::GetOutputs() const
{
return this->CC.GetOutputs();
}
std::vector<std::string> const& cmCustomCommandGenerator::GetByproducts() const
{
return this->Byproducts;
}
std::vector<std::string> const& cmCustomCommandGenerator::GetDepends() const
{
return this->Depends;
}