Commit 31be918b authored by Pierluigi Taddei's avatar Pierluigi Taddei Committed by Brad King

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.
parent 01014031
Pipeline #26295 passed with stage
......@@ -170,11 +170,21 @@ is acceptable the following variables are set:
``<package>_VERSION_COUNT``
number of version components, 0 to 4
and the corresponding package configuration file is loaded. When
multiple package configuration files are available whose version files
and the corresponding package configuration file is loaded.
When multiple package configuration files are available whose version files
claim compatibility with the version requested it is unspecified which
one is chosen. No attempt is made to choose a highest or closest
version number.
one is chosen: unless the variable :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`
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.
Much of the interface is provided for completeness and for use
......
......@@ -38,6 +38,8 @@ Variables that Provide Information
/variable/CMAKE_EXTRA_GENERATOR
/variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES
/variable/CMAKE_FIND_PACKAGE_NAME
/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
/variable/CMAKE_FIND_PACKAGE_SORT_ORDER
/variable/CMAKE_GENERATOR
/variable/CMAKE_GENERATOR_PLATFORM
/variable/CMAKE_GENERATOR_TOOLSET
......
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.
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.
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).
......@@ -12,6 +12,7 @@
#include "cmFindPackageCommand.h"
#include "cmAlgorithms.h"
#include <cmSystemTools.h>
#include <cmsys/Directory.hxx>
#include <cmsys/Encoding.hxx>
#include <cmsys/RegularExpression.hxx>
......@@ -33,6 +34,45 @@ cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::Builds(
cmFindPackageCommand::PathLabel
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()
{
this->CMakePathName = "PACKAGE";
......@@ -58,7 +98,8 @@ cmFindPackageCommand::cmFindPackageCommand()
this->VersionFoundTweak = 0;
this->VersionFoundCount = 0;
this->RequiredCMakeVersion = 0;
this->SortOrder = None;
this->SortDirection = Asc;
this->AppendSearchPathGroups();
}
......@@ -135,6 +176,23 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args,
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.
this->SelectDefaultRootPathMode();
......@@ -1666,17 +1724,33 @@ private:
class cmFileListGeneratorProject : public cmFileListGeneratorBase
{
public:
cmFileListGeneratorProject(std::vector<std::string> const& names)
cmFileListGeneratorProject(std::vector<std::string> const& names,
cmFindPackageCommand::SortOrderType so,
cmFindPackageCommand::SortDirectionType sd)
: cmFileListGeneratorBase()
, Names(names)
{
this->SetSort(so, sd);
}
cmFileListGeneratorProject(cmFileListGeneratorProject const& r)
: cmFileListGeneratorBase()
, 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:
std::vector<std::string> const& Names;
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();
i != matches.end(); ++i) {
if (this->Consider(parent + *i, lister)) {
......@@ -1895,7 +1976,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names);
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) {
return true;
}
......@@ -1905,7 +1987,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) {
return true;
......@@ -1932,7 +2015,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorFixed("cmake") /
cmFileListGeneratorProject(this->Names);
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) {
return true;
}
......@@ -1943,7 +2027,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names);
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) {
return true;
}
......@@ -1954,7 +2039,8 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) {
return true;
......@@ -1965,10 +2051,12 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorFixed("cmake") /
cmFileListGeneratorProject(this->Names);
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) {
return true;
}
......@@ -1978,9 +2066,11 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names);
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection);
if (lister.Search()) {
return true;
}
......@@ -1990,9 +2080,11 @@ bool cmFindPackageCommand::SearchPrefix(std::string const& prefix_in)
{
cmFindPackageFileList lister(this);
lister / cmFileListGeneratorFixed(prefix) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorEnumerate(common) /
cmFileListGeneratorProject(this->Names) /
cmFileListGeneratorProject(this->Names, this->SortOrder,
this->SortDirection) /
cmFileListGeneratorCaseInsensitive("cmake");
if (lister.Search()) {
return true;
......
......@@ -24,6 +24,27 @@ class cmFindPackageFileList;
class cmFindPackageCommand : public cmFindCommon
{
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();
/**
......@@ -156,6 +177,11 @@ private:
std::vector<std::string> Configs;
std::set<std::string> IgnoredPaths;
/*! the selected sortOrder (None by default)*/
SortOrderType SortOrder;
/*! the selected sortDirection (Asc by default)*/
SortDirectionType SortDirection;
struct ConfigFileInfo
{
std::string filename;
......
......@@ -11,6 +11,7 @@ set(CMakeLib_TESTS
testUTF8
testXMLParser
testXMLSafe
testFindPackageCommand
)
set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
......
/*============================================================================
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;
}
......@@ -633,3 +633,33 @@ endif()
if(PACKAGE_VERSION_UNSUITABLE)
message(SEND_ERROR "PACKAGE_VERSION_UNSUITABLE set, but must not be !")
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 )
set(SORT_LIB_VERSION 3.1.1)
message("SortLib 3.1.1 config reached")
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()
set(SORT_LIB_VERSION 3.10.1)
message("SortLib 3.10.1 config reached")
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()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment