diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h index 160dda0520..223c3ce699 100644 --- a/Source/cmArgumentParser.h +++ b/Source/cmArgumentParser.h @@ -394,6 +394,11 @@ public: this->Parse(result, args, unparsedArguments, pos); return result; } + + bool HasKeyword(cm::string_view key) const + { + return this->Bindings.Keywords.Find(key) != this->Bindings.Keywords.end(); + } }; template <> @@ -469,6 +474,11 @@ public: return parseResult; } + bool HasKeyword(cm::string_view key) const + { + return this->Bindings.Keywords.Find(key) != this->Bindings.Keywords.end(); + } + protected: using Base::Instance; using Base::BindKeywordMissingValue; diff --git a/Source/cmProjectCommand.cxx b/Source/cmProjectCommand.cxx index 0835b1250e..3decfffafb 100644 --- a/Source/cmProjectCommand.cxx +++ b/Source/cmProjectCommand.cxx @@ -22,6 +22,7 @@ #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" +#include "cmRange.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" @@ -35,7 +36,6 @@ void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name, struct ProjectArguments : ArgumentParser::ParseResult { - cm::optional ProjectName; cm::optional Version; cm::optional CompatVersion; cm::optional License; @@ -67,7 +67,6 @@ bool cmProjectCommand(std::vector const& args, ProjectArgumentParser parser; parser.BindKeywordMissingValue(missingValueKeywords) .BindParsedKeywords(parsedKeywords) - .Bind(0, prArgs.ProjectName) .Bind("VERSION"_s, prArgs.Version) .Bind("DESCRIPTION"_s, prArgs.Description) .Bind("HOMEPAGE_URL"_s, prArgs.HomepageURL) @@ -82,13 +81,24 @@ bool cmProjectCommand(std::vector const& args, parser.Bind("SPDX_LICENSE"_s, prArgs.License); } - parser.Parse(args, &unparsedArgs, 0); - - if (!prArgs.ProjectName) { + if (args.empty()) { status.SetError("PROJECT called with incorrect number of arguments"); return false; } + std::string const& projectName = args[0]; + if (parser.HasKeyword(projectName)) { + mf.IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat( + "project() called with '", projectName, + "' as first argument. The first parameter should be the project name, " + "not a keyword argument. See the cmake-commands(7) manual for correct " + "usage of the project() command.")); + } + + parser.Parse(cmMakeRange(args).advance(1), &unparsedArgs, 1); + if (mf.IsRootMakefile() && !mf.GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION")) { mf.IssueMessage( @@ -102,16 +112,16 @@ bool cmProjectCommand(std::vector const& args, return false; } - if (!IncludeByVariable( - status, "CMAKE_PROJECT_" + *prArgs.ProjectName + "_INCLUDE_BEFORE")) { + if (!IncludeByVariable(status, + "CMAKE_PROJECT_" + projectName + "_INCLUDE_BEFORE")) { return false; } - mf.SetProjectName(*prArgs.ProjectName); + mf.SetProjectName(projectName); cmPolicies::PolicyStatus cmp0180 = mf.GetPolicyStatus(cmPolicies::CMP0180); - std::string varName = cmStrCat(*prArgs.ProjectName, "_BINARY_DIR"_s); + std::string varName = cmStrCat(projectName, "_BINARY_DIR"_s); bool nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName); mf.AddCacheDefinition(varName, mf.GetCurrentBinaryDirectory(), "Value Computed by CMake", cmStateEnums::STATIC); @@ -119,7 +129,7 @@ bool cmProjectCommand(std::vector const& args, mf.AddDefinition(varName, mf.GetCurrentBinaryDirectory()); } - varName = cmStrCat(*prArgs.ProjectName, "_SOURCE_DIR"_s); + varName = cmStrCat(projectName, "_SOURCE_DIR"_s); nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName); mf.AddCacheDefinition(varName, mf.GetCurrentSourceDirectory(), "Value Computed by CMake", cmStateEnums::STATIC); @@ -130,11 +140,11 @@ bool cmProjectCommand(std::vector const& args, mf.AddDefinition("PROJECT_BINARY_DIR", mf.GetCurrentBinaryDirectory()); mf.AddDefinition("PROJECT_SOURCE_DIR", mf.GetCurrentSourceDirectory()); - mf.AddDefinition("PROJECT_NAME", *prArgs.ProjectName); + mf.AddDefinition("PROJECT_NAME", projectName); mf.AddDefinitionBool("PROJECT_IS_TOP_LEVEL", mf.IsRootMakefile()); - varName = cmStrCat(*prArgs.ProjectName, "_IS_TOP_LEVEL"_s); + varName = cmStrCat(projectName, "_IS_TOP_LEVEL"_s); nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName); mf.AddCacheDefinition(varName, mf.IsRootMakefile() ? "ON" : "OFF", "Value Computed by CMake", cmStateEnums::STATIC); @@ -142,7 +152,7 @@ bool cmProjectCommand(std::vector const& args, mf.AddDefinition(varName, mf.IsRootMakefile() ? "ON" : "OFF"); } - TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_NAME", *prArgs.ProjectName); + TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_NAME", projectName); std::set seenKeywords; for (cm::string_view keyword : parsedKeywords) { @@ -255,7 +265,7 @@ bool cmProjectCommand(std::vector const& args, auto createVariables = [&](cm::string_view var, std::string const& val) { mf.AddDefinition(cmStrCat("PROJECT_"_s, var), val); - mf.AddDefinition(cmStrCat(*prArgs.ProjectName, "_"_s, var), val); + mf.AddDefinition(cmStrCat(projectName, "_"_s, var), val); TopLevelCMakeVarCondSet(mf, cmStrCat("CMAKE_PROJECT_"_s, var), val); }; @@ -285,8 +295,8 @@ bool cmProjectCommand(std::vector const& args, return false; } - if (!IncludeByVariable( - status, "CMAKE_PROJECT_" + *prArgs.ProjectName + "_INCLUDE")) { + if (!IncludeByVariable(status, + "CMAKE_PROJECT_" + projectName + "_INCLUDE")) { return false; } diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx index 2dbff01da3..4bf6a40ca6 100644 --- a/Tests/CMakeLib/testArgumentParser.cxx +++ b/Tests/CMakeLib/testArgumentParser.cxx @@ -300,6 +300,11 @@ bool testArgumentParserDynamic() return result.Func4(key, arg); }; + cmArgumentParser parserDynamic; + parserDynamic.Bind("OPTION_1"_s, result.Option1); + ASSERT_TRUE(parserDynamic.HasKeyword("OPTION_1"_s)); + ASSERT_TRUE(!parserDynamic.HasKeyword("NOT_AN_OPTION"_s)); + static_cast(result) = cmArgumentParser{} .Bind(0, result.Pos0) @@ -424,6 +429,9 @@ BIND_TRAILING(parserTrailingDerivedStatic, DerivedTrailingPos); bool testArgumentParserStatic() { + ASSERT_TRUE(parserStatic.HasKeyword("OPTION_1"_s)); + ASSERT_TRUE(!parserStatic.HasKeyword("NOT_AN_OPTION"_s)); + std::vector unparsedArguments; Result const result = parserStatic.Parse(args, &unparsedArguments); if (!verifyResult(result, unparsedArguments)) { @@ -438,6 +446,9 @@ bool testArgumentParserStatic() bool testArgumentParserDerivedStatic() { + ASSERT_TRUE(parserDerivedStatic.HasKeyword("OPTION_1"_s)); + ASSERT_TRUE(!parserDerivedStatic.HasKeyword("NOT_AN_OPTION"_s)); + std::vector unparsedArguments; Derived const result = parserDerivedStatic.Parse(args, &unparsedArguments); if (!verifyResult(result, unparsedArguments)) { diff --git a/Tests/RunCMake/project/KeywordProjectName-stderr.txt b/Tests/RunCMake/project/KeywordProjectName-stderr.txt new file mode 100644 index 0000000000..a96ac320b7 --- /dev/null +++ b/Tests/RunCMake/project/KeywordProjectName-stderr.txt @@ -0,0 +1,31 @@ +CMake Warning \(dev\) at KeywordProjectName\.cmake:[0-9]+ \(project\): + project\(\) called with 'LANGUAGES' as first argument\. The first parameter + should be the project name, not a keyword argument\. See the + cmake-commands\(7\) manual for correct usage of the project\(\) command\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-dev to suppress it\. + +CMake Warning \(dev\) at KeywordProjectName\.cmake:[0-9]+ \(project\): + project\(\) called with 'VERSION' as first argument\. The first parameter + should be the project name, not a keyword argument\. See the + cmake-commands\(7\) manual for correct usage of the project\(\) command\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-dev to suppress it\. + +CMake Warning \(dev\) at KeywordProjectName\.cmake:[0-9]+ \(project\): + project\(\) called with 'DESCRIPTION' as first argument\. The first parameter + should be the project name, not a keyword argument\. See the + cmake-commands\(7\) manual for correct usage of the project\(\) command\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-dev to suppress it\. + +CMake Warning \(dev\) at KeywordProjectName\.cmake:[0-9]+ \(project\): + project\(\) called with 'HOMEPAGE_URL' as first argument\. The first + parameter should be the project name, not a keyword argument\. See the + cmake-commands\(7\) manual for correct usage of the project\(\) command\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/project/KeywordProjectName.cmake b/Tests/RunCMake/project/KeywordProjectName.cmake new file mode 100644 index 0000000000..07a075782b --- /dev/null +++ b/Tests/RunCMake/project/KeywordProjectName.cmake @@ -0,0 +1,9 @@ +project(LANGUAGES) +project(VERSION) +project(DESCRIPTION) +project(HOMEPAGE_URL) + +# CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO=b80be207-778e-46ba-8080-b23bba22639e +# Enable these when Package Info is no longer experimental +# project(COMPAT_VERSION) +# project(SPDX_LICENSE) diff --git a/Tests/RunCMake/project/RunCMakeTest.cmake b/Tests/RunCMake/project/RunCMakeTest.cmake index 31579e9d83..86bdfae48c 100644 --- a/Tests/RunCMake/project/RunCMakeTest.cmake +++ b/Tests/RunCMake/project/RunCMakeTest.cmake @@ -21,6 +21,8 @@ if(CMake_TEST_RESOURCES) run_cmake(ExplicitRC) endif() +run_cmake(KeywordProjectName) + set(RunCMake_DEFAULT_stderr .) run_cmake(LanguagesDuplicate) unset(RunCMake_DEFAULT_stderr)