Browse Source

find_package: Use deterministic search order by default

Historically, find_package() does not guarantee the order in which directories
matching a search path containing a glob expression are processed in - the
"first valid package" will be selected if there are multiple candidates. In such
cases, which package is chosen is completely random and can change, potentially
leading to build failures and reproducibility issues. This is rather unexpected
and confusing for developers.

Now that CMake has bumped its major version, it's a good time to change default
sort order and direction could be changed to natural sorting with a descending
order. That will result in the newest version of a library being picked in case
there are multiple ones available.
Moritz Haase 5 months ago
parent
commit
61d8fae116

+ 21 - 12
Help/command/find_package.rst

@@ -581,12 +581,12 @@ Paths are searched in the order described above.  The first viable package
 configuration file found is used, even if a newer version of the package
 resides later in the list of search paths.
 
-For search paths which contain glob expressions (``*``), the order in which
-directories matching the glob are searched is unspecified unless the
-:variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` variable is set.  This variable,
-along with the :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable,
-determines the order in which CMake considers glob matches.  For example, if
-the file system contains the package configuration files
+For search paths which contain glob expressions (``*``), directories matching
+the glob are searched in natural, descending order by default. This behavior
+can be overridden by setting variables :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`
+and :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` accordingly. Those variables
+determine the order in which CMake considers glob matches. For example, if the
+file system contains the package configuration files
 
 ::
 
@@ -594,21 +594,21 @@ the file system contains the package configuration files
   <prefix>/example-1.10/example-config.cmake
   <prefix>/share/example-2.0/example-config.cmake
 
-it is unspecified (when the aforementioned variables are unset) whether
-``find_package(example)`` will find ``example-1.2`` or ``example-1.10``
-(assuming that both are viable), but ``find_package`` will *not* find
-``example-2.0``, because one of the other two will be found first.
+then ``find_package(example)`` will (when the aforementioned variables are
+unset) pick ``example-1.10`` (assuming both ``example-1.2`` and ``example-1.10``
+are viable). Note however that ``find_package`` will *not* find ``example-2.0``,
+because one of the other two will be found first.
 
 To control the order in which ``find_package`` searches directories that match
 a glob expression, use :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` and
 :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`.
-For instance, to cause the above example to select ``example-1.10``,
+For instance, to cause the above example to select ``example-1.2``,
 one can set
 
 .. code-block:: cmake
 
   set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
-  set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
+  set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
 
 before calling ``find_package``.
 
@@ -624,6 +624,15 @@ before calling ``find_package``.
    and ``<prefix>/<name>.framework/Versions/*/Resources/CMake``.  In previous
    versions of CMake, this order was unspecified.
 
+.. versionchanged:: 4.2
+   When encountering multiple viable matches, ``find_package`` now picks the
+   one with the most recent version by default. In previous versions of CMake,
+   the result was unspecified. Accordingly, the default of
+   :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` has changed from ``NONE`` to
+   ``NATURAL`` and :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`
+   now defaults to ``DEC`` (descending) instead of ``ASC`` (ascending).
+
+
 .. include:: include/FIND_XXX_ROOT.rst
 .. include:: include/FIND_XXX_ORDER.rst
 

+ 9 - 4
Help/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION.rst

@@ -3,16 +3,21 @@ CMAKE_FIND_PACKAGE_SORT_DIRECTION
 
 .. versionadded:: 3.7
 
+.. versionchanged:: 4.2
+
+  The default sort direction has changed from ``DEC`` to ``ASC``.
+
+
 The sorting direction used by :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`.
 It can assume one of the following values:
 
 ``ASC``
-  Default.  Ordering is done in ascending mode.
+  Ordering is done in ascending mode.
   The lowest folder found will be tested first.
 
 ``DEC``
-  Ordering is done in descending mode.
+  Default. Ordering is done in descending mode.
   The highest 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.
+If :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` is set to ``NONE`` this variable
+has no effect.

+ 20 - 19
Help/variable/CMAKE_FIND_PACKAGE_SORT_ORDER.rst

@@ -3,39 +3,40 @@ CMAKE_FIND_PACKAGE_SORT_ORDER
 
 .. versionadded:: 3.7
 
+.. versionchanged:: 4.2
+
+  The default sort order has changed from ``NONE`` to ``NATURAL``.
+
+
 The default order for sorting directories which match a search path containing
 a glob expression found using :command:`find_package`.  It can assume one of
 the following values:
 
 ``NONE``
-  Default.  No attempt is done to sort directories.
+  No attempt is done to sort directories.
   The first valid package found will be selected.
 
 ``NAME``
   Sort directories lexicographically before searching.
 
 ``NATURAL``
-  Sort directories using natural order (see ``strverscmp(3)`` manual),
+  Default. Sort directories 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 available to be found by
-:command:`find_package`.  For example suppose that the following libraries
-have package configuration files on disk, in a directory of the same name,
-with all such directories residing in the same parent directory:
-
-* 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``.
+Natural sorting is employed by default to return the highest version when
+multiple versions of the same library are available to be found by
+:command:`find_package`.  For example suppose that the following libraries have
+package configuration files on disk, in a directory of the same name, with all
+such directories residing in the same parent directory:
 
-.. code-block:: cmake
+* ``libX-1.1.0``
+* ``libX-1.2.9``
+* ``libX-1.2.10``
 
-  set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
-  find_package(libX CONFIG)
+The default order of ``NATURAL`` will select the one with the highest version
+number, i.e. ``libX-1.2.10``.
 
 The sort direction can be controlled using the
-:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable
-(by default descending, e.g. lib-B will be tested before lib-A).
+:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable (by default descending,
+i.e. ``libX-1.2`` will be tested before ``libX-1.0`` and  ``lib-B`` will be
+tested before ``lib-A``).

+ 4 - 4
Source/cmFindPackageCommand.h

@@ -334,10 +334,10 @@ private:
 
   class FlushDebugBufferOnExit;
 
-  /*! the selected sortOrder (None by default)*/
-  SortOrderType SortOrder = None;
-  /*! the selected sortDirection (Asc by default)*/
-  SortDirectionType SortDirection = Asc;
+  /*! the selected sortOrder (Natural by default)*/
+  SortOrderType SortOrder = Natural;
+  /*! the selected sortDirection (Dec by default)*/
+  SortDirectionType SortDirection = Dec;
 
   struct ConfigFileInfo
   {

+ 12 - 1
Tests/FindPackageCMakeTest/CMakeLists.txt

@@ -117,7 +117,7 @@ foreach(p ${PACKAGES})
 endforeach()
 
 # Enable framework and bundle searching.  Make sure bundles are found
-# before unix-syle packages.
+# before unix-style packages.
 set(CMAKE_FIND_FRAMEWORK LAST)
 set(CMAKE_FIND_APPBUNDLE FIRST)
 
@@ -584,6 +584,17 @@ endif()
 unset(SortLib_VERSION)
 
 
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+# Expected to default to 'NATURAL' and 'DEC'
+unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
+unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
+FIND_PACKAGE(SortLib CONFIG)
+IF (NOT "${SortLib_VERSION}" STREQUAL "3.10.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Default! ${SortLib_VERSION}")
+endif()
+unset(SortLib_VERSION)
+
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
 SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)

+ 16 - 0
Tests/FindPackageCpsTest/CMakeLists.txt

@@ -90,6 +90,7 @@ foreach(CMAKE_FIND_PACKAGE_SORT_DIRECTION IN ITEMS "" ASC Bogus)
   unset(SortLib_VERSION)
 endforeach()
 
+
 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)
@@ -100,6 +101,7 @@ endif()
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortLib_VERSION)
 
+
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 FIND_PACKAGE(SortLib 4.0 CONFIG)
 IF (NOT "${SortLib_VERSION}" STREQUAL "4.0.0")
@@ -107,6 +109,18 @@ IF (NOT "${SortLib_VERSION}" STREQUAL "4.0.0")
 endif()
 unset(SortLib_VERSION)
 
+
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+# Expected to default to 'NATURAL' and 'DEC'
+unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
+unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
+FIND_PACKAGE(SortLib CONFIG)
+IF (NOT "${SortLib_VERSION}" STREQUAL "3.10.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Default! ${SortLib_VERSION}")
+endif()
+unset(SortLib_VERSION)
+
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
 set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
@@ -117,6 +131,7 @@ endif()
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortFramework_VERSION)
 
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
 set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
@@ -127,6 +142,7 @@ endif()
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortFramework_VERSION)
 
+
 unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
 unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
 

+ 1 - 0
Tests/RunCMake/find_package/ConfigureLog.cmake

@@ -6,6 +6,7 @@ list(INSERT CMAKE_PREFIX_PATH 0
 set(CMAKE_FIND_DEBUG_MODE 1)
 # Stable sorting for predictable behaviors.
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
+set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASCENDING)
 
 # Unset search variables for more predictable output.
 unset(CMAKE_FRAMEWORK_PATH)