export, install: Improve argument parsing

Modify the `export(EXPORT)` and `install(PACKAGE_INFO)` commands to
actually enforce that certain arguments must be non-empty. With respect
to `export`, this is mainly targeted at CPS export, but ends up
affecting all flavors of `export(EXPORT)` because that is the level at
which argument parsing is performed.

Also, add unit tests to verify that the new rejection of arguments with
missing or empty values is working, update the existing tests due to
changes in error reporting, and remove no-longer-needed manual
validation of an empty `PACKAGE_INFO` argument.
This commit is contained in:
Matthew Woehlke
2025-12-05 15:17:46 -05:00
committed by Brad King
parent 2e71df0156
commit 2151e5f79d
31 changed files with 365 additions and 32 deletions

View File

@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.3
.. toctree::
:maxdepth: 1
CMP0208: export(EXPORT) does not allow empty arguments. </policy/CMP0208>
CMP0207: file(GET_RUNTIME_DEPENDENCIES) normalizes paths before matching. </policy/CMP0207>
CMP0206: The CPack Archive Generator defaults to UID 0 and GID 0. </policy/CMP0206>
CMP0205: file(CREATE_LINK) with COPY_ON_ERROR copies directory content. </policy/CMP0205>

30
Help/policy/CMP0208.rst Normal file
View File

@@ -0,0 +1,30 @@
CMP0208
-------
.. versionadded:: 4.3
:command:`export(EXPORT)` does not allow empty arguments.
In CMake 4.2 and below, the :command:`export(EXPORT)` command silently
accepted ``NAMESPACE``, ``FILE`` or ``CXX_MODULES_DIRECTORY`` arguments with
empty or missing values.
CMake 4.3 and above issue a diagnostic if any of these keywords are present
without an associated value, or of the value given to ``FILE`` or
``CXX_MODULES_DIRECTORY`` is empty. (``NAMESPACE`` is allowed to have an empty
value, but an empty value must be quoted.) A diagnostic is also issued
immediately if the value of ``EXPORT`` is missing or empty. (Previously, this
would result in an error at generate-time because no export set whose name is
empty can exist.)
This policy provides compatibility with projects which may have relied on the
previous lack of enforcement. The ``OLD`` behavior for this policy permits
these keywords to be present without an accompanying value, or to be given an
empty value. The ``NEW`` behavior requires that a value be given, and that
the value (except for ``NAMESPACE``) is non-empty.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.3
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst

View File

@@ -0,0 +1,5 @@
export-empty-args
-----------------
* The :command:`export(EXPORT)` command no longer allows certain
arguments to be missing or empty. See policy :policy:`CMP0208`.

View File

@@ -209,7 +209,7 @@ static bool HandleTargetsMode(std::vector<std::string> const& args,
static bool HandleExportMode(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
struct ExportArguments
struct ExportArguments : public ArgumentParser::ParseResult
{
ArgumentParser::NonEmpty<std::string> ExportSetName;
ArgumentParser::MaybeEmpty<std::string> Namespace;
@@ -248,12 +248,23 @@ static bool HandleExportMode(std::vector<std::string> const& args,
cmMakefile& mf = status.GetMakefile();
cmGlobalGenerator* gg = mf.GetGlobalGenerator();
if (arguments.PackageInfo) {
if (arguments.PackageInfo->PackageName.empty()) {
// TODO: Fix our use of the parser to enforce this.
status.SetError("PACKAGE_INFO missing required value.");
if (!arguments.Check(args[0], &unknownArgs, status)) {
cmPolicies::PolicyStatus const p =
status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0208);
if (arguments.PackageInfo || !unknownArgs.empty() ||
p == cmPolicies::NEW) {
return false;
}
if (p == cmPolicies::WARN) {
status.GetMakefile().IssueMessage(
MessageType::AUTHOR_WARNING, cmStrCat("export "_s, status.GetError()));
status.GetMakefile().IssueMessage(
MessageType::AUTHOR_WARNING,
cmPolicies::GetPolicyWarning(cmPolicies::CMP0208));
}
}
if (arguments.PackageInfo) {
if (!arguments.Filename.empty()) {
status.SetError("PACKAGE_INFO and FILE are mutually exclusive.");
return false;
@@ -268,12 +279,6 @@ static bool HandleExportMode(std::vector<std::string> const& args,
}
}
if (!unknownArgs.empty()) {
status.SetError("EXPORT given unknown argument: \"" + unknownArgs.front() +
"\".");
return false;
}
std::string fname;
if (arguments.Filename.empty()) {
if (arguments.PackageInfo) {

View File

@@ -2340,12 +2340,9 @@ bool HandlePackageInfoMode(std::vector<std::string> const& args,
// ica.Bind("CXX_MODULES_DIRECTORY"_s, cxxModulesDirectory); TODO?
std::vector<std::string> unknownArgs;
ica.Parse(args, &unknownArgs);
ArgumentParser::ParseResult result = ica.Parse(args, &unknownArgs);
if (!unknownArgs.empty()) {
// Unknown argument.
status.SetError(
cmStrCat(args[0], " given unknown argument \"", unknownArgs[0], "\"."));
if (!result.Check(args[0], &unknownArgs, status)) {
return false;
}
@@ -2353,12 +2350,6 @@ bool HandlePackageInfoMode(std::vector<std::string> const& args,
return false;
}
if (arguments.PackageName.empty()) {
// TODO: Fix our use of the parser to enforce this.
status.SetError(cmStrCat(args[0], " missing package name."));
return false;
}
if (exportName.empty()) {
status.SetError(cmStrCat(args[0], " missing EXPORT."));
return false;

View File

@@ -621,6 +621,8 @@ class cmMakefile;
WARN) \
SELECT(POLICY, CMP0207, \
"file(GET_RUNTIME_DEPENDENCIES) normalizes paths before matching.", \
4, 3, 0, WARN) \
SELECT(POLICY, CMP0208, "export(EXPORT) does not allow empty arguments.", \
4, 3, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)

View File

@@ -1,4 +1,6 @@
CMake Error at BadArgs0\.cmake:3 \(export\):
export PACKAGE_INFO missing required value\.
CMake Error at BadArgs0\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
PACKAGE_INFO: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,95 @@
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
APPENDIX: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
VERSION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
COMPAT_VERSION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
VERSION_SCHEMA: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
LICENSE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
DEFAULT_LICENSE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
DESCRIPTION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
HOMEPAGE_URL: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
DEFAULT_TARGETS: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
DEFAULT_CONFIGURATIONS: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
PROJECT: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs5\.cmake:[0-9]+ \(export\):
export EXPORT given invalid arguments:
DEFAULT_LICENSE: empty string not allowed
LICENSE: empty string not allowed
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1,15 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
set(args EXPORT foo PACKAGE_INFO foo)
export(${args} APPENDIX)
export(${args} VERSION)
export(${args} COMPAT_VERSION)
export(${args} VERSION_SCHEMA)
export(${args} LICENSE)
export(${args} DEFAULT_LICENSE)
export(${args} DESCRIPTION)
export(${args} HOMEPAGE_URL)
export(${args} DEFAULT_TARGETS)
export(${args} DEFAULT_CONFIGURATIONS)
export(${args} PROJECT)
export(${args} LICENSE "" DEFAULT_LICENSE "")

View File

@@ -3,7 +3,7 @@ add_library(bar foo.cxx)
target_link_libraries(bar foo)
install(TARGETS foo EXPORT foo)
export(EXPORT foo NAMESPACE ${NAMESPACE})
export(EXPORT foo NAMESPACE "${NAMESPACE}")
export(EXPORT foo PACKAGE_INFO foo)
install(TARGETS bar EXPORT bar)

View File

@@ -21,6 +21,7 @@ run_cmake(BadArgs1)
run_cmake(BadArgs2)
run_cmake(BadArgs3)
run_cmake(BadArgs4)
run_cmake(BadArgs5)
run_cmake(BadName)
run_cmake(DuplicateOutput)
run_cmake(BadDefaultTarget)

View File

@@ -1,22 +1,28 @@
CMake Error at BadArgs0\.cmake:1 \(install\):
install PACKAGE_INFO missing package name\.
CMake Error at BadArgs0\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
PACKAGE_INFO: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs0\.cmake:2 \(install\):
CMake Error at BadArgs0\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO missing EXPORT\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs0\.cmake:3 \(install\):
install PACKAGE_INFO missing EXPORT\.
CMake Error at BadArgs0\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
EXPORT: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs0\.cmake:7 \(install\):
install PACKAGE_INFO missing package name\.
CMake Error at BadArgs0\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
PACKAGE_INFO: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,95 @@
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
APPENDIX: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
VERSION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
COMPAT_VERSION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
VERSION_SCHEMA: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
LICENSE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
DEFAULT_LICENSE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
DESCRIPTION: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
HOMEPAGE_URL: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
DEFAULT_TARGETS: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
DEFAULT_CONFIGURATIONS: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid argument:
PROJECT: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs3\.cmake:[0-9]+ \(install\):
install PACKAGE_INFO given invalid arguments:
DEFAULT_LICENSE: empty string not allowed
LICENSE: empty string not allowed
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1,15 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
set(args PACKAGE_INFO test EXPORT foo)
install(${args} APPENDIX)
install(${args} VERSION)
install(${args} COMPAT_VERSION)
install(${args} VERSION_SCHEMA)
install(${args} LICENSE)
install(${args} DEFAULT_LICENSE)
install(${args} DESCRIPTION)
install(${args} HOMEPAGE_URL)
install(${args} DEFAULT_TARGETS)
install(${args} DEFAULT_CONFIGURATIONS)
install(${args} PROJECT)
install(${args} LICENSE "" DEFAULT_LICENSE "")

View File

@@ -32,6 +32,7 @@ endfunction()
run_cmake(BadArgs0)
run_cmake(BadArgs1)
run_cmake(BadArgs2)
run_cmake(BadArgs3)
run_cmake(BadName)
run_cmake(BadDefaultTarget)
run_cmake(ReferencesNonExportedTarget)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,6 @@
CMake Error at EmptyExport-CMP0208-NEW\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
EXPORT: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1,3 @@
cmake_policy(SET CMP0208 NEW)
export(EXPORT)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
CMake Error at EmptyExport-CMP0208-OLD\.cmake:[0-9]+ \(export\):
export Export set "" not found\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1,3 @@
cmake_policy(SET CMP0208 OLD)
export(EXPORT)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,6 @@
CMake Error at EmptyExportFile-CMP0208-NEW\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
FILE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -0,0 +1,6 @@
cmake_policy(SET CMP0208 NEW)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo)
export(EXPORT foo FILE)

View File

@@ -0,0 +1,6 @@
cmake_policy(SET CMP0208 OLD)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo)
export(EXPORT foo FILE)

View File

@@ -0,0 +1,15 @@
CMake Warning \(dev\) at EmptyExportFile-CMP0208-WARN\.cmake:[0-9]+ \(export\):
export EXPORT given invalid argument:
FILE: missing required value
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
This warning is for project developers\. Use -Wno-dev to suppress it\.
CMake Warning \(dev\) at EmptyExportFile-CMP0208-WARN\.cmake:[0-9]+ \(export\):
Policy CMP0208 is not set: export\(EXPORT\) does not allow empty arguments\.
Run "cmake --help-policy CMP0208" for policy details\. Use the cmake_policy
command to set the policy and suppress this warning\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@@ -0,0 +1,4 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo)
export(EXPORT foo FILE)

View File

@@ -0,0 +1,5 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo)
# Ensure we can export with an explicitly-empty namespace.
export(EXPORT foo NAMESPACE "")

View File

@@ -1,5 +1,11 @@
include(RunCMake)
run_cmake(EmptyExport-CMP0208-OLD)
run_cmake(EmptyExport-CMP0208-NEW)
run_cmake(EmptyExportFile-CMP0208-WARN)
run_cmake(EmptyExportFile-CMP0208-OLD)
run_cmake(EmptyExportFile-CMP0208-NEW)
run_cmake(EmptyNamespace)
run_cmake(CustomTarget)
run_cmake(Empty)
run_cmake(Repeat-CMP0103-WARN)