Merge topic 'ImprovedCOMPONENTSSupportInFindPackage2'

6d100f9 find_package: Test rejection of required+optional components
d81d83c add macro check_required_components() to configure_package_config_file()
b15c0b4 FPHSA(): add HANDLE_COMPONENTS option
34108cd find_package: add documentation for OPTIONAL_COMPONENTS
cdabde8 FPHSA(): add missing "]" to documentation
f2e0a18 find_package: add OPTIONAL_COMPONENTS keyword
This commit is contained in:
David Cole 2012-03-21 13:26:46 -04:00 committed by CMake Topic Stage
commit 349ea3f6e6
11 changed files with 186 additions and 24 deletions

View File

@ -2,7 +2,8 @@
# #
# CONFIGURE_PACKAGE_CONFIG_FILE(<input> <output> INSTALL_DESTINATION <path> # CONFIGURE_PACKAGE_CONFIG_FILE(<input> <output> INSTALL_DESTINATION <path>
# [PATH_VARS <var1> <var2> ... <varN>] # [PATH_VARS <var1> <var2> ... <varN>]
# [NO_SET_AND_CHECK_MACRO] ) # [NO_SET_AND_CHECK_MACRO]
# [NO_CHECK_REQUIRED_COMPONENTS_MACRO])
# #
# CONFIGURE_PACKAGE_CONFIG_FILE() should be used instead of the plain # CONFIGURE_PACKAGE_CONFIG_FILE() should be used instead of the plain
# CONFIGURE_FILE() command when creating the <Name>Config.cmake or <Name>-config.cmake # CONFIGURE_FILE() command when creating the <Name>Config.cmake or <Name>-config.cmake
@ -49,8 +50,10 @@
# For absolute locations it works only if the absolute location is a subdirectory # For absolute locations it works only if the absolute location is a subdirectory
# of CMAKE_INSTALL_PREFIX. # of CMAKE_INSTALL_PREFIX.
# #
# By default configure_package_config_file() also generates a macro set_and_check() # By default configure_package_config_file() also generates two helper macros,
# into the FooConfig.cmake file. This should be used instead of the normal set() # set_and_check() and check_required_components() into the FooConfig.cmake file.
#
# set_and_check() should be used instead of the normal set()
# command for setting directories and file locations. Additionally to setting the # command for setting directories and file locations. Additionally to setting the
# variable it also checks that the referenced file or directory actually exists # variable it also checks that the referenced file or directory actually exists
# and fails with a FATAL_ERROR otherwise. This makes sure that the created # and fails with a FATAL_ERROR otherwise. This makes sure that the created
@ -58,6 +61,16 @@
# When using the NO_SET_AND_CHECK_MACRO, this macro is not generated into the # When using the NO_SET_AND_CHECK_MACRO, this macro is not generated into the
# FooConfig.cmake file. # FooConfig.cmake file.
# #
# check_required_components(<package_name>) should be called at the end of the
# FooConfig.cmake file if the package supports components.
# This macro checks whether all requested, non-optional components have been found,
# and if this is not the case, sets the Foo_FOUND variable to FALSE, so that the package
# is considered to be not found.
# It does that by testing the Foo_<Component>_FOUND variables for all requested
# required components.
# When using the NO_CHECK_REQUIRED_COMPONENTS option, this macro is not generated
# into the FooConfig.cmake file.
#
# For an example see below the documentation for WRITE_BASIC_PACKAGE_VERSION_FILE(). # For an example see below the documentation for WRITE_BASIC_PACKAGE_VERSION_FILE().
# #
# #
@ -114,6 +127,8 @@
# ... # ...
# set_and_check(FOO_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") # set_and_check(FOO_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
# set_and_check(FOO_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@") # set_and_check(FOO_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@")
#
# check_required_components(Foo)
#============================================================================= #=============================================================================
@ -139,7 +154,7 @@ endmacro()
function(CONFIGURE_PACKAGE_CONFIG_FILE _inputFile _outputFile) function(CONFIGURE_PACKAGE_CONFIG_FILE _inputFile _outputFile)
set(options NO_SET_AND_CHECK_MACRO) set(options NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO)
set(oneValueArgs INSTALL_DESTINATION ) set(oneValueArgs INSTALL_DESTINATION )
set(multiValueArgs PATH_VARS ) set(multiValueArgs PATH_VARS )
@ -189,6 +204,21 @@ endmacro()
") ")
endif() endif()
if(NOT CCF_NO_CHECK_REQUIRED_COMPONENTS_MACRO)
set(PACKAGE_INIT "${PACKAGE_INIT}
macro(check_required_components _NAME)
foreach(comp \${\${_NAME}_FIND_COMPONENTS})
if(NOT \${_NAME}_\${comp}_FOUND)
if(\${_NAME}_FIND_REQUIRED_\${comp})
set(\${_NAME}_FOUND FALSE)
endif()
endif()
endforeach(comp)
endmacro()
")
endif()
set(PACKAGE_INIT "${PACKAGE_INIT} set(PACKAGE_INIT "${PACKAGE_INIT}
####################################################################################") ####################################################################################")

View File

@ -19,7 +19,8 @@
# #
# The second mode is more powerful and also supports version checking: # The second mode is more powerful and also supports version checking:
# FIND_PACKAGE_HANDLE_STANDARD_ARGS(NAME [REQUIRED_VARS <var1>...<varN>] # FIND_PACKAGE_HANDLE_STANDARD_ARGS(NAME [REQUIRED_VARS <var1>...<varN>]
# [VERSION_VAR <versionvar> # [VERSION_VAR <versionvar>]
# [HANDLE_COMPONENTS]
# [CONFIG_MODE] # [CONFIG_MODE]
# [FAIL_MESSAGE "Custom failure message"] ) # [FAIL_MESSAGE "Custom failure message"] )
# #
@ -32,6 +33,11 @@
# in the find_package() call. The EXACT keyword is also handled. The default # in the find_package() call. The EXACT keyword is also handled. The default
# messages include information about the required version and the version # messages include information about the required version and the version
# which has been actually found, both if the version is ok or not. # which has been actually found, both if the version is ok or not.
# If the package supports components, use the HANDLE_COMPONENTS option to enable
# handling them. In this case, find_package_handle_standard_args() will report
# which components have been found and which are missing, and the <NAME>_FOUND
# variable will be set to FALSE if any of the required components (i.e. not the
# ones listed after OPTIONAL_COMPONENTS) are missing.
# Use the option CONFIG_MODE if your FindXXX.cmake module is a wrapper for # Use the option CONFIG_MODE if your FindXXX.cmake module is a wrapper for
# a find_package(... NO_MODULE) call. In this case VERSION_VAR will be set # a find_package(... NO_MODULE) call. In this case VERSION_VAR will be set
# to <NAME>_VERSION and the macro will automatically check whether the # to <NAME>_VERSION and the macro will automatically check whether the
@ -128,7 +134,7 @@ FUNCTION(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG)
# set up the arguments for CMAKE_PARSE_ARGUMENTS and check whether we are in # set up the arguments for CMAKE_PARSE_ARGUMENTS and check whether we are in
# new extended or in the "old" mode: # new extended or in the "old" mode:
SET(options CONFIG_MODE) SET(options CONFIG_MODE HANDLE_COMPONENTS)
SET(oneValueArgs FAIL_MESSAGE VERSION_VAR) SET(oneValueArgs FAIL_MESSAGE VERSION_VAR)
SET(multiValueArgs REQUIRED_VARS) SET(multiValueArgs REQUIRED_VARS)
SET(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) SET(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} )
@ -189,6 +195,36 @@ FUNCTION(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG)
ENDIF(NOT ${_CURRENT_VAR}) ENDIF(NOT ${_CURRENT_VAR})
ENDFOREACH(_CURRENT_VAR) ENDFOREACH(_CURRENT_VAR)
# component handling
UNSET(FOUND_COMPONENTS_MSG)
UNSET(MISSING_COMPONENTS_MSG)
IF(FPHSA_HANDLE_COMPONENTS)
FOREACH(comp ${${_NAME}_FIND_COMPONENTS})
IF(${_NAME}_${comp}_FOUND)
IF(NOT DEFINED FOUND_COMPONENTS_MSG)
SET(FOUND_COMPONENTS_MSG "found components: ")
ENDIF()
SET(FOUND_COMPONENTS_MSG "${FOUND_COMPONENTS_MSG} ${comp}")
ELSE()
IF(NOT DEFINED MISSING_COMPONENTS_MSG)
SET(MISSING_COMPONENTS_MSG "missing components: ")
ENDIF()
SET(MISSING_COMPONENTS_MSG "${MISSING_COMPONENTS_MSG} ${comp}")
IF(${_NAME}_FIND_REQUIRED_${comp})
SET(${_NAME_UPPER}_FOUND FALSE)
SET(MISSING_VARS "${MISSING_VARS} ${comp}")
ENDIF()
ENDIF()
ENDFOREACH(comp)
SET(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}")
SET(DETAILS "${DETAILS}[c${COMPONENT_MSG}]")
ENDIF(FPHSA_HANDLE_COMPONENTS)
# version handling: # version handling:
SET(VERSION_MSG "") SET(VERSION_MSG "")
@ -240,7 +276,7 @@ FUNCTION(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG)
# print the result: # print the result:
IF (${_NAME_UPPER}_FOUND) IF (${_NAME_UPPER}_FOUND)
FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG}" "${DETAILS}") FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}")
ELSE (${_NAME_UPPER}_FOUND) ELSE (${_NAME_UPPER}_FOUND)
IF(FPHSA_CONFIG_MODE) IF(FPHSA_CONFIG_MODE)

View File

@ -119,16 +119,30 @@ able to find the package. If the
REQUIRED option is given to the command it will set the variable REQUIRED option is given to the command it will set the variable
XXX_FIND_REQUIRED to true before loading the FindXXX.cmake module. If XXX_FIND_REQUIRED to true before loading the FindXXX.cmake module. If
this variable is set the module should issue a FATAL_ERROR if the this variable is set the module should issue a FATAL_ERROR if the
package cannot be found. For each package-specific component, say package cannot be found.
YYY, listed after the REQUIRED option a variable XXX_FIND_REQUIRED_YYY
to true. The set of components listed after either the REQUIRED
option or the COMPONENTS option will be specified in a
XXX_FIND_COMPONENTS variable. This can be used by the FindXXX.cmake
module to determine which sub-components of the package must be found.
If neither the QUIET nor REQUIRED options are given then the If neither the QUIET nor REQUIRED options are given then the
FindXXX.cmake module should look for the package and complain without FindXXX.cmake module should look for the package and complain without
error if the module is not found. error if the module is not found.
A package can be provide sub-components.
Those components can be listed after the COMPONENTS (or REQUIRED)
or OPTIONAL_COMPONENTS keywords. The set of all listed components will be
specified in a XXX_FIND_COMPONENTS variable.
For each package-specific component, say Yyy, a variable XXX_FIND_REQUIRED_Yyy
will be set to true if it listed after COMPONENTS and it will be set to false
if it was listed after OPTIONAL_COMPONENTS.
Using those variables a FindXXX.cmake module and also a XXXConfig.cmake package
configuration file can determine whether and which components have been requested,
and whether they were requested as required or as optional.
For each of the requested components a XXX_Yyy_FOUND variable should be set
accordingly.
The per-package XXX_FOUND variable should be only set to true if all requested
required components have been found. A missing optional component should not
keep the XXX_FOUND variable from being set to true.
If the package provides XXX_INCLUDE_DIRS and XXX_LIBRARIES variables, the include
dirs and libraries for all components which were requested and which have been
found should be added to those two variables.
To get this behaviour you can use the FIND_PACKAGE_HANDLE_STANDARD_ARGS() To get this behaviour you can use the FIND_PACKAGE_HANDLE_STANDARD_ARGS()
macro, as an example see FindJPEG.cmake. macro, as an example see FindJPEG.cmake.

View File

@ -90,6 +90,7 @@ void cmFindPackageCommand::GenerateDocumentation()
this->CommandDocumentation = this->CommandDocumentation =
" find_package(<package> [version] [EXACT] [QUIET] [MODULE]\n" " find_package(<package> [version] [EXACT] [QUIET] [MODULE]\n"
" [[REQUIRED|COMPONENTS] [components...]]\n" " [[REQUIRED|COMPONENTS] [components...]]\n"
" [OPTIONAL_COMPONENTS components...]\n"
" [NO_POLICY_SCOPE])\n" " [NO_POLICY_SCOPE])\n"
"Finds and loads settings from an external project. " "Finds and loads settings from an external project. "
"<package>_FOUND will be set to indicate whether the package was found. " "<package>_FOUND will be set to indicate whether the package was found. "
@ -98,10 +99,14 @@ void cmFindPackageCommand::GenerateDocumentation()
"The QUIET option disables messages if the package cannot be found. " "The QUIET option disables messages if the package cannot be found. "
"The MODULE option disables the second signature documented below. " "The MODULE option disables the second signature documented below. "
"The REQUIRED option stops processing with an error message if the " "The REQUIRED option stops processing with an error message if the "
"package cannot be found. " "package cannot be found."
"A package-specific list of components may be listed after the " "\n"
"REQUIRED option or after the COMPONENTS option if no REQUIRED " "A package-specific list of required components may be listed after the "
"option is given. " "COMPONENTS option or directly after the REQUIRED option. "
"Additional optional components may be listed after OPTIONAL_COMPONENTS. "
"Available components and their influence on whether a package is "
"considered to be found are defined by the target package."
"\n"
"The [version] argument requests a version with which the package found " "The [version] argument requests a version with which the package found "
"should be compatible (format is major[.minor[.patch[.tweak]]]). " "should be compatible (format is major[.minor[.patch[.tweak]]]). "
"The EXACT option requests that the version be matched exactly. " "The EXACT option requests that the version be matched exactly. "
@ -410,6 +415,8 @@ bool cmFindPackageCommand
this->Name = args[0]; this->Name = args[0];
std::string components; std::string components;
const char* components_sep = ""; const char* components_sep = "";
std::set<std::string> requiredComponents;
std::set<std::string> optionalComponents;
// Check ancient compatibility. // Check ancient compatibility.
this->Compatibility_1_6 = this->Compatibility_1_6 =
@ -420,8 +427,8 @@ bool cmFindPackageCommand
this->SearchPathSuffixes.push_back(""); this->SearchPathSuffixes.push_back("");
// Parse the arguments. // Parse the arguments.
enum Doing { DoingNone, DoingComponents, DoingNames, DoingPaths, enum Doing { DoingNone, DoingComponents, DoingOptionalComponents, DoingNames,
DoingPathSuffixes, DoingConfigs, DoingHints }; DoingPaths, DoingPathSuffixes, DoingConfigs, DoingHints };
Doing doing = DoingNone; Doing doing = DoingNone;
cmsys::RegularExpression version("^[0-9.]+$"); cmsys::RegularExpression version("^[0-9.]+$");
bool haveVersion = false; bool haveVersion = false;
@ -465,6 +472,11 @@ bool cmFindPackageCommand
this->Compatibility_1_6 = false; this->Compatibility_1_6 = false;
doing = DoingComponents; doing = DoingComponents;
} }
else if(args[i] == "OPTIONAL_COMPONENTS")
{
this->Compatibility_1_6 = false;
doing = DoingOptionalComponents;
}
else if(args[i] == "NAMES") else if(args[i] == "NAMES")
{ {
configArgs.insert(i); configArgs.insert(i);
@ -528,12 +540,23 @@ bool cmFindPackageCommand
this->Compatibility_1_6 = false; this->Compatibility_1_6 = false;
doing = DoingNone; doing = DoingNone;
} }
else if(doing == DoingComponents) else if((doing == DoingComponents) || (doing == DoingOptionalComponents))
{ {
// Set a variable telling the find script this component // Set a variable telling the find script whether this component
// is required. // is required.
const char* isRequired = "1";
if (doing == DoingOptionalComponents)
{
isRequired = "0";
optionalComponents.insert(args[i]);
}
else
{
requiredComponents.insert(args[i]);
}
std::string req_var = this->Name + "_FIND_REQUIRED_" + args[i]; std::string req_var = this->Name + "_FIND_REQUIRED_" + args[i];
this->AddFindDefinition(req_var.c_str(), "1"); this->AddFindDefinition(req_var.c_str(), isRequired);
// Append to the list of required components. // Append to the list of required components.
components += components_sep; components += components_sep;
@ -584,6 +607,22 @@ bool cmFindPackageCommand
} }
} }
std::vector<std::string> doubledComponents;
std::set_intersection(requiredComponents.begin(), requiredComponents.end(),
optionalComponents.begin(), optionalComponents.end(),
std::back_inserter(doubledComponents));
if(!doubledComponents.empty())
{
cmOStringStream e;
e << "called with components that are both required and optional:\n";
for(unsigned int i=0; i<doubledComponents.size(); ++i)
{
e << " " << doubledComponents[i] << "\n";
}
this->SetError(e.str().c_str());
return false;
}
// Maybe choose one mode exclusively. // Maybe choose one mode exclusively.
this->UseFindModules = configArgs.empty(); this->UseFindModules = configArgs.empty();
this->UseConfigFiles = moduleArgs.empty(); this->UseConfigFiles = moduleArgs.empty();

View File

@ -37,6 +37,12 @@ FIND_PACKAGE(VersionTestB 1.2)
FIND_PACKAGE(VersionTestC 1.2.3) FIND_PACKAGE(VersionTestC 1.2.3)
FIND_PACKAGE(VersionTestD 1.2.3.4) FIND_PACKAGE(VersionTestD 1.2.3.4)
FIND_PACKAGE(LotsOfComponents COMPONENTS AComp OPTIONAL_COMPONENTS BComp CComp)
IF(NOT LOTSOFCOMPONENTS_FOUND)
MESSAGE(SEND_ERROR "LotsOfComponents not found !")
ENDIF()
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# Test system package registry if possible. # Test system package registry if possible.
SET(CMakeTestSystemPackage "") SET(CMakeTestSystemPackage "")
@ -330,6 +336,8 @@ configure_package_config_file(RelocatableConfig.cmake.in "${CMAKE_CURRENT_BINARY
PATH_VARS INCLUDE_INSTALL_DIR SHARE_INSTALL_DIR CURRENT_BUILD_DIR PATH_VARS INCLUDE_INSTALL_DIR SHARE_INSTALL_DIR CURRENT_BUILD_DIR
) )
set(Relocatable_FIND_COMPONENTS AComp BComp CComp)
set(Relocatable_FIND_REQUIRED_BComp 1)
include("${CMAKE_CURRENT_BINARY_DIR}/RelocatableConfig.cmake") include("${CMAKE_CURRENT_BINARY_DIR}/RelocatableConfig.cmake")
if(NOT "${RELOC_INCLUDE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/include") if(NOT "${RELOC_INCLUDE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/include")
@ -344,6 +352,14 @@ if(NOT "${RELOC_BUILD_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to \"${RELOC_BUILD_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}\")") message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to \"${RELOC_BUILD_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}\")")
endif() endif()
if(NOT DEFINED Relocatable_FOUND)
message(SEND_ERROR "Relocatable_FOUND not defined !")
endif()
if(Relocatable_FOUND)
message(SEND_ERROR "Relocatable_FOUND set to TRUE !")
endif()
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# Test write_basic_config_version_file(). # Test write_basic_config_version_file().

View File

@ -0,0 +1,10 @@
set(LOC_FOO TRUE)
set(LotsOfComponents_AComp_FOUND TRUE)
set(LotsOfComponents_BComp_FOUND FALSE)
set(LotsOfComponents_CComp_FOUND TRUE)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LotsOfComponents REQUIRED_VARS LOC_FOO
HANDLE_COMPONENTS)

View File

@ -3,3 +3,9 @@
set(RELOC_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") set(RELOC_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set(RELOC_SHARE_DIR "@PACKAGE_SHARE_INSTALL_DIR@") set(RELOC_SHARE_DIR "@PACKAGE_SHARE_INSTALL_DIR@")
set_and_check(RELOC_BUILD_DIR "@PACKAGE_CURRENT_BUILD_DIR@") set_and_check(RELOC_BUILD_DIR "@PACKAGE_CURRENT_BUILD_DIR@")
set(Relocatable_AComp_FOUND TRUE)
set(Relocatable_BComp_FOUND FALSE)
set(Relocatable_CComp_FOUND FALSE)
check_required_components(Relocatable)

View File

@ -0,0 +1,8 @@
CMake Error at ComponentRequiredAndOptional.cmake:1 \(find_package\):
find_package called with components that are both required and optional:
CompA
CompB
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1 @@
find_package(NotHere REQUIRED CompA CompB CompC OPTIONAL_COMPONENTS CompA CompB CompD)

View File

@ -1,5 +1,6 @@
include(RunCMake) include(RunCMake)
run_cmake(ComponentRequiredAndOptional)
run_cmake(MissingNormal) run_cmake(MissingNormal)
run_cmake(MissingNormalRequired) run_cmake(MissingNormalRequired)
run_cmake(MissingNormalVersion) run_cmake(MissingNormalVersion)