find_*: Add variable to default calls to REQUIRED

This adds a `CMAKE_FIND_REQUIRED` variable which causes `find_package`,
`find_path`, `find_file`, `find_library` and `find_program` to be
considered `REQUIRED` by default.

It also introduces an `OPTIONAL` keyword to those commands, allowing
them to ignore the value of this variable.

Issue: #26576
This commit is contained in:
Martin Duffy 2025-02-20 15:24:04 -05:00 committed by Brad King
parent d3484a31c6
commit 857a039d66
38 changed files with 337 additions and 22 deletions

View File

@ -18,7 +18,7 @@ The general signature is:
[VALIDATOR function]
[DOC "cache documentation string"]
[NO_CACHE]
[REQUIRED]
[REQUIRED|OPTIONAL]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
@ -118,6 +118,18 @@ Options include:
the search will be attempted again the next time |FIND_XXX| is invoked
with the same variable.
.. versionadded:: 4.1
Every |FIND_XXX| command will be treated as ``REQUIRED`` when the
:variable:`CMAKE_FIND_REQUIRED` variable is enabled.
``OPTIONAL``
.. versionadded:: 4.1
Ignore the value of :variable:`CMAKE_FIND_REQUIRED` and
continue without an error message if nothing is found.
Incompatible with ``REQUIRED``.
If ``NO_DEFAULT_PATH`` is specified, then no additional paths are
added to the search.
If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:

View File

@ -132,7 +132,7 @@ Basic Signature
.. code-block:: cmake
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[REQUIRED|OPTIONAL] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
@ -159,6 +159,13 @@ otherwise execution still continues. As a form of shorthand, if the
``REQUIRED`` option is present, the ``COMPONENTS`` keyword can be omitted
and the required components can be listed directly after ``REQUIRED``.
The :variable:`CMAKE_FIND_REQUIRED` variable can be enabled to make this call
``REQUIRED`` by default. This behavior can be overridden by providing the
``OPTIONAL`` keyword. As with the ``REQUIRED`` option, a list of components
can be listed directly after ``OPTIONAL``, which is equivalent to listing
them after the ``COMPONENTS`` keyword. When the ``OPTIONAL`` keyword is given,
the warning output when a package is not found is suppressed.
Additional optional components may be listed after
``OPTIONAL_COMPONENTS``. If these cannot be satisfied, the package overall
can still be considered found, as long as all required components are
@ -249,7 +256,7 @@ Full Signature
.. code-block:: cmake
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[REQUIRED|OPTIONAL] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[GLOBAL]
@ -635,6 +642,9 @@ Every non-REQUIRED ``find_package`` call can be disabled or made REQUIRED:
Setting both variables to ``TRUE`` simultaneously is an error.
The :variable:`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>` variable takes priority
over the ``OPTIONAL`` keyword in determining whether a package is required.
.. _`version selection`:
Config Mode Version Selection

View File

@ -61,6 +61,7 @@ Variables that Provide Information
/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR
/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
/variable/CMAKE_FIND_PACKAGE_SORT_ORDER
/variable/CMAKE_FIND_REQUIRED
/variable/CMAKE_GENERATOR
/variable/CMAKE_GENERATOR_INSTANCE
/variable/CMAKE_GENERATOR_PLATFORM

View File

@ -0,0 +1,8 @@
cmake-find-required
-------------------
* The :variable:`CMAKE_FIND_REQUIRED` variable was added to tell
:command:`find_package`, :command:`find_path`, :command:`find_file`,
:command:`find_library`, and :command:`find_program` to be ``REQUIRED``
by default. The commands also gained an ``OPTIONAL`` keyword to ignore
the variable for a specific call.

View File

@ -0,0 +1,27 @@
CMAKE_FIND_REQUIRED
-------------------
.. versionadded:: 4.1
If enabled, the following commands are treated as having the ``REQUIRED``
keyword unless provided with the ``OPTIONAL`` keyword:
* :command:`find_package`
* :command:`find_program`
* :command:`find_library`
* :command:`find_path`
* :command:`find_file`
When :command:`find_package` loads a ``Find<PackageName>.cmake``
or ``<PackageName>Config.cmake`` module, the ``CMAKE_FIND_REQUIRED``
variable is automatically unset within it to restore the default
behavior for nested find operations. The module is free to set the
``CMAKE_FIND_REQUIRED`` variable itself to opt-in to the behavior.
Note that enabling this variable breaks some commonly used patterns.
Multiple calls to :command:`find_package` are sometimes used to obtain a
different search order to the default.
See also the :variable:`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>` for making
a :command:`find_package` call ``REQUIRED``, and for additional information on
how enabling these variables can break commonly used patterns.

View File

@ -94,6 +94,7 @@ bool cmFindBase::ParseArguments(std::vector<std::string> const& argsIn)
this->SelectDefaultMacMode();
bool newStyle = false;
bool haveRequiredOrOptional = false;
enum Doing
{
DoingNone,
@ -129,8 +130,21 @@ bool cmFindBase::ParseArguments(std::vector<std::string> const& argsIn)
this->NoDefaultPath = true;
} else if (args[j] == "REQUIRED") {
doing = DoingNone;
if (haveRequiredOrOptional && !this->Required) {
this->SetError("cannot be both REQUIRED and OPTIONAL");
return false;
}
this->Required = true;
newStyle = true;
haveRequiredOrOptional = true;
} else if (args[j] == "OPTIONAL") {
doing = DoingNone;
if (haveRequiredOrOptional && this->Required) {
this->SetError("cannot be both REQUIRED and OPTIONAL");
return false;
}
newStyle = true;
haveRequiredOrOptional = true;
} else if (args[j] == "REGISTRY_VIEW") {
if (++j == args.size()) {
this->SetError("missing required argument for REGISTRY_VIEW");
@ -187,6 +201,10 @@ bool cmFindBase::ParseArguments(std::vector<std::string> const& argsIn)
}
}
if (!haveRequiredOrOptional) {
this->Required = this->Makefile->IsOn("CMAKE_FIND_REQUIRED");
}
if (this->VariableDocumentation.empty()) {
this->VariableDocumentation = "Where can ";
if (this->Names.empty()) {

View File

@ -747,7 +747,18 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
configArgs.push_back(i);
doing = DoingNone;
} else if (args[i] == "REQUIRED") {
this->Required = true;
if (this->Required == RequiredStatus::OptionalExplicit) {
this->SetError("cannot be both REQUIRED and OPTIONAL");
return false;
}
this->Required = RequiredStatus::RequiredExplicit;
doing = DoingComponents;
} else if (args[i] == "OPTIONAL") {
if (this->Required == RequiredStatus::RequiredExplicit) {
this->SetError("cannot be both REQUIRED and OPTIONAL");
return false;
}
this->Required = RequiredStatus::OptionalExplicit;
doing = DoingComponents;
} else if (args[i] == "COMPONENTS") {
doing = DoingComponents;
@ -844,6 +855,11 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
}
}
if (this->Required == RequiredStatus::Optional &&
this->Makefile->IsOn("CMAKE_FIND_REQUIRED")) {
this->Required = RequiredStatus::RequiredFromFindVar;
}
if (!this->GlobalScope) {
cmValue value(
this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_TARGETS_GLOBAL"));
@ -978,21 +994,21 @@ bool cmFindPackageCommand::FindPackage(
bool const makePackageRequiredSet =
this->Makefile->IsOn(makePackageRequiredVar);
if (makePackageRequiredSet) {
if (this->Required) {
if (this->IsRequired()) {
this->Makefile->IssueMessage(
MessageType::WARNING,
cmStrCat("for module ", this->Name,
" already called with REQUIRED, thus ",
makePackageRequiredVar, " has no effect."));
} else {
this->Required = true;
this->Required = RequiredStatus::RequiredFromPackageVar;
}
}
std::string const disableFindPackageVar =
cmStrCat("CMAKE_DISABLE_FIND_PACKAGE_", this->Name);
if (this->Makefile->IsOn(disableFindPackageVar)) {
if (this->Required) {
if (this->IsRequired()) {
this->SetError(
cmStrCat("for module ", this->Name,
(makePackageRequiredSet
@ -1310,6 +1326,9 @@ void cmFindPackageCommand::SetModuleVariables()
{
this->AddFindDefinition("CMAKE_FIND_PACKAGE_NAME", this->Name);
// Nested find calls are not automatically required.
this->AddFindDefinition("CMAKE_FIND_REQUIRED", ""_s);
// Store the list of components and associated variable definitions.
std::string components_var = this->Name + "_FIND_COMPONENTS";
this->AddFindDefinition(components_var, this->Components);
@ -1329,7 +1348,7 @@ void cmFindPackageCommand::SetModuleVariables()
this->AddFindDefinition(quietly, "1"_s);
}
if (this->Required) {
if (this->IsRequired()) {
// Tell the module that is about to be read that it should report
// a fatal error if the package is not found.
std::string req = cmStrCat(this->Name, "_FIND_REQUIRED");
@ -1575,11 +1594,13 @@ bool cmFindPackageCommand::HandlePackageMode(
// package not found
if (result && !found) {
// warn if package required or neither quiet nor in config mode
if (this->Required ||
!(this->Quiet ||
(this->UseConfigFiles && !this->UseFindModules &&
this->ConsideredConfigs.empty()))) {
// warn if package required or
// (neither quiet nor in config mode and not explicitly optional)
if (this->IsRequired() ||
(!(this->Quiet ||
(this->UseConfigFiles && !this->UseFindModules &&
this->ConsideredConfigs.empty())) &&
this->Required != RequiredStatus::OptionalExplicit)) {
// The variable is not set.
std::ostringstream e;
std::ostringstream aw;
@ -1675,11 +1696,19 @@ bool cmFindPackageCommand::HandlePackageMode(
"without ensuring that it is actually available.\n";
}
}
if (this->Required == RequiredStatus::RequiredFromFindVar) {
e << "\nThis package is considered required because the "
"CMAKE_FIND_REQUIRED variable has been enabled.\n";
} else if (this->Required == RequiredStatus::RequiredFromPackageVar) {
e << "\nThis package is considered required because the "
<< cmStrCat("CMAKE_REQUIRE_FIND_PACKAGE_", this->Name)
<< " variable has been enabled.\n";
}
this->Makefile->IssueMessage(this->Required ? MessageType::FATAL_ERROR
: MessageType::WARNING,
e.str());
if (this->Required) {
this->Makefile->IssueMessage(
this->IsRequired() ? MessageType::FATAL_ERROR : MessageType::WARNING,
e.str());
if (this->IsRequired()) {
cmSystemTools::SetFatalErrorOccurred();
}
@ -1912,7 +1941,7 @@ bool cmFindPackageCommand::ReadPackage()
// Loop over appendices.
auto iter = this->CpsAppendices.begin();
while (iter != this->CpsAppendices.end()) {
bool required = false;
RequiredStatus required = RequiredStatus::Optional;
bool important = false;
// Check if this appendix provides any requested components.
@ -1972,7 +2001,7 @@ bool cmFindPackageCommand::ReadPackage()
bool cmFindPackageCommand::FindPackageDependencies(
std::string const& fileName, cmPackageInfoReader const& reader,
bool required)
RequiredStatus required)
{
// Get package requirements.
for (cmPackageRequirement const& dep : reader.GetRequirements()) {
@ -2110,7 +2139,7 @@ void cmFindPackageCommand::AppendSuccessInformation()
}
this->Makefile->GetState()->SetGlobalProperty(versionInfoPropName,
versionInfo);
if (this->Required) {
if (this->IsRequired()) {
std::string const requiredInfoPropName =
cmStrCat("_CMAKE_", this->Name, "_TYPE");
this->Makefile->GetState()->SetGlobalProperty(requiredInfoPropName,
@ -3265,6 +3294,13 @@ bool cmFindPackageCommand::SearchEnvironmentPrefix(std::string const& prefix)
return TryGeneratedPaths(searchFn, pdt::Cps, prefix, pkgDirGen);
}
bool cmFindPackageCommand::IsRequired() const
{
return this->Required == RequiredStatus::RequiredExplicit ||
this->Required == RequiredStatus::RequiredFromPackageVar ||
this->Required == RequiredStatus::RequiredFromFindVar;
}
// TODO: Debug cmsys::Glob double slash problem.
bool cmFindPackage(std::vector<std::string> const& args,

View File

@ -151,9 +151,17 @@ private:
using AppendixMap = std::map<std::string, Appendix>;
AppendixMap FindAppendices(std::string const& base,
cmPackageInfoReader const& baseReader) const;
enum RequiredStatus
{
Optional,
OptionalExplicit,
RequiredExplicit,
RequiredFromPackageVar,
RequiredFromFindVar
};
bool FindPackageDependencies(std::string const& fileName,
cmPackageInfoReader const& reader,
bool required);
RequiredStatus required);
bool ImportPackageTargets(std::string const& fileName,
cmPackageInfoReader& reader);
@ -194,6 +202,8 @@ private:
bool SearchAppBundlePrefix(std::string const& prefix);
bool SearchEnvironmentPrefix(std::string const& prefix);
bool IsRequired() const;
struct OriginalDef
{
bool exists;
@ -234,7 +244,7 @@ private:
unsigned int VersionFoundCount = 0;
KWIML_INT_uint64_t RequiredCMakeVersion = 0;
bool Quiet = false;
bool Required = false;
RequiredStatus Required = RequiredStatus::Optional;
bool UseCpsFiles = false;
bool UseConfigFiles = true;
bool UseFindModules = true;

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,10 @@
CMake Error at Optional.cmake:[0-9]+ \(find_file\):
find_file cannot be both REQUIRED and OPTIONAL
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Error at Optional\.cmake:[0-9]+ \(find_file\):
Could not find FILE_doNotExists using the following files: doNotExists.h
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,14 @@
set(CMAKE_FIND_REQUIRED ON)
find_file(FILE_doNotExists_Optional
NAMES doNotExists.h
OPTIONAL
)
find_file(FILE_doNotExists_OptionalRequired
NAMES doNotExists.h
OPTIONAL
REQUIRED
)
find_file(FILE_doNotExists
NAMES doNotExists.h
REQUIRED
)

View File

@ -4,6 +4,7 @@ run_cmake(FromPATHEnv)
run_cmake(FromPrefixPath)
run_cmake(PrefixInPATH)
run_cmake(Required)
run_cmake(Optional)
run_cmake(NO_CACHE)
run_cmake(REGISTRY_VIEW-no-view)
run_cmake(REGISTRY_VIEW-wrong-view)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,10 @@
CMake Error at Optional.cmake:[0-9]+ \(find_library\):
find_library cannot be both REQUIRED and OPTIONAL
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Error at Optional\.cmake:[0-9]+ \(find_library\):
Could not find LIB_doNotExists using the following names: doNotExists
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,13 @@
set(CMAKE_FIND_REQUIRED ON)
find_library(LIB_doNotExists_Optional
NAMES doNotExists
OPTIONAL
)
find_library(LIB_doNotExists_OptionalRequired
NAMES doNotExists
OPTIONAL
REQUIRED
)
find_library(LIB_doNotExists
NAMES doNotExists
)

View File

@ -12,6 +12,7 @@ if(UNIX AND NOT CYGWIN)
endif()
run_cmake(PrefixInPATH)
run_cmake(Required)
run_cmake(Optional)
run_cmake(NO_CACHE)
run_cmake(REGISTRY_VIEW-no-view)
run_cmake(REGISTRY_VIEW-wrong-view)

View File

@ -1,5 +1,9 @@
CMake Error at MissingNormalForceRequired.cmake:2 \(find_package\):
No "FindNotHere.cmake" found in CMAKE_MODULE_PATH\.
This package is considered required because the
CMAKE_REQUIRE_FIND_PACKAGE_NotHere variable has been enabled.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+

View File

@ -0,0 +1,20 @@
CMake Error at PackageVarOverridesOptional.cmake:[0-9]+ \(find_package\):
By not providing "FindFoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "Foo", but
CMake did not find one.
Could not find a package configuration file provided by "Foo" with any of
the following names:
FooConfig.cmake
foo-config.cmake
Add the installation prefix of "Foo" to CMAKE_PREFIX_PATH or set "Foo_DIR"
to a directory containing one of the above files. If "Foo" provides a
separate development package or SDK, be sure it has been installed.
This package is considered required because the
CMAKE_REQUIRE_FIND_PACKAGE_Foo variable has been enabled.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,2 @@
set(CMAKE_REQUIRE_FIND_PACKAGE_Foo ON)
find_package(Foo OPTIONAL)

View File

@ -0,0 +1,4 @@
CMake Error at RequiredOptionalKeywordsClash.cmake:[0-9]+ \(find_package\):
find_package cannot be both REQUIRED and OPTIONAL
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
find_package(Foo OPTIONAL REQUIRED)

View File

@ -0,0 +1,18 @@
CMake Warning at RequiredVarNestedConfig\.cmake:[0-9]+ \(find_package\):
By not providing "FindDoesNotExist\.cmake" in CMAKE_MODULE_PATH this project
has asked CMake to find a package configuration file provided by
"DoesNotExist", but CMake did not find one\.
Could not find a package configuration file provided by "DoesNotExist" with
any of the following names:
DoesNotExistConfig.cmake
doesnotexist-config.cmake
Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
"DoesNotExist_DIR" to a directory containing one of the above files\. If
"DoesNotExist" provides a separate development package or SDK, be sure it
has been installed\.
Call Stack \(most recent call first\):
RequiredVarNested.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,3 @@
set(CMAKE_FIND_REQUIRED ON CACHE BOOL "") # The cache entry must be shadowed by a nested definition.
set(RequiredVarNested_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
find_package(RequiredVarNested CONFIG)

View File

@ -0,0 +1,8 @@
if (CMAKE_FIND_REQUIRED)
message(FATAL_ERROR "CMAKE_FIND_REQUIRED enabled in Config.cmake")
endif()
find_package(DoesNotExist)
find_library(library DoesNotExist)
find_program(program DoesNotExist)
find_path(path DoesNotExist)
find_file(file DoesNotExist)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,21 @@
CMake Error at RequiredVarOptional.cmake:[0-9]+ \(find_package\):
By not providing "FindDoesNotExist.cmake" in CMAKE_MODULE_PATH this project
has asked CMake to find a package configuration file provided by
"DoesNotExist", but CMake did not find one.
Could not find a package configuration file provided by "DoesNotExist" with
any of the following names:
DoesNotExistConfig.cmake
doesnotexist-config.cmake
Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
"DoesNotExist_DIR" to a directory containing one of the above files. If
"DoesNotExist" provides a separate development package or SDK, be sure it
has been installed.
This package is considered required because the CMAKE_FIND_REQUIRED
variable has been enabled.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,4 @@
set(CMAKE_FIND_REQUIRED ON)
find_package(DoesNotExist-Optional OPTIONAL CompA CompB CompC)
find_package(DoesNotExist)
message(FATAL_ERROR "This error must not be reachable.")

View File

@ -27,9 +27,13 @@ run_cmake_with_options(ModuleModeDebugPkg --debug-find-pkg=Foo,Zot)
run_cmake(PackageRoot)
run_cmake(PackageRootNestedConfig)
run_cmake(PackageRootNestedModule)
run_cmake(PackageVarOverridesOptional)
run_cmake(PolicyPush)
run_cmake(PolicyPop)
run_cmake(RequiredOptionValuesClash)
run_cmake(RequiredOptionalKeywordsClash)
run_cmake(RequiredVarOptional)
run_cmake(RequiredVarNested)
run_cmake(FindRootPathAndPrefixPathAreEqual)
run_cmake(SetFoundFALSE)
run_cmake(WrongVersion)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,10 @@
CMake Error at Optional.cmake:[0-9]+ \(find_path\):
find_path cannot be both REQUIRED and OPTIONAL
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Error at Optional\.cmake:[0-9]+ \(find_path\):
Could not find PATH_doNotExists using the following files: doNotExists.h
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,13 @@
set(CMAKE_FIND_REQUIRED ON)
find_path(PATH_doNotExists_Optional
NAMES doNotExists.h
OPTIONAL
)
find_path(PATH_doNotExists_OptionalRequired
NAMES doNotExists.h
OPTIONAL
REQUIRED
)
find_path(PATH_doNotExists
NAMES doNotExists.h
)

View File

@ -4,6 +4,7 @@ run_cmake(EmptyOldStyle)
run_cmake(FromPATHEnv)
run_cmake(PrefixInPATH)
run_cmake(Required)
run_cmake(Optional)
run_cmake(NO_CACHE)
run_cmake(REGISTRY_VIEW-no-view)
run_cmake(REGISTRY_VIEW-wrong-view)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,10 @@
CMake Error at Optional.cmake:[0-9]+ \(find_program\):
find_program cannot be both REQUIRED and OPTIONAL
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Error at Optional\.cmake:[0-9]+ \(find_program\):
Could not find PROG_AandB using the following names: testAandB
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,13 @@
set(CMAKE_FIND_REQUIRED ON)
find_program(PROG_AandB_Optional
NAMES testAandB
OPTIONAL
)
find_program(PROG_AandB_OptionalRequired
NAMES testAandB
OPTIONAL
REQUIRED
)
find_program(PROG_AandB
NAMES testAandB
)

View File

@ -5,6 +5,7 @@ run_cmake(DirsPerName)
run_cmake(NamesPerDir)
run_cmake(RelAndAbsPath)
run_cmake(Required)
run_cmake(Optional)
run_cmake(NO_CACHE)
run_cmake(IgnorePrefixPath)
run_cmake(REGISTRY_VIEW-no-view)