| /* 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; | |
| } |