blob: d6bb62b45df8d959877a9ca01c1c2987835ea259 [file]
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmAddCustomRuleCommand.h"
#include <algorithm>
#include <utility>
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#include "cmsys/RegularExpression.hxx"
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmRange.h"
#include "cmRule.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
namespace {
bool IsReservedName(std::string const& name)
{
static cmsys::RegularExpression reservedNameValidator("^[A-Z_.:+-]+$");
return reservedNameValidator.find(name);
}
template <typename Result>
class FromRuleArgumentParser : public cmArgumentParser<Result>
{
public:
FromRuleArgumentParser()
: cmArgumentParser<Result>()
{
this->Bind("CHAIN"_s, &Result::Chain)
.Bind("OVERRIDE"_s, &Result::Override)
.BindParsedKeywords(&Result::ParsedKeywords);
}
};
}
bool cmAddCustomRuleCommand(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
if (args.size() < 2) {
status.SetError("called with incorrect number of arguments");
return false;
}
// keywords
static cm::static_string_view const COMMAND{ "COMMAND"_s };
static cm::static_string_view const OUTPUT{ "OUTPUT"_s };
static cm::static_string_view const DEPENDS{ "DEPENDS"_s };
static cm::static_string_view const DEPFILE{ "DEPFILE"_s };
static cm::static_string_view const BYPRODUCTS{ "BYPRODUCTS"_s };
static cm::static_string_view const GLOBAL{ "GLOBAL"_s };
static cm::static_string_view const CONFIGURATOR{ "CONFIGURATOR"_s };
static cm::static_string_view const FOR_FILE_SET{ "FOR_FILE_SET"_s };
static cm::static_string_view const FOR_SOURCE{ "FOR_SOURCE"_s };
static cm::static_string_view const FROM_RULE{ "FROM_RULE"_s };
static cm::string_view const Keywords[]{ COMMAND, OUTPUT, DEPENDS,
DEPFILE, BYPRODUCTS, GLOBAL,
CONFIGURATOR, FROM_RULE };
cmMakefile& mf = status.GetMakefile();
std::string const& ruleName = args[0];
// Check the rule name.
if (cm::contains(Keywords, ruleName)) {
status.SetError("rule name is missing.");
return false;
}
// check name validity
if (IsReservedName(ruleName)) {
status.SetError("names in all uppercase are reserved for CMake.");
return false;
}
if (!cmGeneratorExpression::IsValidTargetName(ruleName)) {
status.SetError(cmStrCat("invalid name for RULE: ", ruleName, '.'));
return false;
}
// Make sure the rule does not already exist.
if (mf.FindRuleToUse(ruleName)) {
status.SetError(
cmStrCat("cannot create RULE \"", ruleName,
"\" because another RULE with the same name already exists."));
return false;
}
struct BaseArguments : public ArgumentParser::ParseResult
{
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
Configurators;
bool Global = false;
std::vector<cm::string_view> ParsedKeywords;
cm::RuleScope GetScope()
{
return this->Global ? cm::RuleScope::Global : cm::RuleScope::Local;
}
};
if (cm::contains(args, FROM_RULE)) {
struct Arguments : public BaseArguments
{
std::string FromRule;
};
std::vector<std::string> unexpectedArgs;
auto parser = cmArgumentParser<Arguments>{}
.Bind(FROM_RULE, &Arguments::FromRule)
.Bind(CONFIGURATOR, &Arguments::Configurators)
.Bind(GLOBAL, &Arguments::Global)
.BindParsedKeywords(&Arguments::ParsedKeywords);
auto parsedArgs =
parser.Parse(cmMakeRange(args).advance(1), &unexpectedArgs);
// do various checks for arguments consistency
if (!parsedArgs.Check("", &unexpectedArgs, status)) {
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if ((std::count(parsedArgs.ParsedKeywords.cbegin(),
parsedArgs.ParsedKeywords.cend(), FROM_RULE) > 1) ||
(std::count(parsedArgs.ParsedKeywords.cbegin(),
parsedArgs.ParsedKeywords.cend(), CONFIGURATOR) > 1)) {
status.SetError(
"only one occurrence of \"FROM_RULE\" or \"CONFIGURATOR\" "
"options is allowed.");
return false;
}
// Configurator syntax: <configurator> (CHAIN|OVERRIDE)
struct ConfiguratorArguments : public ArgumentParser::ParseResult
{
std::string Configurator;
bool Chain = false;
bool Override = false;
std::vector<cm::string_view> ParsedKeywords;
};
// configurators syntax: FOR_FILE_SET <configurator> <options>
// FOR_SOURCE <configurator> <options>
struct ConfiguratorsArguments : public ArgumentParser::ParseResult
{
cm::optional<ConfiguratorArguments> ForFileSet;
cm::optional<ConfiguratorArguments> ForSource;
} parsedConfigurators;
if (parsedArgs.Configurators) {
auto fileSetConfiguratorParser =
FromRuleArgumentParser<ConfiguratorArguments>{}.Bind(
FOR_FILE_SET, &ConfiguratorArguments::Configurator);
auto sourceConfiguratorParser =
FromRuleArgumentParser<ConfiguratorArguments>{}.Bind(
FOR_SOURCE, &ConfiguratorArguments::Configurator);
auto configuratorsParser =
cmArgumentParser<ConfiguratorsArguments>{}
.BindSubParser(FOR_FILE_SET, fileSetConfiguratorParser,
&ConfiguratorsArguments::ForFileSet)
.BindSubParser(FOR_SOURCE, sourceConfiguratorParser,
&ConfiguratorsArguments::ForSource);
unexpectedArgs.clear();
configuratorsParser.Parse(parsedConfigurators, *parsedArgs.Configurators,
&unexpectedArgs);
// do various checks for arguments consistency
if (!parsedConfigurators.Check("", &unexpectedArgs, status)) {
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (!parsedConfigurators.ForFileSet && !parsedConfigurators.ForSource) {
status.SetError(
cmStrCat("cannot create RULE \"", ruleName,
"\" because the options \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
"are expected for the \"CONFIGURATOR\" option."));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if ((parsedConfigurators.ForFileSet &&
std::count(parsedConfigurators.ForFileSet->ParsedKeywords.cbegin(),
parsedConfigurators.ForFileSet->ParsedKeywords.cend(),
FOR_FILE_SET) > 1) ||
(parsedConfigurators.ForSource &&
std::count(parsedConfigurators.ForSource->ParsedKeywords.cbegin(),
parsedConfigurators.ForSource->ParsedKeywords.cend(),
FOR_SOURCE) > 1)) {
status.SetError(
"only one occurrence of \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
"sub-options of \"CONFIGURATOR\" option is allowed.");
return false;
}
auto checkConfigurator =
[&status, &mf,
&ruleName](cm::optional<ConfiguratorArguments>& configurator,
cm::string_view type) -> bool {
if (!configurator) {
return true;
}
ConfiguratorArguments& ca = configurator.value();
cm::optional<cmStateEnums::CommandType> commandType =
mf.GetState()->GetCommandType(ca.Configurator);
if (!commandType) {
status.SetError(cmStrCat("command specified for \"", type,
"\" does not exist: ", ca.Configurator,
'.'));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (*commandType != cmStateEnums::CommandType::Function) {
status.SetError(cmStrCat("command specified for \"", type,
"\" is not a function: ", ca.Configurator,
'.'));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (ca.Chain && ca.Override) {
status.SetError(cmStrCat("cannot create RULE \"", ruleName,
"\" because the \"CHAIN\" and \"OVERRIDE\" "
"options of CONFIGURATOR \"",
ca.Configurator,
"\" are mutually exclusive."));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (!ca.Chain && !ca.Override) {
ca.Override = true;
}
return true;
};
if (!checkConfigurator(parsedConfigurators.ForFileSet, FOR_FILE_SET) ||
!checkConfigurator(parsedConfigurators.ForSource, FOR_SOURCE)) {
return false;
}
}
cmRule const* rule = mf.FindRuleToUse(parsedArgs.FromRule);
if (!rule) {
status.SetError(cmStrCat("cannot create RULE \"", ruleName,
"\" because the RULE \"", parsedArgs.FromRule,
"\" does not exist or is not accessible."));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
auto newRule = cm::make_unique<cmSpecializedRule>(mf, ruleName, *rule,
parsedArgs.GetScope());
if (parsedArgs.Configurators) {
if (parsedConfigurators.ForFileSet) {
newRule->SetConfigurator(
cmRule::ConfiguratorType::FileSet,
std::move(parsedConfigurators.ForFileSet->Configurator),
parsedConfigurators.ForFileSet->Chain
? cmRule::ChainConfigurators::Yes
: cmRule::ChainConfigurators::No);
}
if (parsedConfigurators.ForSource) {
newRule->SetConfigurator(
cmRule::ConfiguratorType::Source,
std::move(parsedConfigurators.ForSource->Configurator),
parsedConfigurators.ForSource->Chain
? cmRule::ChainConfigurators::Yes
: cmRule::ChainConfigurators::No);
}
}
mf.AddRule(std::move(newRule));
return true;
}
struct Arguments : public BaseArguments
{
ArgumentParser::NonEmpty<std::vector<std::string>> Output;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
Byproducts;
ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> Commands;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Depends;
cm::optional<std::string> Depfile;
};
std::vector<std::string> unexpectedArgs;
auto parser = cmArgumentParser<Arguments>{}
.Bind(OUTPUT, &Arguments::Output)
.Bind(BYPRODUCTS, &Arguments::Byproducts)
.Bind(COMMAND, &Arguments::Commands)
.Bind(DEPENDS, &Arguments::Depends)
.Bind(DEPFILE, &Arguments::Depfile)
.Bind(CONFIGURATOR, &Arguments::Configurators)
.Bind(GLOBAL, &Arguments::Global)
.BindParsedKeywords(&Arguments::ParsedKeywords);
auto parsedArgs =
parser.Parse(cmMakeRange(args).advance(1), &unexpectedArgs);
// do various checks for arguments consistency
if (!parsedArgs.Check("", &unexpectedArgs, status)) {
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (parsedArgs.Commands.empty() || parsedArgs.Output.empty()) {
status.SetError(cmStrCat(
"cannot create RULE \"", ruleName,
"\" because the mandatory options \"COMMAND\" or \"OUTPUT\" are "
"missing."));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if ((std::count(parsedArgs.ParsedKeywords.cbegin(),
parsedArgs.ParsedKeywords.cend(), DEPFILE) > 1) ||
(std::count(parsedArgs.ParsedKeywords.cbegin(),
parsedArgs.ParsedKeywords.cend(), CONFIGURATOR) > 1)) {
status.SetError("only one occurrence of \"DEPFILE\" or \"CONFIGURATOR\" "
"options is allowed.");
return false;
}
struct ConfiguratorsArguments : public ArgumentParser::ParseResult
{
cm::optional<std::string> ForFileSet;
cm::optional<std::string> ForSource;
std::vector<cm::string_view> ParsedKeywords;
} parsedConfigurators;
if (parsedArgs.Configurators) {
// parse the arguments of CONFIGURATOR option
// CONFIGURATOR syntax: FOR_FILE_SET <configurator>
// FOR_SOURCE <configurator>
auto configuratorsParser =
cmArgumentParser<ConfiguratorsArguments>{}
.Bind(FOR_FILE_SET, &ConfiguratorsArguments::ForFileSet)
.Bind(FOR_SOURCE, &ConfiguratorsArguments::ForSource)
.BindParsedKeywords(&ConfiguratorsArguments::ParsedKeywords);
unexpectedArgs.clear();
configuratorsParser.Parse(parsedConfigurators, *parsedArgs.Configurators,
&unexpectedArgs);
// do various checks for arguments consistency
if (!parsedConfigurators.Check("", &unexpectedArgs, status)) {
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if ((std::count(parsedConfigurators.ParsedKeywords.cbegin(),
parsedConfigurators.ParsedKeywords.cend(),
FOR_FILE_SET) > 1) ||
(std::count(parsedConfigurators.ParsedKeywords.cbegin(),
parsedConfigurators.ParsedKeywords.cend(),
FOR_SOURCE) > 1)) {
status.SetError(
"only one occurrence of \"FOR_FILE_SET\" or \"FOR_SOURCE\" "
"sub-options of \"CONFIGURATOR\" option is allowed.");
return false;
}
if (!parsedConfigurators.ForFileSet && !parsedConfigurators.ForSource) {
status.SetError(cmStrCat(
"cannot create RULE \"", ruleName,
"\" because the options \"FOR_FILE_SET\" or \"FOR_SOURCE\" are "
"expected for the \"CONFIGURATOR\" option."));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
auto checkConfigurator =
[&status, &mf](cm::optional<std::string> const& configurator,
cm::string_view type) -> bool {
if (!configurator) {
return true;
}
auto commandType = mf.GetState()->GetCommandType(*configurator);
if (!commandType) {
status.SetError(cmStrCat("command specified for \"", type,
"\" does not exist: ", *configurator, '.'));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
if (*commandType != cmStateEnums::CommandType::Function) {
status.SetError(cmStrCat("command specified for \"", type,
"\" is not a function: ", *configurator,
'.'));
cmSystemTools::SetFatalErrorOccurred();
return false;
}
return true;
};
if (!checkConfigurator(parsedConfigurators.ForFileSet, FOR_FILE_SET) ||
!checkConfigurator(parsedConfigurators.ForSource, FOR_SOURCE)) {
return false;
}
}
auto rule =
cm::make_unique<cmCustomRule>(mf, ruleName, parsedArgs.Commands,
parsedArgs.Output, parsedArgs.GetScope());
if (parsedArgs.Byproducts) {
rule->SetByproducts(std::move(*parsedArgs.Byproducts));
}
if (parsedArgs.Depends) {
rule->SetDepends(std::move(*parsedArgs.Depends));
}
if (parsedArgs.Depfile) {
rule->SetDepfile(std::move(*parsedArgs.Depfile));
}
if (parsedArgs.Configurators) {
if (parsedConfigurators.ForFileSet) {
rule->SetConfigurator(cmRule::ConfiguratorType::FileSet,
std::move(*parsedConfigurators.ForFileSet));
}
if (parsedConfigurators.ForSource) {
rule->SetConfigurator(cmRule::ConfiguratorType::Source,
std::move(*parsedConfigurators.ForSource));
}
}
mf.AddRule(std::move(rule));
return true;
}