/* 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 #include #include #include #include #include #include #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 class FromRuleArgumentParser : public cmArgumentParser { public: FromRuleArgumentParser() : cmArgumentParser() { this->Bind("CHAIN"_s, &Result::Chain) .Bind("OVERRIDE"_s, &Result::Override) .BindParsedKeywords(&Result::ParsedKeywords); } }; } bool cmAddCustomRuleCommand(std::vector 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>> Configurators; bool Global = false; std::vector 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 unexpectedArgs; auto parser = cmArgumentParser{} .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: (CHAIN|OVERRIDE) struct ConfiguratorArguments : public ArgumentParser::ParseResult { std::string Configurator; bool Chain = false; bool Override = false; std::vector ParsedKeywords; }; // configurators syntax: FOR_FILE_SET // FOR_SOURCE struct ConfiguratorsArguments : public ArgumentParser::ParseResult { cm::optional ForFileSet; cm::optional ForSource; } parsedConfigurators; if (parsedArgs.Configurators) { auto fileSetConfiguratorParser = FromRuleArgumentParser{}.Bind( FOR_FILE_SET, &ConfiguratorArguments::Configurator); auto sourceConfiguratorParser = FromRuleArgumentParser{}.Bind( FOR_SOURCE, &ConfiguratorArguments::Configurator); auto configuratorsParser = cmArgumentParser{} .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& configurator, cm::string_view type) -> bool { if (!configurator) { return true; } ConfiguratorArguments& ca = configurator.value(); cm::optional 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(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> Output; cm::optional>> Byproducts; ArgumentParser::NonEmpty>> Commands; cm::optional>> Depends; cm::optional Depfile; }; std::vector unexpectedArgs; auto parser = cmArgumentParser{} .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 ForFileSet; cm::optional ForSource; std::vector ParsedKeywords; } parsedConfigurators; if (parsedArgs.Configurators) { // parse the arguments of CONFIGURATOR option // CONFIGURATOR syntax: FOR_FILE_SET // FOR_SOURCE auto configuratorsParser = cmArgumentParser{} .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 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(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; }