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.
cmake/Source/cmAddCustomCommandCommand.cxx

637 lines
20 KiB

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmAddCustomCommandCommand.h"
#include <algorithm>
#include <iterator>
#include <set>
#include <sstream>
#include <unordered_set>
#include <utility>
#include <cm/memory>
#include <cmext/string_view>
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmCustomCommandTypes.h"
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmValue.h"
bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
/* Let's complain at the end of this function about the lack of a particular
arg. For the moment, let's say that COMMAND, and either TARGET or SOURCE
are required.
*/
if (args.size() < 4) {
status.SetError("called with wrong number of arguments.");
return false;
}
cmMakefile& mf = status.GetMakefile();
std::string source;
std::string target;
std::string main_dependency;
std::string working;
std::string depfile;
std::string job_pool;
std::string job_server_aware;
std::string comment_buffer;
const char* comment = nullptr;
std::vector<std::string> depends;
std::vector<std::string> outputs;
std::vector<std::string> output;
std::vector<std::string> byproducts;
bool verbatim = false;
bool append = false;
bool uses_terminal = false;
bool command_expand_lists = false;
bool depends_explicit_only =
mf.IsOn("CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY");
bool codegen = false;
std::string implicit_depends_lang;
cmImplicitDependsList implicit_depends;
// Accumulate one command line at a time.
cmCustomCommandLine currentLine;
// Save all command lines.
cmCustomCommandLines commandLines;
cmCustomCommandType cctype = cmCustomCommandType::POST_BUILD;
enum tdoing
{
doing_source,
doing_command,
doing_target,
doing_depends,
doing_implicit_depends_lang,
doing_implicit_depends_file,
doing_main_dependency,
doing_output,
doing_outputs,
doing_byproducts,
doing_comment,
doing_working_directory,
doing_depfile,
doing_job_pool,
doing_job_server_aware,
doing_nothing
};
tdoing doing = doing_nothing;
#define MAKE_STATIC_KEYWORD(KEYWORD) \
static const std::string key##KEYWORD = #KEYWORD
MAKE_STATIC_KEYWORD(APPEND);
MAKE_STATIC_KEYWORD(ARGS);
MAKE_STATIC_KEYWORD(BYPRODUCTS);
MAKE_STATIC_KEYWORD(COMMAND);
MAKE_STATIC_KEYWORD(COMMAND_EXPAND_LISTS);
MAKE_STATIC_KEYWORD(COMMENT);
MAKE_STATIC_KEYWORD(DEPENDS);
MAKE_STATIC_KEYWORD(DEPFILE);
MAKE_STATIC_KEYWORD(IMPLICIT_DEPENDS);
MAKE_STATIC_KEYWORD(JOB_POOL);
MAKE_STATIC_KEYWORD(JOB_SERVER_AWARE);
MAKE_STATIC_KEYWORD(MAIN_DEPENDENCY);
MAKE_STATIC_KEYWORD(OUTPUT);
MAKE_STATIC_KEYWORD(OUTPUTS);
MAKE_STATIC_KEYWORD(POST_BUILD);
MAKE_STATIC_KEYWORD(PRE_BUILD);
MAKE_STATIC_KEYWORD(PRE_LINK);
MAKE_STATIC_KEYWORD(SOURCE);
MAKE_STATIC_KEYWORD(TARGET);
MAKE_STATIC_KEYWORD(USES_TERMINAL);
MAKE_STATIC_KEYWORD(VERBATIM);
MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);
MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY);
MAKE_STATIC_KEYWORD(CODEGEN);
#undef MAKE_STATIC_KEYWORD
static std::unordered_set<std::string> const keywords{
keyAPPEND,
keyARGS,
keyBYPRODUCTS,
keyCOMMAND,
keyCOMMAND_EXPAND_LISTS,
keyCOMMENT,
keyDEPENDS,
keyDEPFILE,
keyIMPLICIT_DEPENDS,
keyJOB_POOL,
keyMAIN_DEPENDENCY,
keyOUTPUT,
keyOUTPUTS,
keyPOST_BUILD,
keyPRE_BUILD,
keyPRE_LINK,
keySOURCE,
keyJOB_SERVER_AWARE,
keyTARGET,
keyUSES_TERMINAL,
keyVERBATIM,
keyWORKING_DIRECTORY,
keyDEPENDS_EXPLICIT_ONLY,
keyCODEGEN
};
/* clang-format off */
static std::set<std::string> const supportedTargetKeywords{
keyARGS,
keyBYPRODUCTS,
keyCOMMAND,
keyCOMMAND_EXPAND_LISTS,
keyCOMMENT,
keyPOST_BUILD,
keyPRE_BUILD,
keyPRE_LINK,
keyTARGET,
keyUSES_TERMINAL,
keyVERBATIM,
keyWORKING_DIRECTORY
};
/* clang-format on */
static std::set<std::string> const supportedOutputKeywords{
keyAPPEND,
keyARGS,
keyBYPRODUCTS,
keyCODEGEN,
keyCOMMAND,
keyCOMMAND_EXPAND_LISTS,
keyCOMMENT,
keyDEPENDS,
keyDEPENDS_EXPLICIT_ONLY,
keyDEPFILE,
keyIMPLICIT_DEPENDS,
keyJOB_POOL,
keyJOB_SERVER_AWARE,
keyMAIN_DEPENDENCY,
keyOUTPUT,
keyUSES_TERMINAL,
keyVERBATIM,
keyWORKING_DIRECTORY
};
/* clang-format off */
static std::set<std::string> const supportedAppendKeywords{
keyAPPEND,
keyARGS,
keyCOMMAND,
keyCOMMENT, // Allowed but ignored
keyDEPENDS,
keyIMPLICIT_DEPENDS,
keyMAIN_DEPENDENCY, // Allowed but ignored
keyOUTPUT,
keyWORKING_DIRECTORY // Allowed but ignored
};
/* clang-format on */
std::set<std::string> keywordsSeen;
std::string const* keywordExpectingValue = nullptr;
auto const cmp0175 = mf.GetPolicyStatus(cmPolicies::CMP0175);
for (std::string const& copy : args) {
if (keywords.count(copy)) {
// Check if a preceding keyword expected a value but there wasn't one
if (keywordExpectingValue) {
std::string const msg =
cmStrCat("Keyword ", *keywordExpectingValue,
" requires a value, but none was given.");
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, '\n',
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
keywordExpectingValue = nullptr;
keywordsSeen.insert(copy);
if (copy == keySOURCE) {
doing = doing_source;
keywordExpectingValue = &keySOURCE;
} else if (copy == keyCOMMAND) {
doing = doing_command;
// Save the current command before starting the next command.
if (!currentLine.empty()) {
commandLines.push_back(currentLine);
currentLine.clear();
}
} else if (copy == keyPRE_BUILD) {
cctype = cmCustomCommandType::PRE_BUILD;
} else if (copy == keyPRE_LINK) {
cctype = cmCustomCommandType::PRE_LINK;
} else if (copy == keyPOST_BUILD) {
cctype = cmCustomCommandType::POST_BUILD;
} else if (copy == keyVERBATIM) {
verbatim = true;
} else if (copy == keyAPPEND) {
append = true;
} else if (copy == keyUSES_TERMINAL) {
uses_terminal = true;
} else if (copy == keyCOMMAND_EXPAND_LISTS) {
command_expand_lists = true;
} else if (copy == keyDEPENDS_EXPLICIT_ONLY) {
depends_explicit_only = true;
} else if (copy == keyCODEGEN) {
codegen = true;
} else if (copy == keyTARGET) {
doing = doing_target;
keywordExpectingValue = &keyTARGET;
} else if (copy == keyARGS) {
// Ignore this old keyword.
} else if (copy == keyDEPENDS) {
doing = doing_depends;
} else if (copy == keyOUTPUTS) {
doing = doing_outputs;
} else if (copy == keyOUTPUT) {
doing = doing_output;
keywordExpectingValue = &keyOUTPUT;
} else if (copy == keyBYPRODUCTS) {
doing = doing_byproducts;
} else if (copy == keyWORKING_DIRECTORY) {
doing = doing_working_directory;
keywordExpectingValue = &keyWORKING_DIRECTORY;
} else if (copy == keyMAIN_DEPENDENCY) {
doing = doing_main_dependency;
keywordExpectingValue = &keyMAIN_DEPENDENCY;
} else if (copy == keyIMPLICIT_DEPENDS) {
doing = doing_implicit_depends_lang;
} else if (copy == keyCOMMENT) {
doing = doing_comment;
keywordExpectingValue = &keyCOMMENT;
} else if (copy == keyDEPFILE) {
doing = doing_depfile;
if (!mf.GetGlobalGenerator()->SupportsCustomCommandDepfile()) {
status.SetError(cmStrCat("Option DEPFILE not supported by ",
mf.GetGlobalGenerator()->GetName()));
return false;
}
keywordExpectingValue = &keyDEPFILE;
} else if (copy == keyJOB_POOL) {
doing = doing_job_pool;
keywordExpectingValue = &keyJOB_POOL;
} else if (copy == keyJOB_SERVER_AWARE) {
doing = doing_job_server_aware;
keywordExpectingValue = &keyJOB_SERVER_AWARE;
}
} else {
keywordExpectingValue = nullptr; // Value is being processed now
std::string filename;
switch (doing) {
case doing_output:
case doing_outputs:
case doing_byproducts:
if (!cmSystemTools::FileIsFullPath(copy) &&
cmGeneratorExpression::Find(copy) != 0) {
// This is an output to be generated, so it should be
// under the build tree.
filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/');
}
filename += copy;
cmSystemTools::ConvertToUnixSlashes(filename);
break;
case doing_source:
// We do not want to convert the argument to SOURCE because
// that option is only available for backward compatibility.
// Old-style use of this command may use the SOURCE==TARGET
// trick which we must preserve. If we convert the source
// to a full path then it will no longer equal the target.
default:
break;
}
if (cmSystemTools::FileIsFullPath(filename)) {
filename = cmSystemTools::CollapseFullPath(filename);
}
switch (doing) {
case doing_depfile:
depfile = copy;
break;
case doing_job_pool:
job_pool = copy;
break;
case doing_job_server_aware:
job_server_aware = copy;
break;
case doing_working_directory:
working = copy;
break;
case doing_source:
source = copy;
break;
case doing_output:
output.push_back(filename);
break;
case doing_main_dependency:
main_dependency = copy;
break;
case doing_implicit_depends_lang:
implicit_depends_lang = copy;
doing = doing_implicit_depends_file;
break;
case doing_implicit_depends_file: {
// An implicit dependency starting point is also an
// explicit dependency.
std::string dep = copy;
// Upfront path conversion is correct because Genex
// are not supported.
cmSystemTools::ConvertToUnixSlashes(dep);
depends.push_back(dep);
// Add the implicit dependency language and file.
implicit_depends.emplace_back(implicit_depends_lang, dep);
// Switch back to looking for a language.
doing = doing_implicit_depends_lang;
} break;
case doing_command:
currentLine.push_back(copy);
break;
case doing_target:
target = copy;
break;
case doing_depends:
depends.push_back(copy);
break;
case doing_outputs:
outputs.push_back(filename);
break;
case doing_byproducts:
byproducts.push_back(filename);
break;
case doing_comment:
if (!comment_buffer.empty()) {
std::string const msg =
"COMMENT requires exactly one argument, but multiple values "
"or COMMENT keywords have been given.";
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, '\n',
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
comment_buffer = copy;
comment = comment_buffer.c_str();
break;
default:
status.SetError("Wrong syntax. Unknown type of argument.");
return false;
}
}
}
// Store the last command line finished.
if (!currentLine.empty()) {
commandLines.push_back(currentLine);
currentLine.clear();
}
// At this point we could complain about the lack of arguments. For
// the moment, let's say that COMMAND, TARGET are always required.
if (output.empty() && target.empty()) {
status.SetError("Wrong syntax. A TARGET or OUTPUT must be specified.");
return false;
}
if (source.empty() && !target.empty() && !output.empty()) {
status.SetError(
"Wrong syntax. A TARGET and OUTPUT can not both be specified.");
return false;
}
if (append && output.empty()) {
status.SetError("given APPEND option with no OUTPUT.");
return false;
}
if (!implicit_depends.empty() && !depfile.empty() &&
mf.GetGlobalGenerator()->GetName() != "Ninja") {
// Makefiles generators does not support both at the same time
status.SetError("IMPLICIT_DEPENDS and DEPFILE can not both be specified.");
return false;
}
if (codegen) {
if (output.empty()) {
status.SetError("CODEGEN requires at least 1 OUTPUT.");
return false;
}
if (append) {
status.SetError("CODEGEN may not be used with APPEND.");
return false;
}
if (!implicit_depends.empty()) {
status.SetError("CODEGEN is not compatible with IMPLICIT_DEPENDS.");
return false;
}
if (mf.GetPolicyStatus(cmPolicies::CMP0171) != cmPolicies::NEW) {
status.SetError("CODEGEN option requires policy CMP0171 be set to NEW!");
return false;
}
}
// Check for an append request.
if (append) {
std::vector<std::string> unsupportedKeywordsUsed;
std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
supportedAppendKeywords.begin(),
supportedAppendKeywords.end(),
std::back_inserter(unsupportedKeywordsUsed));
if (!unsupportedKeywordsUsed.empty()) {
std::string const msg =
cmJoin(unsupportedKeywordsUsed, ", "_s,
"The following keywords are not supported when using "
"APPEND with add_custom_command(OUTPUT): "_s);
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, ".\n",
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends,
commandLines);
return true;
}
if (uses_terminal && !job_pool.empty()) {
status.SetError("JOB_POOL is shadowed by USES_TERMINAL.");
return false;
}
// Choose which mode of the command to use.
auto cc = cm::make_unique<cmCustomCommand>();
cc->SetByproducts(byproducts);
cc->SetCommandLines(commandLines);
cc->SetComment(comment);
cc->SetWorkingDirectory(working.c_str());
cc->SetEscapeOldStyle(!verbatim);
cc->SetUsesTerminal(uses_terminal);
cc->SetDepfile(depfile);
cc->SetJobPool(job_pool);
cc->SetJobserverAware(cmIsOn(job_server_aware));
cc->SetCommandExpandLists(command_expand_lists);
cc->SetDependsExplicitOnly(depends_explicit_only);
if (source.empty() && output.empty()) {
// Source is empty, use the target.
if (commandLines.empty()) {
std::string const msg = "At least one COMMAND must be given.";
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, '\n',
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
std::vector<std::string> unsupportedKeywordsUsed;
std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
supportedTargetKeywords.begin(),
supportedTargetKeywords.end(),
std::back_inserter(unsupportedKeywordsUsed));
if (!unsupportedKeywordsUsed.empty()) {
std::string const msg =
cmJoin(unsupportedKeywordsUsed, ", "_s,
"The following keywords are not supported when using "
"add_custom_command(TARGET): "_s);
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, ".\n",
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
auto const prePostCount = keywordsSeen.count(keyPRE_BUILD) +
keywordsSeen.count(keyPRE_LINK) + keywordsSeen.count(keyPOST_BUILD);
if (prePostCount != 1) {
std::string msg =
"Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given.";
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
msg += " Assuming ";
switch (cctype) {
case cmCustomCommandType::PRE_BUILD:
msg += "PRE_BUILD";
break;
case cmCustomCommandType::PRE_LINK:
msg += "PRE_LINK";
break;
case cmCustomCommandType::POST_BUILD:
msg += "POST_BUILD";
}
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, " to preserve backward compatibility.\n",
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
mf.AddCustomCommandToTarget(target, cctype, std::move(cc));
} else if (target.empty()) {
// Target is empty, use the output.
std::vector<std::string> unsupportedKeywordsUsed;
std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
supportedOutputKeywords.begin(),
supportedOutputKeywords.end(),
std::back_inserter(unsupportedKeywordsUsed));
if (!unsupportedKeywordsUsed.empty()) {
std::string const msg =
cmJoin(unsupportedKeywordsUsed, ", "_s,
"The following keywords are not supported when using "
"add_custom_command(OUTPUT): "_s);
if (cmp0175 == cmPolicies::NEW) {
mf.IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
if (cmp0175 == cmPolicies::WARN) {
mf.IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(msg, ".\n",
cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
}
}
cc->SetOutputs(output);
cc->SetMainDependency(main_dependency);
cc->SetDepends(depends);
cc->SetCodegen(codegen);
cc->SetImplicitDepends(implicit_depends);
mf.AddCustomCommandToOutput(std::move(cc));
} else {
if (!byproducts.empty()) {
status.SetError(
"BYPRODUCTS may not be specified with SOURCE signatures");
return false;
}
if (uses_terminal) {
status.SetError("USES_TERMINAL may not be used with SOURCE signatures");
return false;
}
bool issueMessage = true;
std::ostringstream e;
MessageType messageType = MessageType::AUTHOR_WARNING;
switch (mf.GetPolicyStatus(cmPolicies::CMP0050)) {
case cmPolicies::WARN:
e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0050) << "\n";
break;
case cmPolicies::OLD:
issueMessage = false;
break;
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::REQUIRED_IF_USED:
case cmPolicies::NEW:
messageType = MessageType::FATAL_ERROR;
break;
}
if (issueMessage) {
e << "The SOURCE signatures of add_custom_command are no longer "
"supported.";
mf.IssueMessage(messageType, e.str());
if (messageType == MessageType::FATAL_ERROR) {
return false;
}
}
// Use the old-style mode for backward compatibility.
mf.AddCustomCommandOldStyle(target, outputs, depends, source, commandLines,
comment);
}
return true;
}