Browse Source

find_package: Use PackageName_ROOT variables as search prefixes

This feature was originally added by commit v3.9.0-rc1~71^2~2 (find_*:
Add a new PackageRoot search path group, 2017-05-03) and documented by
commit v3.9.0-rc1~71^2 (find_*: Add docs for PackageRoot search path
group, 2017-05-03).  However, we had to disable the feature and remove
the documentation in commit v3.9.1~2^2 (find_*: Disable the PACKAGE_ROOT
search path group for CMake 3.9, 2017-08-08) due to breaking projects
that used `PackageName_ROOT` variables themselves.

Add policy `CMP0074` to restore the `PackageName_ROOT` variable behavior
in a compatible way.  Also revise the stack of root paths to store the
paths themselves rather than the package names.  This way the policy can
be considered at the `find_package` call site instead of individual
`find_` calls inside a find module.

Co-Author: Chuck Atkins <[email protected]>
Issue: #17144
Brad King 7 years ago
parent
commit
eb35d8884b

+ 23 - 6
Help/command/FIND_XXX.txt

@@ -16,6 +16,7 @@ The general signature is:
              [PATH_SUFFIXES suffix1 [suffix2 ...]]
              [PATH_SUFFIXES suffix1 [suffix2 ...]]
              [DOC "cache documentation string"]
              [DOC "cache documentation string"]
              [NO_DEFAULT_PATH]
              [NO_DEFAULT_PATH]
+             [NO_PACKAGE_ROOT_PATH]
              [NO_CMAKE_PATH]
              [NO_CMAKE_PATH]
              [NO_CMAKE_ENVIRONMENT_PATH]
              [NO_CMAKE_ENVIRONMENT_PATH]
              [NO_SYSTEM_ENVIRONMENT_PATH]
              [NO_SYSTEM_ENVIRONMENT_PATH]
@@ -60,6 +61,10 @@ If ``NO_DEFAULT_PATH`` is specified, then no additional paths are
 added to the search.
 added to the search.
 If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
 If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
 
 
+.. |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX_SUBDIR| replace::
+   |prefix_XXX_SUBDIR| for each ``<prefix>`` in ``PackageName_ROOT`` if called
+   from within a find module
+
 .. |CMAKE_PREFIX_PATH_XXX_SUBDIR| replace::
 .. |CMAKE_PREFIX_PATH_XXX_SUBDIR| replace::
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in :variable:`CMAKE_PREFIX_PATH`
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in :variable:`CMAKE_PREFIX_PATH`
 
 
@@ -71,7 +76,19 @@ If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in
    :variable:`CMAKE_SYSTEM_PREFIX_PATH`
    :variable:`CMAKE_SYSTEM_PREFIX_PATH`
 
 
-1. Search paths specified in cmake-specific cache variables.
+1. If called from within a find module, search prefix paths unique to the
+   current package being found.  Specifically look in the ``PackageName_ROOT``
+   CMake and environment variables.  The package root variables are maintained
+   as a stack so if called from nested find modules, root paths from the
+   parent's find module will be searchd after paths from the current module,
+   i.e. ``CurrentPackage_ROOT``, ``ENV{CurrentPackage_ROOT}``,
+   ``ParentPackage_ROOT``, ``ENV{ParentPacakge_ROOT}``, etc.
+   This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed.
+   See policy :policy:`CMP0074`.
+
+   * |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX|
+
+2. Search paths specified in cmake-specific cache variables.
    These are intended to be used on the command line with a ``-DVAR=value``.
    These are intended to be used on the command line with a ``-DVAR=value``.
    The values are interpreted as :ref:`;-lists <CMake Language Lists>`.
    The values are interpreted as :ref:`;-lists <CMake Language Lists>`.
    This can be skipped if ``NO_CMAKE_PATH`` is passed.
    This can be skipped if ``NO_CMAKE_PATH`` is passed.
@@ -80,7 +97,7 @@ If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
    * |CMAKE_XXX_PATH|
    * |CMAKE_XXX_PATH|
    * |CMAKE_XXX_MAC_PATH|
    * |CMAKE_XXX_MAC_PATH|
 
 
-2. Search paths specified in cmake-specific environment variables.
+3. Search paths specified in cmake-specific environment variables.
    These are intended to be set in the user's shell configuration,
    These are intended to be set in the user's shell configuration,
    and therefore use the host's native path separator
    and therefore use the host's native path separator
    (``;`` on Windows and ``:`` on UNIX).
    (``;`` on Windows and ``:`` on UNIX).
@@ -90,17 +107,17 @@ If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
    * |CMAKE_XXX_PATH|
    * |CMAKE_XXX_PATH|
    * |CMAKE_XXX_MAC_PATH|
    * |CMAKE_XXX_MAC_PATH|
 
 
-3. Search the paths specified by the ``HINTS`` option.
+4. Search the paths specified by the ``HINTS`` option.
    These should be paths computed by system introspection, such as a
    These should be paths computed by system introspection, such as a
    hint provided by the location of another item already found.
    hint provided by the location of another item already found.
    Hard-coded guesses should be specified with the ``PATHS`` option.
    Hard-coded guesses should be specified with the ``PATHS`` option.
 
 
-4. Search the standard system environment variables.
+5. Search the standard system environment variables.
    This can be skipped if ``NO_SYSTEM_ENVIRONMENT_PATH`` is an argument.
    This can be skipped if ``NO_SYSTEM_ENVIRONMENT_PATH`` is an argument.
 
 
    * |SYSTEM_ENVIRONMENT_PATH_XXX|
    * |SYSTEM_ENVIRONMENT_PATH_XXX|
 
 
-5. Search cmake variables defined in the Platform files
+6. Search cmake variables defined in the Platform files
    for the current system.  This can be skipped if ``NO_CMAKE_SYSTEM_PATH``
    for the current system.  This can be skipped if ``NO_CMAKE_SYSTEM_PATH``
    is passed.
    is passed.
 
 
@@ -108,7 +125,7 @@ If ``NO_DEFAULT_PATH`` is not specified, the search process is as follows:
    * |CMAKE_SYSTEM_XXX_PATH|
    * |CMAKE_SYSTEM_XXX_PATH|
    * |CMAKE_SYSTEM_XXX_MAC_PATH|
    * |CMAKE_SYSTEM_XXX_MAC_PATH|
 
 
-6. Search the paths specified by the PATHS option
+7. Search the paths specified by the PATHS option
    or in the short-hand version of the command.
    or in the short-hand version of the command.
    These are typically hard-coded guesses.
    These are typically hard-coded guesses.
 
 

+ 3 - 0
Help/command/find_file.rst

@@ -8,6 +8,9 @@ find_file
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/include``
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/include``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/include``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/include``
 
 
+.. |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX| replace::
+   ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
+   is set, and |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX_SUBDIR|
 .. |CMAKE_PREFIX_PATH_XXX| replace::
 .. |CMAKE_PREFIX_PATH_XXX| replace::
    ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
    ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
    is set, and |CMAKE_PREFIX_PATH_XXX_SUBDIR|
    is set, and |CMAKE_PREFIX_PATH_XXX_SUBDIR|

+ 3 - 0
Help/command/find_library.rst

@@ -8,6 +8,9 @@ find_library
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/lib``
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/lib``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/lib``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/lib``
 
 
+.. |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX| replace::
+   ``<prefix>/lib/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
+   and |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX_SUBDIR|
 .. |CMAKE_PREFIX_PATH_XXX| replace::
 .. |CMAKE_PREFIX_PATH_XXX| replace::
    ``<prefix>/lib/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
    ``<prefix>/lib/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
    and |CMAKE_PREFIX_PATH_XXX_SUBDIR|
    and |CMAKE_PREFIX_PATH_XXX_SUBDIR|

+ 16 - 8
Help/command/find_package.rst

@@ -64,6 +64,7 @@ The complete Config mode command signature is::
                [PATHS path1 [path2 ... ]]
                [PATHS path1 [path2 ... ]]
                [PATH_SUFFIXES suffix1 [suffix2 ...]]
                [PATH_SUFFIXES suffix1 [suffix2 ...]]
                [NO_DEFAULT_PATH]
                [NO_DEFAULT_PATH]
+               [NO_PACKAGE_ROOT_PATH]
                [NO_CMAKE_PATH]
                [NO_CMAKE_PATH]
                [NO_CMAKE_ENVIRONMENT_PATH]
                [NO_CMAKE_ENVIRONMENT_PATH]
                [NO_SYSTEM_ENVIRONMENT_PATH]
                [NO_SYSTEM_ENVIRONMENT_PATH]
@@ -249,7 +250,14 @@ The set of installation prefixes is constructed using the following
 steps.  If ``NO_DEFAULT_PATH`` is specified all ``NO_*`` options are
 steps.  If ``NO_DEFAULT_PATH`` is specified all ``NO_*`` options are
 enabled.
 enabled.
 
 
-1. Search paths specified in cmake-specific cache variables.  These
+1. Search paths specified in the ``PackageName_ROOT`` CMake and environment
+   variables.  The package root variables are maintained as a stack so if
+   called from within a find module, root paths from the parent's find
+   module will also be searched after paths for the current package.
+   This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed.
+   See policy :policy:`CMP0074`.
+
+2. Search paths specified in cmake-specific cache variables.  These
    are intended to be used on the command line with a ``-DVAR=value``.
    are intended to be used on the command line with a ``-DVAR=value``.
    The values are interpreted as :ref:`;-lists <CMake Language Lists>`.
    The values are interpreted as :ref:`;-lists <CMake Language Lists>`.
    This can be skipped if ``NO_CMAKE_PATH`` is passed::
    This can be skipped if ``NO_CMAKE_PATH`` is passed::
@@ -258,7 +266,7 @@ enabled.
      CMAKE_FRAMEWORK_PATH
      CMAKE_FRAMEWORK_PATH
      CMAKE_APPBUNDLE_PATH
      CMAKE_APPBUNDLE_PATH
 
 
-2. Search paths specified in cmake-specific environment variables.
+3. Search paths specified in cmake-specific environment variables.
    These are intended to be set in the user's shell configuration,
    These are intended to be set in the user's shell configuration,
    and therefore use the host's native path separator
    and therefore use the host's native path separator
    (``;`` on Windows and ``:`` on UNIX).
    (``;`` on Windows and ``:`` on UNIX).
@@ -269,26 +277,26 @@ enabled.
      CMAKE_FRAMEWORK_PATH
      CMAKE_FRAMEWORK_PATH
      CMAKE_APPBUNDLE_PATH
      CMAKE_APPBUNDLE_PATH
 
 
-3. Search paths specified by the ``HINTS`` option.  These should be paths
+4. Search paths specified by the ``HINTS`` option.  These should be paths
    computed by system introspection, such as a hint provided by the
    computed by system introspection, such as a hint provided by the
    location of another item already found.  Hard-coded guesses should
    location of another item already found.  Hard-coded guesses should
    be specified with the ``PATHS`` option.
    be specified with the ``PATHS`` option.
 
 
-4. Search the standard system environment variables.  This can be
+5. Search the standard system environment variables.  This can be
    skipped if ``NO_SYSTEM_ENVIRONMENT_PATH`` is passed.  Path entries
    skipped if ``NO_SYSTEM_ENVIRONMENT_PATH`` is passed.  Path entries
    ending in ``/bin`` or ``/sbin`` are automatically converted to their
    ending in ``/bin`` or ``/sbin`` are automatically converted to their
    parent directories::
    parent directories::
 
 
      PATH
      PATH
 
 
-5. Search paths stored in the CMake :ref:`User Package Registry`.
+6. Search paths stored in the CMake :ref:`User Package Registry`.
    This can be skipped if ``NO_CMAKE_PACKAGE_REGISTRY`` is passed or by
    This can be skipped if ``NO_CMAKE_PACKAGE_REGISTRY`` is passed or by
    setting the :variable:`CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY`
    setting the :variable:`CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY`
    to ``TRUE``.
    to ``TRUE``.
    See the :manual:`cmake-packages(7)` manual for details on the user
    See the :manual:`cmake-packages(7)` manual for details on the user
    package registry.
    package registry.
 
 
-6. Search cmake variables defined in the Platform files for the
+7. Search cmake variables defined in the Platform files for the
    current system.  This can be skipped if ``NO_CMAKE_SYSTEM_PATH`` is
    current system.  This can be skipped if ``NO_CMAKE_SYSTEM_PATH`` is
    passed::
    passed::
 
 
@@ -296,14 +304,14 @@ enabled.
      CMAKE_SYSTEM_FRAMEWORK_PATH
      CMAKE_SYSTEM_FRAMEWORK_PATH
      CMAKE_SYSTEM_APPBUNDLE_PATH
      CMAKE_SYSTEM_APPBUNDLE_PATH
 
 
-7. Search paths stored in the CMake :ref:`System Package Registry`.
+8. Search paths stored in the CMake :ref:`System Package Registry`.
    This can be skipped if ``NO_CMAKE_SYSTEM_PACKAGE_REGISTRY`` is passed
    This can be skipped if ``NO_CMAKE_SYSTEM_PACKAGE_REGISTRY`` is passed
    or by setting the
    or by setting the
    :variable:`CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY` to ``TRUE``.
    :variable:`CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY` to ``TRUE``.
    See the :manual:`cmake-packages(7)` manual for details on the system
    See the :manual:`cmake-packages(7)` manual for details on the system
    package registry.
    package registry.
 
 
-8. Search paths specified by the ``PATHS`` option.  These are typically
+9. Search paths specified by the ``PATHS`` option.  These are typically
    hard-coded guesses.
    hard-coded guesses.
 
 
 .. |FIND_XXX| replace:: find_package
 .. |FIND_XXX| replace:: find_package

+ 3 - 0
Help/command/find_path.rst

@@ -8,6 +8,9 @@ find_path
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/include``
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/include``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/include``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/include``
 
 
+.. |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX| replace::
+   ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
+   is set, and |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX_SUBDIR|
 .. |CMAKE_PREFIX_PATH_XXX| replace::
 .. |CMAKE_PREFIX_PATH_XXX| replace::
    ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
    ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE`
    is set, and |CMAKE_PREFIX_PATH_XXX_SUBDIR|
    is set, and |CMAKE_PREFIX_PATH_XXX_SUBDIR|

+ 2 - 0
Help/command/find_program.rst

@@ -8,6 +8,8 @@ find_program
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/[s]bin``
 .. |prefix_XXX_SUBDIR| replace:: ``<prefix>/[s]bin``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/[s]bin``
 .. |entry_XXX_SUBDIR| replace:: ``<entry>/[s]bin``
 
 
+.. |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX| replace::
+   |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX_SUBDIR|
 .. |CMAKE_PREFIX_PATH_XXX| replace::
 .. |CMAKE_PREFIX_PATH_XXX| replace::
    |CMAKE_PREFIX_PATH_XXX_SUBDIR|
    |CMAKE_PREFIX_PATH_XXX_SUBDIR|
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_PROGRAM_PATH`
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_PROGRAM_PATH`

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.12
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
 
 
+   CMP0074: find_package uses PackageName_ROOT variables. </policy/CMP0074>
    CMP0073: Do not produce legacy _LIB_DEPENDS cache entries. </policy/CMP0073>
    CMP0073: Do not produce legacy _LIB_DEPENDS cache entries. </policy/CMP0073>
 
 
 Policies Introduced by CMake 3.11
 Policies Introduced by CMake 3.11

+ 22 - 0
Help/policy/CMP0074.rst

@@ -0,0 +1,22 @@
+CMP0074
+-------
+
+:command:`find_package` uses ``PackageName_ROOT`` variables.
+
+In CMake 3.12 and above the ``find_package(PackageName)`` command now searches
+a prefix specified by a ``PackageName_ROOT`` CMake or environment variable.
+Package roots are maintained as a stack so nested calls to all ``find_*``
+commands inside find modules also search the roots as prefixes.  This policy
+provides compatibility with projects that have not been updated to avoid using
+``PackageName_ROOT`` variables for other purposes.
+
+The ``OLD`` behavior for this policy is to ignore ``PackageName_ROOT``
+variables.  The ``NEW`` behavior for this policy is to use ``PackageName_ROOT``
+variables.
+
+This policy was introduced in CMake version 3.12.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt

+ 8 - 0
Help/release/dev/find-package_root-restore.rst

@@ -0,0 +1,8 @@
+find-package_root-restore
+-------------------------
+
+* The :command:`find_package` command now searches a prefix specified by
+  a ``PackageName_ROOT`` CMake or environment variable.  Package roots are
+  maintained as a stack so nested calls to all ``find_*`` commands inside
+  find modules also search the roots as prefixes.
+  See policy :policy:`CMP0074`.

+ 6 - 12
Source/cmFindBase.cxx

@@ -67,8 +67,6 @@ bool cmFindBase::ParseArguments(std::vector<std::string> const& argsIn)
   }
   }
   this->AlreadyInCache = false;
   this->AlreadyInCache = false;
 
 
-  this->SelectDefaultNoPackageRootPath();
-
   // Find the current root path mode.
   // Find the current root path mode.
   this->SelectDefaultRootPathMode();
   this->SelectDefaultRootPathMode();
 
 
@@ -206,16 +204,12 @@ void cmFindBase::FillPackageRootPath()
 {
 {
   cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
   cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
 
 
-  // Add package specific search prefixes
-  // NOTE: This should be using const_reverse_iterator but HP aCC and
-  //       Oracle sunCC both currently have standard library issues
-  //       with the reverse iterator APIs.
-  for (std::deque<std::string>::reverse_iterator pkg =
-         this->Makefile->FindPackageModuleStack.rbegin();
-       pkg != this->Makefile->FindPackageModuleStack.rend(); ++pkg) {
-    std::string varName = *pkg + "_ROOT";
-    paths.AddCMakePrefixPath(varName);
-    paths.AddEnvPrefixPath(varName);
+  // Add the PACKAGE_ROOT_PATH from each enclosing find_package call.
+  for (std::deque<std::vector<std::string>>::const_reverse_iterator pkgPaths =
+         this->Makefile->FindPackageRootPathStack.rbegin();
+       pkgPaths != this->Makefile->FindPackageRootPathStack.rend();
+       ++pkgPaths) {
+    paths.AddPrefixPaths(*pkgPaths);
   }
   }
 
 
   paths.AddSuffixes(this->SearchPathSuffixes);
   paths.AddSuffixes(this->SearchPathSuffixes);

+ 0 - 7
Source/cmFindCommon.cxx

@@ -88,13 +88,6 @@ void cmFindCommon::InitializeSearchPathGroups()
     std::make_pair(PathLabel::Guess, cmSearchPath(this)));
     std::make_pair(PathLabel::Guess, cmSearchPath(this)));
 }
 }
 
 
-void cmFindCommon::SelectDefaultNoPackageRootPath()
-{
-  if (!this->Makefile->IsOn("__UNDOCUMENTED_CMAKE_FIND_PACKAGE_ROOT")) {
-    this->NoPackageRootPath = true;
-  }
-}
-
 void cmFindCommon::SelectDefaultRootPathMode()
 void cmFindCommon::SelectDefaultRootPathMode()
 {
 {
   // Check the policy variable for this find command type.
   // Check the policy variable for this find command type.

+ 0 - 3
Source/cmFindCommon.h

@@ -84,9 +84,6 @@ protected:
   /** Compute final search path list (reroot + trailing slash).  */
   /** Compute final search path list (reroot + trailing slash).  */
   void ComputeFinalPaths();
   void ComputeFinalPaths();
 
 
-  /** Decide whether to enable the PACKAGE_ROOT search entries.  */
-  void SelectDefaultNoPackageRootPath();
-
   /** Compute the current default root path mode.  */
   /** Compute the current default root path mode.  */
   void SelectDefaultRootPathMode();
   void SelectDefaultRootPathMode();
 
 

+ 42 - 16
Source/cmFindPackageCommand.cxx

@@ -21,6 +21,7 @@
 
 
 #include "cmAlgorithms.h"
 #include "cmAlgorithms.h"
 #include "cmMakefile.h"
 #include "cmMakefile.h"
+#include "cmPolicies.h"
 #include "cmSearchPath.h"
 #include "cmSearchPath.h"
 #include "cmState.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
 #include "cmStateTypes.h"
@@ -209,8 +210,6 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args,
     this->SortDirection = strcmp(sd, "ASC") == 0 ? Asc : Dec;
     this->SortDirection = strcmp(sd, "ASC") == 0 ? Asc : Dec;
   }
   }
 
 
-  this->SelectDefaultNoPackageRootPath();
-
   // Find the current root path mode.
   // Find the current root path mode.
   this->SelectDefaultRootPathMode();
   this->SelectDefaultRootPathMode();
 
 
@@ -454,6 +453,38 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args,
     return true;
     return true;
   }
   }
 
 
+  {
+    // Allocate a PACKAGE_ROOT_PATH for the current find_package call.
+    this->Makefile->FindPackageRootPathStack.emplace_back();
+    std::vector<std::string>& rootPaths =
+      *this->Makefile->FindPackageRootPathStack.rbegin();
+
+    // Add root paths from <PackageName>_ROOT CMake and environment variables,
+    // subject to CMP0074.
+    switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0074)) {
+      case cmPolicies::WARN:
+        this->Makefile->MaybeWarnCMP0074(this->Name);
+        CM_FALLTHROUGH;
+      case cmPolicies::OLD:
+        // OLD behavior is to ignore the <pkg>_ROOT variables.
+        break;
+      case cmPolicies::REQUIRED_IF_USED:
+      case cmPolicies::REQUIRED_ALWAYS:
+        this->Makefile->IssueMessage(
+          cmake::FATAL_ERROR,
+          cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0074));
+        break;
+      case cmPolicies::NEW: {
+        // NEW behavior is to honor the <pkg>_ROOT variables.
+        std::string const rootVar = this->Name + "_ROOT";
+        if (const char* pkgRoot = this->Makefile->GetDefinition(rootVar)) {
+          cmSystemTools::ExpandListArgument(pkgRoot, rootPaths, false);
+        }
+        cmSystemTools::GetPath(rootPaths, rootVar.c_str());
+      } break;
+    }
+  }
+
   this->SetModuleVariables(components);
   this->SetModuleVariables(components);
 
 
   // See if there is a Find<package>.cmake module.
   // See if there is a Find<package>.cmake module.
@@ -589,9 +620,6 @@ void cmFindPackageCommand::SetModuleVariables(const std::string& components)
     exact += "_FIND_VERSION_EXACT";
     exact += "_FIND_VERSION_EXACT";
     this->AddFindDefinition(exact, this->VersionExact ? "1" : "0");
     this->AddFindDefinition(exact, this->VersionExact ? "1" : "0");
   }
   }
-
-  // Push on to the package stack
-  this->Makefile->FindPackageModuleStack.push_back(this->Name);
 }
 }
 
 
 void cmFindPackageCommand::AddFindDefinition(const std::string& var,
 void cmFindPackageCommand::AddFindDefinition(const std::string& var,
@@ -1076,7 +1104,7 @@ void cmFindPackageCommand::AppendSuccessInformation()
   this->RestoreFindDefinitions();
   this->RestoreFindDefinitions();
 
 
   // Pop the package stack
   // Pop the package stack
-  this->Makefile->FindPackageModuleStack.pop_back();
+  this->Makefile->FindPackageRootPathStack.pop_back();
 }
 }
 
 
 void cmFindPackageCommand::ComputePrefixes()
 void cmFindPackageCommand::ComputePrefixes()
@@ -1116,16 +1144,14 @@ void cmFindPackageCommand::FillPrefixesPackageRoot()
 {
 {
   cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
   cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
 
 
-  // Add package specific search prefixes
-  // NOTE: This should be using const_reverse_iterator but HP aCC and
-  //       Oracle sunCC both currently have standard library issues
-  //       with the reverse iterator APIs.
-  for (std::deque<std::string>::reverse_iterator pkg =
-         this->Makefile->FindPackageModuleStack.rbegin();
-       pkg != this->Makefile->FindPackageModuleStack.rend(); ++pkg) {
-    std::string varName = *pkg + "_ROOT";
-    paths.AddCMakePath(varName);
-    paths.AddEnvPath(varName);
+  // Add the PACKAGE_ROOT_PATH from each enclosing find_package call.
+  for (std::deque<std::vector<std::string>>::const_reverse_iterator pkgPaths =
+         this->Makefile->FindPackageRootPathStack.rbegin();
+       pkgPaths != this->Makefile->FindPackageRootPathStack.rend();
+       ++pkgPaths) {
+    for (std::string const& path : *pkgPaths) {
+      paths.AddPath(path);
+    }
   }
   }
 }
 }
 
 

+ 20 - 0
Source/cmMakefile.cxx

@@ -158,6 +158,26 @@ bool cmMakefile::CheckCMP0037(std::string const& targetName,
   return true;
   return true;
 }
 }
 
 
+void cmMakefile::MaybeWarnCMP0074(std::string const& pkg)
+{
+  // Warn if a <pkg>_ROOT variable we may use is set.
+  std::string const varName = pkg + "_ROOT";
+  bool const haveVar = this->GetDefinition(varName) != nullptr;
+  bool const haveEnv = cmSystemTools::HasEnv(varName);
+  if ((haveVar || haveEnv) && this->WarnedCMP0074.insert(varName).second) {
+    std::ostringstream w;
+    w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0074) << "\n";
+    if (haveVar) {
+      w << "CMake variable " << varName << " is set.\n";
+    }
+    if (haveEnv) {
+      w << "Environment variable " << varName << " is set.\n";
+    }
+    w << "For compatibility, CMake is ignoring the variable.";
+    this->IssueMessage(cmake::AUTHOR_WARNING, w.str());
+  }
+}
+
 cmStringRange cmMakefile::GetIncludeDirectoriesEntries() const
 cmStringRange cmMakefile::GetIncludeDirectoriesEntries() const
 {
 {
   return this->StateSnapshot.GetDirectory().GetIncludeDirectoriesEntries();
   return this->StateSnapshot.GetDirectory().GetIncludeDirectoriesEntries();

+ 6 - 3
Source/cmMakefile.h

@@ -832,9 +832,11 @@ public:
   void RemoveExportBuildFileGeneratorCMP0024(cmExportBuildFileGenerator* gen);
   void RemoveExportBuildFileGeneratorCMP0024(cmExportBuildFileGenerator* gen);
   void AddExportBuildFileGenerator(cmExportBuildFileGenerator* gen);
   void AddExportBuildFileGenerator(cmExportBuildFileGenerator* gen);
 
 
-  // Maintain a stack of package names to determine the depth of find modules
-  // we are currently being called with
-  std::deque<std::string> FindPackageModuleStack;
+  // Maintain a stack of package roots to allow nested PACKAGE_ROOT_PATH
+  // searches
+  std::deque<std::vector<std::string>> FindPackageRootPathStack;
+
+  void MaybeWarnCMP0074(std::string const& pkg);
 
 
 protected:
 protected:
   // add link libraries and directories to the target
   // add link libraries and directories to the target
@@ -1001,6 +1003,7 @@ private:
   bool WarnUnused;
   bool WarnUnused;
   bool CheckSystemVars;
   bool CheckSystemVars;
   bool CheckCMP0000;
   bool CheckCMP0000;
+  std::set<std::string> WarnedCMP0074;
   bool IsSourceFileTryCompile;
   bool IsSourceFileTryCompile;
   mutable bool SuppressWatches;
   mutable bool SuppressWatches;
 };
 };

+ 3 - 1
Source/cmPolicies.h

@@ -217,7 +217,9 @@ class cmMakefile;
          cmPolicies::WARN)                                                    \
          cmPolicies::WARN)                                                    \
   SELECT(POLICY, CMP0073,                                                     \
   SELECT(POLICY, CMP0073,                                                     \
          "Do not produce legacy _LIB_DEPENDS cache entries.", 3, 12, 0,       \
          "Do not produce legacy _LIB_DEPENDS cache entries.", 3, 12, 0,       \
-         cmPolicies::WARN)
+         cmPolicies::WARN)                                                    \
+  SELECT(POLICY, CMP0074, "find_package uses PackageName_ROOT variables.", 3, \
+         12, 0, cmPolicies::WARN)
 
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 2 - 2
Source/cmSearchPath.h

@@ -39,10 +39,10 @@ public:
   void AddCMakePrefixPath(const std::string& variable);
   void AddCMakePrefixPath(const std::string& variable);
   void AddEnvPrefixPath(const std::string& variable, bool stripBin = false);
   void AddEnvPrefixPath(const std::string& variable, bool stripBin = false);
   void AddSuffixes(const std::vector<std::string>& suffixes);
   void AddSuffixes(const std::vector<std::string>& suffixes);
-
-protected:
   void AddPrefixPaths(const std::vector<std::string>& paths,
   void AddPrefixPaths(const std::vector<std::string>& paths,
                       const char* base = nullptr);
                       const char* base = nullptr);
+
+protected:
   void AddPathInternal(const std::string& path, const char* base = nullptr);
   void AddPathInternal(const std::string& path, const char* base = nullptr);
 
 
   cmFindCommon* FC;
   cmFindCommon* FC;

+ 12 - 0
Tests/RunCMake/find_package/CMP0074-OLD-stderr.txt

@@ -0,0 +1,12 @@
+----------
+Foo_ROOT      :<base>/foo/cmake_root
+ENV{Foo_ROOT} :<base>/foo/env_root
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO :FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT :FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO :FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT :FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO :FOO_TEST_PROG_FOO-NOTFOUND
+
+----------

+ 2 - 0
Tests/RunCMake/find_package/CMP0074-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0074 OLD)
+include(CMP0074-common.cmake)

+ 28 - 0
Tests/RunCMake/find_package/CMP0074-WARN-stderr.txt

@@ -0,0 +1,28 @@
+----------
+Foo_ROOT      :<base>/foo/cmake_root
+ENV{Foo_ROOT} :<base>/foo/env_root
++
+CMake Warning \(dev\) at CMP0074-common.cmake:[0-9]+ \(find_package\):
+  Policy CMP0074 is not set: find_package uses PackageName_ROOT variables.
+  Run "cmake --help-policy CMP0074" for policy details.  Use the cmake_policy
+  command to set the policy and suppress this warning.
+
+  CMake variable Foo_ROOT is set.
+
+  Environment variable Foo_ROOT is set.
+
+  For compatibility, CMake is ignoring the variable.
+Call Stack \(most recent call first\):
+  CMP0074-common.cmake:[0-9]+ \(RunPackageRootTest\)
+  CMP0074-WARN.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
++
+find_package\(Foo\)
+FOO_TEST_FILE_FOO :FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT :FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO :FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT :FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO :FOO_TEST_PROG_FOO-NOTFOUND
+
+----------

+ 2 - 0
Tests/RunCMake/find_package/CMP0074-WARN.cmake

@@ -0,0 +1,2 @@
+# (do not set CMP0074)
+include(CMP0074-common.cmake)

+ 46 - 0
Tests/RunCMake/find_package/CMP0074-common.cmake

@@ -0,0 +1,46 @@
+# (includer selects CMP0074)
+cmake_policy(SET CMP0057 NEW)
+list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
+set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
+
+function(PrintPath label path)
+  string(REPLACE "${PackageRoot_BASE}" "<base>" out "${path}")
+  message("${label}${out}")
+endfunction()
+
+macro(CleanUpPackageRootTest)
+  unset(Foo_ROOT)
+  unset(ENV{Foo_ROOT})
+  unset(FOO_TEST_FILE_FOO)
+  unset(FOO_TEST_FILE_ZOT)
+  unset(FOO_TEST_PATH_FOO)
+  unset(FOO_TEST_PATH_ZOT)
+  unset(FOO_TEST_PROG_FOO)
+  unset(FOO_TEST_FILE_FOO CACHE)
+  unset(FOO_TEST_FILE_ZOT CACHE)
+  unset(FOO_TEST_PATH_FOO CACHE)
+  unset(FOO_TEST_PATH_ZOT CACHE)
+  unset(FOO_TEST_PROG_FOO CACHE)
+endmacro()
+
+macro(RunPackageRootTest)
+  message("----------")
+  PrintPath("Foo_ROOT      :" "${Foo_ROOT}")
+  PrintPath("ENV{Foo_ROOT} :" "$ENV{Foo_ROOT}")
+  message("")
+
+  find_package(Foo)
+  message("find_package(Foo)")
+  PrintPath("FOO_TEST_FILE_FOO :" "${FOO_TEST_FILE_FOO}")
+  PrintPath("FOO_TEST_FILE_ZOT :" "${FOO_TEST_FILE_ZOT}")
+  PrintPath("FOO_TEST_PATH_FOO :" "${FOO_TEST_PATH_FOO}")
+  PrintPath("FOO_TEST_PATH_ZOT :" "${FOO_TEST_PATH_ZOT}")
+  PrintPath("FOO_TEST_PROG_FOO :" "${FOO_TEST_PROG_FOO}")
+  CleanUpPackageRootTest()
+  message("")
+endmacro()
+
+set(Foo_ROOT      ${PackageRoot_BASE}/foo/cmake_root)
+set(ENV{Foo_ROOT} ${PackageRoot_BASE}/foo/env_root)
+RunPackageRootTest()
+message("----------")

+ 1 - 1
Tests/RunCMake/find_package/PackageRoot.cmake

@@ -1,5 +1,5 @@
-set(__UNDOCUMENTED_CMAKE_FIND_PACKAGE_ROOT 1)
 cmake_policy(SET CMP0057 NEW)
 cmake_policy(SET CMP0057 NEW)
+cmake_policy(SET CMP0074 NEW)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 
 

+ 1 - 1
Tests/RunCMake/find_package/PackageRootNestedConfig.cmake

@@ -1,5 +1,5 @@
-set(__UNDOCUMENTED_CMAKE_FIND_PACKAGE_ROOT 1)
 cmake_policy(SET CMP0057 NEW)
 cmake_policy(SET CMP0057 NEW)
+cmake_policy(SET CMP0074 NEW)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 
 

+ 1 - 1
Tests/RunCMake/find_package/PackageRootNestedModule.cmake

@@ -1,5 +1,5 @@
-set(__UNDOCUMENTED_CMAKE_FIND_PACKAGE_ROOT 1)
 cmake_policy(SET CMP0057 NEW)
 cmake_policy(SET CMP0057 NEW)
+cmake_policy(SET CMP0074 NEW)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
 
 

+ 2 - 0
Tests/RunCMake/find_package/RunCMakeTest.cmake

@@ -1,5 +1,7 @@
 include(RunCMake)
 include(RunCMake)
 
 
+run_cmake(CMP0074-WARN)
+run_cmake(CMP0074-OLD)
 run_cmake(ComponentRequiredAndOptional)
 run_cmake(ComponentRequiredAndOptional)
 run_cmake(MissingNormal)
 run_cmake(MissingNormal)
 run_cmake(MissingNormalRequired)
 run_cmake(MissingNormalRequired)