ENH: Added version support to Config mode of find_package command.

- Added EXACT option to request an exact version.
  - Enforce version using check provided by package.
  - Updated FindPackageTest to test versioning in config mode.
This commit is contained in:
Brad King 2008-01-28 20:38:48 -05:00
parent 404db8811e
commit 41c2895b75
12 changed files with 356 additions and 20 deletions

View File

@ -67,19 +67,20 @@ line.
A FindXXX.cmake module will typically be loaded by the command
FIND_PACKAGE(XXX [major[.minor[.patch]]]
FIND_PACKAGE(XXX [major[.minor[.patch]]] [EXACT]
[QUIET] [REQUIRED [components...]])
If any version numbers are given to the command it will set the
variable XXX_FIND_VERSION to contain the whole version. The variables
XXX_FIND_VERSION_MAJOR, XXX_FIND_VERSION_MINOR, and
XXX_FIND_VERSION_PATCH will be set to contain the corresponding
portions of the version number. If the find module supports
versioning it should locate a version of the package that is
compatible with the version requested. If a compatible version of the
package cannot be found the module should not report success. The
version of the package found should be stored in the version variables
named above.
portions of the version number. The variable XXX_FIND_VERSION_EXACT
will indicate whether an exact version is requested.
If the find module supports versioning it should locate a version of
the package that is compatible with the version requested. If a
compatible version of the package cannot be found the module should
not report success. The version of the package found should be stored
in the version variables named above.
If the QUIET option is given to the command it will set the variable
XXX_FIND_QUIETLY to true before loading the FindXXX.cmake module. If

View File

@ -67,8 +67,13 @@ cmFindPackageCommand::cmFindPackageCommand()
this->VersionMinor = 0;
this->VersionPatch = 0;
this->VersionCount = 0;
this->VersionExact = false;
this->VersionFoundMajor = 0;
this->VersionFoundMinor = 0;
this->VersionFoundPatch = 0;
this->VersionFoundCount = 0;
this->CommandDocumentation =
" find_package(<package> [major[.minor[.patch]]] [QUIET]\n"
" find_package(<package> [major[.minor[.patch]]] [EXACT] [QUIET]\n"
" [[REQUIRED|COMPONENTS] [components...]])\n"
"Finds and loads settings from an external project. "
"<package>_FOUND will be set to indicate whether the package was found. "
@ -82,8 +87,9 @@ cmFindPackageCommand::cmFindPackageCommand()
"option is given. "
"The \"[major[.minor[.patch]]]\" version argument specifies a desired "
"version with which the package found should be compatible. "
"The EXACT option requests that the version be matched exactly. "
"Version support is currently provided only on a package-by-package "
"basis and is not enforced by the command.\n"
"basis (details below).\n"
"User code should generally look for packages using the above simple "
"signature. The remainder of this command documentation specifies the "
"full command signature and details of the search process. Project "
@ -96,12 +102,14 @@ cmFindPackageCommand::cmFindPackageCommand()
"CMake searches for a file called \"Find<package>.cmake\" in "
"the CMAKE_MODULE_PATH followed by the CMake installation. "
"If the file is found, it is read and processed by CMake. "
"It is responsible for finding the package or producing an error message "
"if package content cannot be found. "
"Otherwise the command proceeds to Config mode.\n"
"It is responsible for finding the package, checking the version, "
"and producing any needed messages. "
"Many find-modules provide limited or no support for versioning; "
"check the module documentation. "
"If no module is found the command proceeds to Config mode.\n"
"The complete Config mode command signature is:\n"
" find_package(<package> [major[.minor[.patch]]] [QUIET] [NO_MODULE]\n"
" [[REQUIRED|COMPONENTS] [components...]]\n"
" find_package(<package> [major[.minor[.patch]]] [EXACT] [QUIET]\n"
" [[REQUIRED|COMPONENTS] [components...]] [NO_MODULE]\n"
" [NAMES name1 [name2 ...]]\n"
" [CONFIGS config1 [config2 ...]]\n"
" [PATHS path1 [path2 ... ]]\n"
@ -122,7 +130,6 @@ cmFindPackageCommand::cmFindPackageCommand()
"Config mode attempts to locate a configuration file provided by the "
"package to be found. A cache entry called <package>_DIR is created to "
"hold the directory containing the file. "
"Currently versioning is not implemented by Config mode. "
"By default the command searches for a package with the name <package>. "
"If the NAMES option is given the names following it are used instead "
"of <package>. "
@ -144,11 +151,49 @@ cmFindPackageCommand::cmFindPackageCommand()
"a configuration file a fatal error is always generated because user "
"intervention is required."
"\n"
"When the \"[major[.minor[.patch]]]\" version argument is specified "
"Config mode will only find a version of the package that claims "
"compatibility with the requested version. "
"If the EXACT option is given only a version of the package claiming "
"an exact match of the requested version may be found. "
"CMake does not establish any convention for the meaning of version "
"numbers. "
"Package version numbers are checked by \"version\" files provided by "
"the packages themselves. "
"For a candidate package confguration file \"<config-file>.cmake\" the "
"corresponding version file is located next to it and named either "
"\"<config-file>-version.cmake\" or \"<config-file>Version.cmake\". "
"If no such version file is available then the configuration file "
"is assumed to not be compatible with any requested version. "
"When a version file is found it is loaded to check the requested "
"version number. "
"The version file is loaded in a nested scope in which the following "
"variables have been defined:\n"
" PACKAGE_FIND_NAME = the <package> name\n"
" PACKAGE_FIND_VERSION = full requested version string\n"
" PACKAGE_FIND_VERSION_MAJOR = requested major version, if any\n"
" PACKAGE_FIND_VERSION_MINOR = requested minor version, if any\n"
" PACKAGE_FIND_VERSION_PATCH = requested patch version, if any\n"
"The version file checks whether it satisfies the requested version "
"and sets these variables:\n"
" PACKAGE_VERSION = package version (major[.minor[.patch]])\n"
" PACKAGE_VERSION_EXACT = true if version is exact match\n"
" PACKAGE_VERSION_COMPATIBLE = true if version is compatible\n"
"These variables are checked by the find_package command to determine "
"whether the configuration file provides an acceptable version. "
"They are not available after the find_package call returns. "
"If the version is acceptable the following variables are set:\n"
" <package>_VERSION = package version (major[.minor[.patch]])\n"
" <package>_VERSION_MAJOR = major from major[.minor[.patch]], if any\n"
" <package>_VERSION_MINOR = minor from major[.minor[.patch]], if any\n"
" <package>_VERSION_PATCH = patch from major[.minor[.patch]], if any\n"
"and the corresponding package configuration file is loaded."
"\n"
"Config mode provides an elaborate interface and search procedure. "
"Much of the interface is provided for completeness and for use "
"internally by find-modules loaded by Module mode. "
"Most user code should simply call\n"
" find_package(<package> [REQUIRED|QUIET])\n"
" find_package(<package> [major[.minor]] [EXACT] [REQUIRED|QUIET])\n"
"in order to find a package. Package maintainers providing CMake "
"package configuration files are encouraged to name and install "
"them such that the procedure outlined below will find them "
@ -275,6 +320,12 @@ bool cmFindPackageCommand
this->Quiet = true;
doing = DoingNone;
}
else if(args[i] == "EXACT")
{
this->VersionExact = true;
this->Compatibility_1_6 = false;
doing = DoingNone;
}
else if(args[i] == "NO_MODULE")
{
this->NoModule = true;
@ -353,6 +404,15 @@ bool cmFindPackageCommand
}
else if(doing == DoingConfigs)
{
if(args[i].find_first_of(":/\\") != args[i].npos ||
cmSystemTools::GetFilenameLastExtension(args[i]) != ".cmake")
{
cmOStringStream e;
e << "given CONFIGS option followed by invalid file name \""
<< args[i] << "\". The names given must be file names without "
<< "a path and with a \".cmake\" extension.";
return false;
}
this->Configs.push_back(args[i]);
}
else if(!haveVersion && version.find(args[i].c_str()))
@ -494,6 +554,12 @@ bool cmFindPackageCommand::FindModule(bool& found)
} // no break
default: break;
}
// Tell the module whether an exact version has been requested.
std::string exact = this->Name;
exact += "_FIND_VERSION_EXACT";
this->Makefile->AddDefinition(exact.c_str(),
this->VersionExact? "1":"0");
}
// Load the module we found.
@ -555,8 +621,12 @@ bool cmFindPackageCommand::HandlePackageMode()
}
// Find the configuration file.
if(this->FindConfigFile(dir, file))
if(this->FindConfigFileToLoad(dir, file))
{
// Set the version variables before loading the config file.
// It may override them.
this->StoreVersionFound();
// Parse the configuration file.
if(this->ReadListFile(file.c_str()))
{
@ -576,7 +646,7 @@ bool cmFindPackageCommand::HandlePackageMode()
e << "cannot find package " << this->Name << " because "
<< this->Variable << " is set to \"" << def << "\" "
<< "which is not a directory containing a package configuration "
<< "file. "
<< "file (or it is not for the requested version). "
<< "Please set the cache entry " << this->Variable << " "
<< "to the correct directory, or delete it to ask CMake "
<< "to search.";
@ -1009,6 +1079,8 @@ bool cmFindPackageCommand::CheckDirectory(std::string const& dir)
std::string d = dir.substr(0, dir.size()-1);
if(this->FindConfigFile(d, this->FileFound))
{
// Remove duplicate slashes.
cmSystemTools::ConvertToUnixSlashes(this->FileFound);
return true;
}
return false;
@ -1028,7 +1100,8 @@ bool cmFindPackageCommand::FindConfigFile(std::string const& dir,
{
fprintf(stderr, "Checking file [%s]\n", file.c_str());
}
if(cmSystemTools::FileExists(file.c_str(), true))
if(cmSystemTools::FileExists(file.c_str(), true) &&
this->CheckVersion(file))
{
return true;
}
@ -1036,6 +1109,182 @@ bool cmFindPackageCommand::FindConfigFile(std::string const& dir,
return false;
}
//----------------------------------------------------------------------------
bool cmFindPackageCommand::FindConfigFileToLoad(std::string const& dir,
std::string& file)
{
if(this->FileFound.empty())
{
// The file location was cached. Look for the correct file.
return this->FindConfigFile(dir, file);
}
else
{
// The file location was just found during this call.
// Use the file found without searching again.
file = this->FileFound;
return true;
}
}
//----------------------------------------------------------------------------
bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
{
// Get the filename without the .cmake extension.
std::string::size_type pos = config_file.rfind('.');
std::string version_file_base = config_file.substr(0, pos);
// Look for foo-config-version.cmake
std::string version_file = version_file_base;
version_file += "-version.cmake";
if(cmSystemTools::FileExists(version_file.c_str(), true))
{
return this->CheckVersionFile(version_file);
}
// Look for fooConfigVersion.cmake
version_file = version_file_base;
version_file += "Version.cmake";
if(cmSystemTools::FileExists(version_file.c_str(), true))
{
return this->CheckVersionFile(version_file);
}
// If no version was requested a versionless package is acceptable.
if(this->Version.empty())
{
return true;
}
// No version file found. Assume the version is incompatible.
return false;
}
//----------------------------------------------------------------------------
bool cmFindPackageCommand::CheckVersionFile(std::string const& version_file)
{
// The version file will be loaded in an isolated scope.
this->Makefile->PushScope();
// Clear the output variables.
this->Makefile->RemoveDefinition("PACKAGE_VERSION");
this->Makefile->RemoveDefinition("PACKAGE_VERSION_COMPATIBLE");
this->Makefile->RemoveDefinition("PACKAGE_VERSION_EXACT");
// Set the input variables.
this->Makefile->AddDefinition("PACKAGE_FIND_NAME", this->Name.c_str());
this->Makefile->AddDefinition("PACKAGE_FIND_VERSION",
this->Version.c_str());
if(this->VersionCount >= 3)
{
char buf[64];
sprintf(buf, "%u", this->VersionPatch);
this->Makefile->AddDefinition("PACKAGE_FIND_VERSION_PATCH", buf);
}
else
{
this->Makefile->RemoveDefinition("PACKAGE_FIND_VERSION_PATCH");
}
if(this->VersionCount >= 2)
{
char buf[64];
sprintf(buf, "%u", this->VersionMinor);
this->Makefile->AddDefinition("PACKAGE_FIND_VERSION_MINOR", buf);
}
else
{
this->Makefile->RemoveDefinition("PACKAGE_FIND_VERSION_MINOR");
}
if(this->VersionCount >= 1)
{
char buf[64];
sprintf(buf, "%u", this->VersionMajor);
this->Makefile->AddDefinition("PACKAGE_FIND_VERSION_MAJOR", buf);
}
else
{
this->Makefile->RemoveDefinition("PACKAGE_FIND_VERSION_MAJOR");
}
// Load the version check file.
bool found = false;
if(this->ReadListFile(version_file.c_str()))
{
// Check the output variables.
found = this->Makefile->IsOn("PACKAGE_VERSION_EXACT");
if(!found && !this->VersionExact)
{
found = this->Makefile->IsOn("PACKAGE_VERSION_COMPATIBLE");
}
if(found || this->Version.empty())
{
// Get the version found.
this->VersionFound =
this->Makefile->GetSafeDefinition("PACKAGE_VERSION");
// Try to parse the version number and store the results that were
// successfully parsed.
unsigned int parsed_major;
unsigned int parsed_minor;
unsigned int parsed_patch;
this->VersionFoundCount =
sscanf(this->VersionFound.c_str(), "%u.%u.%u",
&parsed_major, &parsed_minor, &parsed_patch);
switch(this->VersionFoundCount)
{
case 3: this->VersionFoundPatch = parsed_patch; // no break!
case 2: this->VersionFoundMinor = parsed_minor; // no break!
case 1: this->VersionFoundMajor = parsed_major; // no break!
default: break;
}
}
}
// Restore the original scope.
this->Makefile->PopScope();
// Succeed if the version was found or no version was requested.
return found || this->Version.empty();
}
//----------------------------------------------------------------------------
void cmFindPackageCommand::StoreVersionFound()
{
// Store the whole version string.
std::string ver = this->Name;
ver += "_VERSION";
if(this->VersionFound.empty())
{
this->Makefile->RemoveDefinition(ver.c_str());
}
else
{
this->Makefile->AddDefinition(ver.c_str(), this->VersionFound.c_str());
}
// Store the portions that could be parsed.
char buf[64];
switch(this->VersionFoundCount)
{
case 3:
{
sprintf(buf, "%u", this->VersionFoundPatch);
this->Makefile->AddDefinition((ver+"_PATCH").c_str(), buf);
} // no break
case 2:
{
sprintf(buf, "%u", this->VersionFoundMinor);
this->Makefile->AddDefinition((ver+"_MINOR").c_str(), buf);
} // no break
case 1:
{
sprintf(buf, "%u", this->VersionFoundMajor);
this->Makefile->AddDefinition((ver+"_MAJOR").c_str(), buf);
} // no break
default: break;
}
}
//----------------------------------------------------------------------------
#include <cmsys/Directory.hxx>
#include <cmsys/Glob.hxx>

View File

@ -80,12 +80,16 @@ private:
bool FindFrameworkConfig();
bool FindAppBundleConfig();
bool ReadListFile(const char* f);
void StoreVersionFound();
void AddUserPath(std::string const& p);
void ComputePrefixes();
bool SearchDirectory(std::string const& dir);
bool CheckDirectory(std::string const& dir);
bool FindConfigFile(std::string const& dir, std::string& file);
bool FindConfigFileToLoad(std::string const& dir, std::string& file);
bool CheckVersion(std::string const& config_file);
bool CheckVersionFile(std::string const& version_file);
bool SearchPrefix(std::string const& prefix);
bool SearchFrameworkPrefix(std::string const& prefix_in);
bool SearchAppBundlePrefix(std::string const& prefix_in);
@ -100,7 +104,13 @@ private:
unsigned int VersionMinor;
unsigned int VersionPatch;
unsigned int VersionCount;
bool VersionExact;
cmStdString FileFound;
cmStdString VersionFound;
unsigned int VersionFoundMajor;
unsigned int VersionFoundMinor;
unsigned int VersionFoundPatch;
unsigned int VersionFoundCount;
bool Quiet;
bool Required;
bool Compatibility_1_6;

View File

@ -32,7 +32,10 @@ FIND_PACKAGE(VersionTestC 1.2.3)
#SET(CMAKE_FIND_DEBUG_MODE 1)
# For purposes of the test wipe out previous find results.
SET(PACKAGES foo Foo Bar TFramework Tframework TApp Tapp Special)
SET(PACKAGES
foo Foo Bar TFramework Tframework TApp Tapp Special
VersionedA VersionedB
)
FOREACH(p ${PACKAGES})
SET(${p}_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
ENDFOREACH(p)
@ -51,6 +54,8 @@ FIND_PACKAGE(Tframework)
FIND_PACKAGE(TApp)
FIND_PACKAGE(Tapp CONFIGS tapp-config.cmake)
FIND_PACKAGE(Special NAMES Suffix SuffixTest PATH_SUFFIXES test)
FIND_PACKAGE(VersionedA 2 NAMES zot)
FIND_PACKAGE(VersionedB 3.1 EXACT NAMES zot)
# Expected locations at which packages should be found.
SET(foo_EXPECTED "lib/foo-1.2/foo-config.cmake")
@ -65,6 +70,8 @@ SET(TApp_EXPECTED
"TApp.app/Contents/Resources/TAppConfig.cmake")
SET(Tapp_EXPECTED
"TApp.app/Contents/Resources/cmake/tapp-config.cmake")
SET(VersionedA_EXPECTED "lib/zot-2.0/zot-config.cmake")
SET(VersionedB_EXPECTED "lib/zot-3.1/zot-config.cmake")
# Check the results.
FOREACH(p ${PACKAGES})
@ -88,3 +95,43 @@ FOREACH(p ${PACKAGES})
MESSAGE(SEND_ERROR "Package ${p} not found!")
ENDIF(${p}_FOUND)
ENDFOREACH(p)
# Check that version information was extracted.
IF(NOT "${VersionedA_VERSION}" STREQUAL "2.0")
MESSAGE(SEND_ERROR
"Package VersionedA is version [${VersionedA_VERSION}], not [2.0]")
ENDIF(NOT "${VersionedA_VERSION}" STREQUAL "2.0")
IF(NOT "${VersionedA_VERSION_MAJOR}" STREQUAL "2")
MESSAGE(SEND_ERROR
"Package VersionedA is major version [${VersionedA_VERSION_MAJOR}], not [2]")
ENDIF(NOT "${VersionedA_VERSION_MAJOR}" STREQUAL "2")
IF(NOT "${VersionedA_VERSION_MINOR}" STREQUAL "0")
MESSAGE(SEND_ERROR
"Package VersionedA is minor version [${VersionedA_VERSION_MINOR}], not [0]")
ENDIF(NOT "${VersionedA_VERSION_MINOR}" STREQUAL "0")
IF(NOT "${VersionedB_VERSION}" STREQUAL "3.1")
MESSAGE(SEND_ERROR
"Package VersionedB is version [${VersionedB_VERSION}], not [3.1]")
ENDIF(NOT "${VersionedB_VERSION}" STREQUAL "3.1")
IF(NOT "${VersionedB_VERSION_MAJOR}" STREQUAL "3")
MESSAGE(SEND_ERROR
"Package VersionedB is major version [${VersionedB_VERSION_MAJOR}], not [3]")
ENDIF(NOT "${VersionedB_VERSION_MAJOR}" STREQUAL "3")
IF(NOT "${VersionedB_VERSION_MINOR}" STREQUAL "1")
MESSAGE(SEND_ERROR
"Package VersionedB is minor version [${VersionedB_VERSION_MINOR}], not [1]")
ENDIF(NOT "${VersionedB_VERSION_MINOR}" STREQUAL "1")
IF(NOT "${Special_VERSION}" STREQUAL "1.2")
MESSAGE(SEND_ERROR
"Package Special is version [${Special_VERSION}], not [1.2]")
ENDIF(NOT "${Special_VERSION}" STREQUAL "1.2")
IF(NOT "${Special_VERSION_MAJOR}" STREQUAL "1")
MESSAGE(SEND_ERROR
"Package Special is major version [${Special_VERSION_MAJOR}], not [1]")
ENDIF(NOT "${Special_VERSION_MAJOR}" STREQUAL "1")
IF(NOT "${Special_VERSION_MINOR}" STREQUAL "2")
MESSAGE(SEND_ERROR
"Package Special is minor version [${Special_VERSION_MINOR}], not [2]")
ENDIF(NOT "${Special_VERSION_MINOR}" STREQUAL "2")

View File

@ -0,0 +1,7 @@
SET(PACKAGE_VERSION 1.2)
IF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 1)
SET(PACKAGE_VERSION_COMPATIBLE 1)
IF("${PACKAGE_FIND_VERSION_MINOR}" EQUAL 2)
SET(PACKAGE_VERSION_EXACT 1)
ENDIF("${PACKAGE_FIND_VERSION_MINOR}" EQUAL 2)
ENDIF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 1)

View File

@ -0,0 +1 @@
# Test config file.

View File

@ -0,0 +1,5 @@
SET(PACKAGE_VERSION 2.0)
IF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 2)
SET(PACKAGE_VERSION_COMPATIBLE 1)
ENDIF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 2)

View File

@ -0,0 +1 @@
# Test config file.

View File

@ -0,0 +1,5 @@
SET(PACKAGE_VERSION 3.0)
IF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 3)
SET(PACKAGE_VERSION_COMPATIBLE 1)
ENDIF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 3)

View File

@ -0,0 +1 @@
# Test config file.

View File

@ -0,0 +1,8 @@
SET(PACKAGE_VERSION 3.1)
IF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 3)
SET(PACKAGE_VERSION_COMPATIBLE 1)
IF("${PACKAGE_FIND_VERSION_MINOR}" EQUAL 1)
SET(PACKAGE_VERSION_EXACT 1)
ENDIF("${PACKAGE_FIND_VERSION_MINOR}" EQUAL 1)
ENDIF("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL 3)

View File

@ -0,0 +1 @@
# Test config file.