find_package: Optionally sort globbed directories in a meaningful order

Add `CMAKE_FIND_PACKAGE_SORT_{ORDER,DIRECTION}` variables to specify
sort order and direction.

When multiple package with the same name have been found in the same
location sorting option can be used to force a specific version to be
loaded (e.g. libA_1.12.0 instead of libA_1.1.0).  Currently sorting by
NAME and by NATURAL order have been implemented.

Natural ordering makes use of the `strverscmp(3)` ordering.
This commit is contained in:
Pierluigi Taddei 2016-09-08 20:26:59 +02:00 committed by Brad King
parent 010140311a
commit 31be918b0b
14 changed files with 341 additions and 17 deletions

View File

@ -170,11 +170,21 @@ is acceptable the following variables are set:
``<package>_VERSION_COUNT`` ``<package>_VERSION_COUNT``
number of version components, 0 to 4 number of version components, 0 to 4
and the corresponding package configuration file is loaded. When and the corresponding package configuration file is loaded.
multiple package configuration files are available whose version files When multiple package configuration files are available whose version files
claim compatibility with the version requested it is unspecified which claim compatibility with the version requested it is unspecified which
one is chosen. No attempt is made to choose a highest or closest one is chosen: unless the variable :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`
version number. is set no attempt is made to choose a highest or closest version number.
To control the order in which ``find_package`` checks for compatibiliy use
the two variables :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` and
:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`.
For instance in order to select the highest version one can set::
SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
before calling ``find_package``.
Config mode provides an elaborate interface and search procedure. Config mode provides an elaborate interface and search procedure.
Much of the interface is provided for completeness and for use Much of the interface is provided for completeness and for use

View File

@ -38,6 +38,8 @@ Variables that Provide Information
/variable/CMAKE_EXTRA_GENERATOR /variable/CMAKE_EXTRA_GENERATOR
/variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES /variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES
/variable/CMAKE_FIND_PACKAGE_NAME /variable/CMAKE_FIND_PACKAGE_NAME
/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
/variable/CMAKE_FIND_PACKAGE_SORT_ORDER
/variable/CMAKE_GENERATOR /variable/CMAKE_GENERATOR
/variable/CMAKE_GENERATOR_PLATFORM /variable/CMAKE_GENERATOR_PLATFORM
/variable/CMAKE_GENERATOR_TOOLSET /variable/CMAKE_GENERATOR_TOOLSET

View File

@ -0,0 +1,13 @@
find_package-dir-sort
---------------------
* The :command:`find_package` command gained the possibility of
sorting compatible libraries by ``NAME`` or by ``NATURAL`` sorting by
setting the two new variables :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`
and :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`.
* Variable :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` was added to control
the sorting mode of the :command:`find_package` command.
* Variable :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` was added to control
the sorting direction the :command:`find_package` command.

View File

@ -0,0 +1,16 @@
CMAKE_FIND_PACKAGE_SORT_DIRECTION
---------------------------------
The sorting direction used by :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`.
It can assume one of the following values:
``DEC``
Default. Ordering is done in descending mode.
The highest folder found will be tested first.
``ASC``
Ordering is done in ascending mode.
The lowest folder found will be tested first.
If :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` is not set or is set to ``NONE``
this variable has no effect.

View File

@ -0,0 +1,36 @@
CMAKE_FIND_PACKAGE_SORT_ORDER
-----------------------------
The default order for sorting packages found using :command:`find_package`.
It can assume one of the following values:
``NONE``
Default. No attempt is done to sort packages.
The first valid package found will be selected.
``NAME``
Sort packages lexicographically before selecting one.
``NATURAL``
Sort packages using natural order (see ``strverscmp(3)`` manual),
i.e. such that contiguous digits are compared as whole numbers.
Natural sorting can be employed to return the highest version when multiple
versions of the same library are found by :command:`find_package`. For
example suppose that the following libraries have been found:
* libX-1.1.0
* libX-1.2.9
* libX-1.2.10
By setting ``NATURAL`` order we can select the one with the highest
version number ``libX-1.2.10``.
.. code-block:: cmake
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
find_package(libX CONFIG)
The sort direction can be controlled using the
:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable
(by default decrescent, e.g. lib-B will be tested before lib-A).

View File

@ -12,6 +12,7 @@
#include "cmFindPackageCommand.h" #include "cmFindPackageCommand.h"
#include "cmAlgorithms.h" #include "cmAlgorithms.h"
#include <cmSystemTools.h>
#include <cmsys/Directory.hxx> #include <cmsys/Directory.hxx>
#include <cmsys/Encoding.hxx> #include <cmsys/Encoding.hxx>
#include <cmsys/RegularExpression.hxx> #include <cmsys/RegularExpression.hxx>
@ -33,6 +34,45 @@ cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::Builds(
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel
cmFindPackageCommand::PathLabel::SystemRegistry("SYSTEM_PACKAGE_REGISTRY"); cmFindPackageCommand::PathLabel::SystemRegistry("SYSTEM_PACKAGE_REGISTRY");
struct StrverscmpGreater
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
return cmSystemTools::strverscmp(lhs, rhs) > 0;
}
};
struct StrverscmpLesser
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
return cmSystemTools::strverscmp(lhs, rhs) < 0;
}
};
void cmFindPackageCommand::Sort(std::vector<std::string>::iterator begin,
std::vector<std::string>::iterator end,
SortOrderType order, SortDirectionType dir)
{
if (order == Name_order) {
if (dir == Dec) {
std::sort(begin, end, std::greater<std::string>());
} else {
std::sort(begin, end);
}
} else if (order == Natural)
// natural order uses letters and numbers (contiguous numbers digit are
// compared such that e.g. 000 00 < 01 < 010 < 09 < 0 < 1 < 9 < 10
{
if (dir == Dec) {
std::sort(begin, end, StrverscmpGreater());
} else {
std::sort(begin, end, StrverscmpLesser());
}
}
// else do not sort
}
cmFindPackageCommand::cmFindPackageCommand() cmFindPackageCommand::cmFindPackageCommand()
{ {
this->CMakePathName = "PACKAGE"; this->CMakePathName = "PACKAGE";
@ -58,7 +98,8 @@ cmFindPackageCommand::cmFindPackageCommand()
this->VersionFoundTweak = 0; this->VersionFoundTweak = 0;
this->VersionFoundCount = 0; this->VersionFoundCount = 0;
this->RequiredCMakeVersion = 0; this->RequiredCMakeVersion = 0;
this->SortOrder = None;
this->SortDirection = Asc;
this->AppendSearchPathGroups(); this->AppendSearchPathGroups();
} }
@ -135,6 +176,23 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args,
this->NoSystemRegistry = true; this->NoSystemRegistry = true;
} }
// Check if Sorting should be enabled
if (const char* so =
this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_SORT_ORDER")) {
if (strcmp(so, "NAME") == 0) {
this->SortOrder = Name_order;
} else if (strcmp(so, "NATURAL") == 0) {
this->SortOrder = Natural;
} else {
this->SortOrder = None;
}
}
if (const char* sd =
this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_SORT_DIRECTION")) {
this->SortDirection = strcmp(sd, "ASC") == 0 ? Asc : Dec;
}
// Find the current root path mode. // Find the current root path mode.
this->SelectDefaultRootPathMode(); this->SelectDefaultRootPathMode();
@ -1666,17 +1724,33 @@ private:
class cmFileListGeneratorProject : public cmFileListGeneratorBase class cmFileListGeneratorProject : public cmFileListGeneratorBase
{ {
public: public:
cmFileListGeneratorProject(std::vector<std::string> const& names) cmFileListGeneratorProject(std::vector<std::string> const& names,
cmFindPackageCommand::SortOrderType so,
cmFindPackageCommand::SortDirectionType sd)
: cmFileListGeneratorBase() : cmFileListGeneratorBase()
, Names(names) , Names(names)
{ {
this->SetSort(so, sd);
} }
cmFileListGeneratorProject(cmFileListGeneratorProject const& r) cmFileListGeneratorProject(cmFileListGeneratorProject const& r)
: cmFileListGeneratorBase() : cmFileListGeneratorBase()
, Names(r.Names) , Names(r.Names)
{ {
this->SetSort(r.SortOrder, r.SortDirection);
} }
void SetSort(cmFindPackageCommand::SortOrderType o,
cmFindPackageCommand::SortDirectionType d)
{
SortOrder = o;
SortDirection = d;
}
protected:
// sort parameters
cmFindPackageCommand::SortOrderType SortOrder;
cmFindPackageCommand::SortDirectionType SortDirection;
private: private:
std::vector<std::string> const& Names; std::vector<std::string> const& Names;
bool Search(std::string const& parent, cmFileList& lister) CM_OVERRIDE bool Search(std::string const& parent, cmFileList& lister) CM_OVERRIDE
@ -1698,6 +1772,13 @@ private:
} }
} }
// before testing the matches check if there is a specific sorting order to
// perform
if (this->SortOrder != cmFindPackageCommand::None) {
cmFindPackageCommand::Sort(matches.begin(), matches.end(), SortOrder,
SortDirection);
}
for (std::vector<std::string>::const_iterator i = matches.begin(); for (std::vector<std::string>::const_iterator i = matches.begin();
i != matches.end(); ++i) { i != matches.end(); ++i) {
if (this->Consider(parent + *i, lister)) { if (this->Consider(parent + *i, lister)) {
@ -1895,7 +1976,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{ {
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names); cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) { if (lister.Search()) {
return true; return true;
} }
@ -1905,7 +1987,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{ {
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake"); cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) { if (lister.Search()) {
return true; return true;
@ -1932,7 +2015,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorFixed("cmake") / cmFileListGeneratorFixed("cmake") /
cmFileListGeneratorProject(this->Names); cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) { if (lister.Search()) {
return true; return true;
} }
@ -1943,7 +2027,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names); cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) { if (lister.Search()) {
return true; return true;
} }
@ -1954,7 +2039,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake"); cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) { if (lister.Search()) {
return true; return true;
@ -1965,10 +2051,12 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{ {
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorFixed("cmake") / cmFileListGeneratorFixed("cmake") /
cmFileListGeneratorProject(this->Names); cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) { if (lister.Search()) {
return true; return true;
} }
@ -1978,9 +2066,11 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{ {
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names); cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) { if (lister.Search()) {
return true; return true;
} }
@ -1990,9 +2080,11 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{ {
cmFindPackageFileList lister(this); cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) / lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) / cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names) / cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake"); cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) { if (lister.Search()) {
return true; return true;

View File

@ -24,6 +24,27 @@ class cmFindPackageFileList;
class cmFindPackageCommand : public cmFindCommon class cmFindPackageCommand : public cmFindCommon
{ {
public: public:
/*! A sorting order strategy to be applied to recovered package folders (see
* FIND_PACKAGE_SORT_ORDER)*/
enum /*class*/ SortOrderType
{
None,
Name_order,
Natural
};
/*! A sorting direction to be applied to recovered package folders (see
* FIND_PACKAGE_SORT_DIRECTION)*/
enum /*class*/ SortDirectionType
{
Asc,
Dec
};
/*! sorts a given list of string based on the input sort parameters */
static void Sort(std::vector<std::string>::iterator begin,
std::vector<std::string>::iterator end, SortOrderType order,
SortDirectionType dir);
cmFindPackageCommand(); cmFindPackageCommand();
/** /**
@ -156,6 +177,11 @@ private:
std::vector<std::string> Configs; std::vector<std::string> Configs;
std::set<std::string> IgnoredPaths; std::set<std::string> IgnoredPaths;
/*! the selected sortOrder (None by default)*/
SortOrderType SortOrder;
/*! the selected sortDirection (Asc by default)*/
SortDirectionType SortDirection;
struct ConfigFileInfo struct ConfigFileInfo
{ {
std::string filename; std::string filename;

View File

@ -11,6 +11,7 @@ set(CMakeLib_TESTS
testUTF8 testUTF8
testXMLParser testXMLParser
testXMLSafe testXMLSafe
testFindPackageCommand
) )
set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR}) set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,76 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2000-2011 Kitware, Inc., Insight Software Consortium
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFindPackageCommand.h"
#include <iostream>
#include <string>
#define cmPassed(m) std::cout << "Passed: " << (m) << "\n"
#define cmFailed(m) \
std::cout << "FAILED: " << (m) << "\n"; \
failed = 1
int testFindPackageCommand(int /*unused*/, char* /*unused*/ [])
{
int failed = 0;
// ----------------------------------------------------------------------
// Test cmFindPackage::Sort
std::vector<std::string> testString;
testString.push_back("lib-0.0");
testString.push_back("lib-1.2");
testString.push_back("lib-2.0");
testString.push_back("lib-19.0.1");
testString.push_back("lib-20.01.1");
testString.push_back("lib-20.2.2a");
cmFindPackageCommand::Sort(testString.begin(), testString.end(),
cmFindPackageCommand::Natural,
cmFindPackageCommand::Asc);
if (!(testString[0] == "lib-0.0" && testString[1] == "lib-1.2" &&
testString[2] == "lib-2.0" && testString[3] == "lib-19.0.1" &&
testString[4] == "lib-20.01.1" && testString[5] == "lib-20.2.2a")) {
cmFailed("cmSystemTools::Sort fail with Natural ASC");
}
cmFindPackageCommand::Sort(testString.begin(), testString.end(),
cmFindPackageCommand::Natural,
cmFindPackageCommand::Dec);
if (!(testString[5] == "lib-0.0" && testString[4] == "lib-1.2" &&
testString[3] == "lib-2.0" && testString[2] == "lib-19.0.1" &&
testString[1] == "lib-20.01.1" && testString[0] == "lib-20.2.2a")) {
cmFailed("cmSystemTools::Sort fail with Natural ASC");
}
cmFindPackageCommand::Sort(testString.begin(), testString.end(),
cmFindPackageCommand::Name_order,
cmFindPackageCommand::Dec);
if (!(testString[5] == "lib-0.0" && testString[4] == "lib-1.2" &&
testString[3] == "lib-19.0.1" && testString[2] == "lib-2.0" &&
testString[1] == "lib-20.01.1" && testString[0] == "lib-20.2.2a")) {
cmFailed("cmSystemTools::Sort fail with Name DEC");
}
cmFindPackageCommand::Sort(testString.begin(), testString.end(),
cmFindPackageCommand::Name_order,
cmFindPackageCommand::Asc);
if (!(testString[0] == "lib-0.0" && testString[1] == "lib-1.2" &&
testString[2] == "lib-19.0.1" && testString[3] == "lib-2.0" &&
testString[4] == "lib-20.01.1" && testString[5] == "lib-20.2.2a")) {
cmFailed("cmSystemTools::Sort fail with Natural ASC");
}
if (!failed) {
cmPassed("cmSystemTools::Sort working");
}
return failed;
}

View File

@ -633,3 +633,33 @@ endif()
if(PACKAGE_VERSION_UNSUITABLE) if(PACKAGE_VERSION_UNSUITABLE)
message(SEND_ERROR "PACKAGE_VERSION_UNSUITABLE set, but must not be !") message(SEND_ERROR "PACKAGE_VERSION_UNSUITABLE set, but must not be !")
endif() endif()
############################################################################
##Test FIND_PACKAGE using sorting
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
FIND_PACKAGE(SortLib CONFIG)
IF (NOT "${SortLib_VERSION}" STREQUAL "3.1.1")
message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Name Asc! ${SortLib_VERSION}")
endif()
unset(SortLib_VERSION)
set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
FIND_PACKAGE(SortLib CONFIG)
IF (NOT "${SortLib_VERSION}" STREQUAL "3.10.1")
message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Natural! Dec ${SortLib_VERSION}")
endif()
set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
unset(SortLib_VERSION)
unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
set(CMAKE_PREFIX_PATH )

View File

@ -0,0 +1,2 @@
set(SORT_LIB_VERSION 3.1.1)
message("SortLib 3.1.1 config reached")

View File

@ -0,0 +1,9 @@
set(PACKAGE_VERSION 3.1.1)
if(PACKAGE_FIND_VERSION_MAJOR EQUAL 3)
if(PACKAGE_FIND_VERSION_MINOR EQUAL 1)
set(PACKAGE_VERSION_COMPATIBLE 1)
if(PACKAGE_FIND_VERSION_PATCH EQUAL 1)
set(PACKAGE_VERSION_EXACT 1)
endif()
endif()
endif()

View File

@ -0,0 +1,2 @@
set(SORT_LIB_VERSION 3.10.1)
message("SortLib 3.10.1 config reached")

View File

@ -0,0 +1,9 @@
set(PACKAGE_VERSION 3.10.1)
if(PACKAGE_FIND_VERSION_MAJOR EQUAL 3)
if(PACKAGE_FIND_VERSION_MINOR EQUAL 10)
set(PACKAGE_VERSION_COMPATIBLE 1)
if(PACKAGE_FIND_VERSION_PATCH EQUAL 1)
set(PACKAGE_VERSION_EXACT 1)
endif()
endif()
endif()