Browse Source

fileapi: Add CONFIGURE_DEPENDS glob info to cmakeFiles object

Fixes: #25668
Co-authored-by: Brad King <[email protected]>
Arctic Lampyrid 1 year ago
parent
commit
6116bcb066

+ 49 - 1
Help/manual/cmake-file-api.7.rst

@@ -1489,7 +1489,7 @@ There is only one ``cmakeFiles`` object major version, version 1.
 
   {
     "kind": "cmakeFiles",
-    "version": { "major": 1, "minor": 0 },
+    "version": { "major": 1, "minor": 1 },
     "paths": {
       "build": "/path/to/top-level-build-dir",
       "source": "/path/to/top-level-source-dir"
@@ -1511,6 +1511,16 @@ There is only one ``cmakeFiles`` object major version, version 1.
         "isExternal": true,
         "path": "/path/to/cmake/Modules/CMakeGenericSystem.cmake"
       }
+    ],
+    "globsDependent": [
+      {
+        "expression": "src/*.cxx",
+        "recurse": true,
+        "files": [
+          "src/foo.cxx",
+          "src/bar.cxx"
+        ]
+      }
     ]
   }
 
@@ -1553,6 +1563,44 @@ The members specific to ``cmakeFiles`` objects are:
     Optional member that is present with boolean value ``true``
     if the path specifies a file in the CMake installation.
 
+``globsDependent``
+  Optional member that is present when the project calls :command:`file(GLOB)`
+  or :command:`file(GLOB_RECURSE)` with the ``CONFIGURE_DEPENDS`` option.
+  The value is a JSON array of JSON objects, each specifying a globbing
+  expression and the list of paths it matched.  If the globbing expression
+  no longer matches the same list of paths, CMake considers the build system
+  to be out of date.
+
+  This field was added in ``cmakeFiles`` version 1.1.
+
+  The members of each entry are:
+
+  ``expression``
+    A string specifying the globbing expression.
+
+  ``recurse``
+    Optional member that is present with boolean value ``true``
+    if the entry corresponds to a :command:`file(GLOB_RECURSE)` call.
+    Otherwise the entry corresponds to a :command:`file(GLOB)` call.
+
+  ``listDirectories``
+    Optional member that is present with boolean value ``true`` if
+    :command:`file(GLOB)` was called without ``LIST_DIRECTORIES false`` or
+    :command:`file(GLOB_RECURSE)` was called with ``LIST_DIRECTORIES true``.
+
+  ``followSymlinks``
+    Optional member that is present with boolean value ``true`` if
+    :command:`file(GLOB)` was called with the ``FOLLOW_SYMLINKS`` option.
+
+  ``relative``
+    Optional member that is present if :command:`file(GLOB)` was called
+    with the ``RELATIVE <path>`` option.  The value is a string containing
+    the ``<path>`` given.
+
+  ``paths``
+    A JSON array of strings specifying the paths matched by the call
+    to :command:`file(GLOB)` or :command:`file(GLOB_RECURSE)`.
+
 Object Kind "toolchains"
 ------------------------
 

+ 9 - 0
Help/release/dev/fileapi-provide-glob-dependent.rst

@@ -0,0 +1,9 @@
+fileapi-provide-glob-dependent
+------------------------------
+
+* The :manual:`cmake-file-api(7)` "cmakeFiles" version 1 object's ``version``
+  field has been updated to 1.1.
+
+* The :manual:`cmake-file-api(7)` "cmakeFiles" version 1 object gained a
+  ``globsDependent`` field to report :command:`file(GLOB)` calls using
+  ``CONFIGURE_DEPENDS``.

+ 1 - 1
Source/cmFileAPI.cxx

@@ -830,7 +830,7 @@ Json::Value cmFileAPI::BuildCache(Object const& object)
 
 // The "cmakeFiles" object kind.
 
-static unsigned int const CMakeFilesV1Minor = 0;
+static unsigned int const CMakeFilesV1Minor = 1;
 
 void cmFileAPI::BuildClientRequestCMakeFiles(
   ClientRequest& r, std::vector<RequestVersion> const& versions)

+ 40 - 0
Source/cmFileAPICMakeFiles.cxx

@@ -31,6 +31,8 @@ class CMakeFiles
   Json::Value DumpPaths();
   Json::Value DumpInputs();
   Json::Value DumpInput(std::string const& file);
+  Json::Value DumpGlobsDependent();
+  Json::Value DumpGlobDependent(cmGlobCacheEntry const& entry);
 
 public:
   CMakeFiles(cmFileAPI& fileAPI, unsigned long version);
@@ -53,6 +55,10 @@ Json::Value CMakeFiles::Dump()
   Json::Value cmakeFiles = Json::objectValue;
   cmakeFiles["paths"] = this->DumpPaths();
   cmakeFiles["inputs"] = this->DumpInputs();
+  Json::Value globsDependent = this->DumpGlobsDependent();
+  if (!globsDependent.empty()) {
+    cmakeFiles["globsDependent"] = std::move(globsDependent);
+  }
   return cmakeFiles;
 }
 
@@ -108,6 +114,40 @@ Json::Value CMakeFiles::DumpInput(std::string const& file)
 
   return input;
 }
+
+Json::Value CMakeFiles::DumpGlobsDependent()
+{
+  Json::Value globsDependent = Json::arrayValue;
+  for (cmGlobCacheEntry const& entry :
+       this->FileAPI.GetCMakeInstance()->GetGlobCacheEntries()) {
+    globsDependent.append(this->DumpGlobDependent(entry));
+  }
+  return globsDependent;
+}
+
+Json::Value CMakeFiles::DumpGlobDependent(cmGlobCacheEntry const& entry)
+{
+  Json::Value globDependent = Json::objectValue;
+  globDependent["expression"] = entry.Expression;
+  if (entry.Recurse) {
+    globDependent["recurse"] = true;
+  }
+  if (entry.ListDirectories) {
+    globDependent["listDirectories"] = true;
+  }
+  if (entry.FollowSymlinks) {
+    globDependent["followSymlinks"] = true;
+  }
+  if (!entry.Relative.empty()) {
+    globDependent["relative"] = entry.Relative;
+  }
+  Json::Value paths = Json::arrayValue;
+  for (std::string const& file : entry.Files) {
+    paths.append(file);
+  }
+  globDependent["paths"] = std::move(paths);
+  return globDependent;
+}
 }
 
 Json::Value cmFileAPICMakeFilesDump(cmFileAPI& fileAPI, unsigned long version)

+ 15 - 0
Source/cmGlobVerificationManager.cxx

@@ -176,6 +176,21 @@ void cmGlobVerificationManager::AddCacheEntry(
   }
 }
 
+std::vector<cmGlobCacheEntry> cmGlobVerificationManager::GetCacheEntries()
+  const
+{
+  std::vector<cmGlobCacheEntry> entries;
+  for (auto const& i : this->Cache) {
+    CacheEntryKey k = std::get<0>(i);
+    CacheEntryValue v = std::get<1>(i);
+    if (v.Initialized) {
+      entries.emplace_back(k.Recurse, k.ListDirectories, k.FollowSymlinks,
+                           k.Relative, k.Expression, v.Files);
+    }
+  }
+  return entries;
+}
+
 void cmGlobVerificationManager::Reset()
 {
   this->Cache.clear();

+ 3 - 0
Source/cmGlobVerificationManager.h

@@ -33,6 +33,9 @@ protected:
                      const std::string& variable,
                      const cmListFileBacktrace& bt, cmMessenger* messenger);
 
+  //! Get all cache entries
+  std::vector<cmGlobCacheEntry> GetCacheEntries() const;
+
   //! Clear the glob cache for state reset.
   void Reset();
 

+ 5 - 0
Source/cmState.cxx

@@ -248,6 +248,11 @@ void cmState::AddGlobCacheEntry(const cmGlobCacheEntry& entry,
                                                messenger);
 }
 
+std::vector<cmGlobCacheEntry> cmState::GetGlobCacheEntries() const
+{
+  return this->GlobVerificationManager->GetCacheEntries();
+}
+
 void cmState::RemoveCacheEntry(std::string const& key)
 {
   this->CacheManager->RemoveCacheEntry(key);

+ 1 - 0
Source/cmState.h

@@ -268,6 +268,7 @@ private:
                          const std::string& variable,
                          cmListFileBacktrace const& bt,
                          cmMessenger* messenger);
+  std::vector<cmGlobCacheEntry> GetGlobCacheEntries() const;
 
   cmPropertyDefinitionMap PropertyDefinitions;
   std::vector<std::string> EnabledLanguages;

+ 5 - 0
Source/cmake.cxx

@@ -2958,6 +2958,11 @@ void cmake::AddGlobCacheEntry(const cmGlobCacheEntry& entry,
                                  this->Messenger.get());
 }
 
+std::vector<cmGlobCacheEntry> cmake::GetGlobCacheEntries() const
+{
+  return this->State->GetGlobCacheEntries();
+}
+
 std::vector<std::string> cmake::GetAllExtensions() const
 {
   std::vector<std::string> allExt = this->CLikeSourceFileExtensions.ordered;

+ 1 - 0
Source/cmake.h

@@ -355,6 +355,7 @@ public:
   void AddGlobCacheEntry(const cmGlobCacheEntry& entry,
                          const std::string& variable,
                          cmListFileBacktrace const& bt);
+  std::vector<cmGlobCacheEntry> GetGlobCacheEntries() const;
 
   /**
    * Get the system information and write it to the file specified

+ 1 - 1
Tests/RunCMake/CommandLine/E_capabilities-stdout.txt

@@ -1 +1 @@
-^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":7}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
+^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":7}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":1}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$

+ 42 - 2
Tests/RunCMake/FileAPI/cmakeFiles-v1-check.py

@@ -3,7 +3,7 @@ from check_index import *
 def check_objects(o):
     assert is_list(o)
     assert len(o) == 1
-    check_index_object(o[0], "cmakeFiles", 1, 0, check_object_cmakeFiles)
+    check_index_object(o[0], "cmakeFiles", 1, 1, check_object_cmakeFiles)
 
 def check_input(actual, expected):
     assert is_dict(actual)
@@ -23,8 +23,27 @@ def check_input(actual, expected):
 
     assert sorted(actual.keys()) == sorted(expected_keys)
 
+def check_glob_dependent(actual, expected):
+    assert is_dict(actual)
+
+    if "followSymlinks" in expected:
+        assert is_bool(actual["followSymlinks"], expected["followSymlinks"])
+
+    if "listDirectories" in expected:
+        assert is_bool(actual["listDirectories"], expected["listDirectories"])
+
+    if "recurse" in expected:
+        assert is_bool(actual["recurse"], expected["recurse"])
+
+    if "relative" in expected:
+        assert matches(actual["relative"], expected["relative"])
+
+    check_list_match(lambda a, e: matches(a, e), actual["paths"], expected["paths"], allow_extra=True)
+
+    assert sorted(actual.keys()) == sorted(expected.keys())
+
 def check_object_cmakeFiles(o):
-    assert sorted(o.keys()) == ["inputs", "kind", "paths", "version"]
+    assert sorted(o.keys()) == ["globsDependent", "inputs", "kind", "paths", "version"]
     # The "kind" and "version" members are handled by check_index_object.
     assert is_dict(o["paths"])
     assert sorted(o["paths"].keys()) == ["build", "source"]
@@ -82,12 +101,33 @@ def check_object_cmakeFiles(o):
         },
     ]
 
+    expected_globs = [
+        {
+            "expression": "^.*/Tests/RunCMake/FileAPI/dir/\\*$",
+            "paths": [
+                "^.*/Tests/RunCMake/FileAPI/dir/dir$",
+                "^.*/Tests/RunCMake/FileAPI/dir/dirtest\\.cmake$"
+            ],
+            "listDirectories": True,
+        },
+        {
+            "expression": "^.*/Tests/RunCMake/FileAPI/dir/\\*\\.cmake$",
+            "paths": [
+                "^dir/dirtest\\.cmake$"
+            ],
+            "followSymlinks": True,
+            "recurse": True,
+            "relative": "^.*/Tests/RunCMake/FileAPI$"
+        }
+    ]
+
     inSource = os.path.dirname(o["paths"]["build"]) == o["paths"]["source"]
     if inSource:
         for e in expected:
             e["path"] = e["path"].replace("^.*/Tests/RunCMake/FileAPI/", "^", 1)
 
     check_list_match(lambda a, e: matches(a["path"], e["path"]), o["inputs"], expected, check=check_input, allow_extra=True)
+    check_list_match(lambda a, e: matches(a["expression"], e["expression"]), o["globsDependent"], expected_globs, check=check_glob_dependent, allow_extra=True)
 
 assert is_dict(index)
 assert sorted(index.keys()) == ["cmake", "objects", "reply"]

+ 10 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1.cmake

@@ -5,4 +5,14 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated.cmake" "")
 include("${CMAKE_CURRENT_BINARY_DIR}/generated.cmake")
 include("${CMAKE_CURRENT_LIST_DIR}/../FileAPIDummyFile.cmake")
 
+file(GLOB var
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_SOURCE_DIR}/dir/*")
+
+file(GLOB_RECURSE var
+  FOLLOW_SYMLINKS
+  RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_SOURCE_DIR}/dir/*.cmake")
+
 add_subdirectory(dir)