Browse Source

Merge topic 'glob_configure_depends'

6c4f8b4596 Adjust help documentation for file(GLOB), add topic notes
20612978c8 Add tests for `file(GLOB)` CONFIGURE_DEPENDS flag
3f4b81f540 Add glob verify support to XCode, VS, Ninja, and Makefile generators
ca0befc2e1 Add `CONFIGURE_DEPENDS` flag support to cmFileCommand::HandleGlobCommand
599c93e22d Add cmGlobVerificationManager class, integrate with cmake and cmState

Acked-by: Kitware Robot <[email protected]>
Merge-request: !1767
Brad King 7 years ago
parent
commit
d308e9442e
38 changed files with 793 additions and 56 deletions
  1. 11 2
      Help/command/file.rst
  2. 6 0
      Help/release/dev/glob_configure_depends.rst
  3. 2 0
      Source/CMakeLists.txt
  4. 38 0
      Source/cmFileCommand.cxx
  5. 172 0
      Source/cmGlobVerificationManager.cxx
  6. 89 0
      Source/cmGlobVerificationManager.h
  7. 71 6
      Source/cmGlobalNinjaGenerator.cxx
  8. 3 0
      Source/cmGlobalNinjaGenerator.h
  9. 7 0
      Source/cmGlobalUnixMakefileGenerator3.cxx
  10. 30 8
      Source/cmGlobalVisualStudio8Generator.cxx
  11. 22 2
      Source/cmGlobalXCodeGenerator.cxx
  12. 10 0
      Source/cmLocalNinjaGenerator.cxx
  13. 15 1
      Source/cmLocalUnixMakefileGenerator3.cxx
  14. 29 9
      Source/cmLocalVisualStudio7Generator.cxx
  15. 36 0
      Source/cmState.cxx
  16. 14 0
      Source/cmState.h
  17. 68 28
      Source/cmake.cxx
  18. 10 0
      Source/cmake.h
  19. 1 0
      Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-build-stdout.txt
  20. 2 0
      Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_first-stdout.txt
  21. 2 0
      Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_second-stdout.txt
  22. 1 0
      Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-stdout.txt
  23. 10 0
      Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake.cmake
  24. 1 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE-result.txt
  25. 1 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE-stderr.txt
  26. 1 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE.cmake
  27. 1 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified-result.txt
  28. 7 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified-stderr.txt
  29. 21 0
      Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified.cmake
  30. 1 0
      Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS-result.txt
  31. 4 0
      Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS-stderr.txt
  32. 1 0
      Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS.cmake
  33. 6 0
      Tests/RunCMake/file/GLOB-warn-CONFIGURE_DEPENDS-late-stderr.txt
  34. 11 0
      Tests/RunCMake/file/GLOB-warn-CONFIGURE_DEPENDS-late.cmake
  35. 13 0
      Tests/RunCMake/file/GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version-stderr.txt
  36. 6 0
      Tests/RunCMake/file/GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version.cmake
  37. 69 0
      Tests/RunCMake/file/RunCMakeTest.cmake
  38. 1 0
      bootstrap

+ 11 - 2
Help/command/file.rst

@@ -98,10 +98,10 @@ command.
 ::
 
   file(GLOB <variable>
-       [LIST_DIRECTORIES true|false] [RELATIVE <path>]
+       [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]
        [<globbing-expressions>...])
   file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS]
-       [LIST_DIRECTORIES true|false] [RELATIVE <path>]
+       [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]
        [<globbing-expressions>...])
 
 Generate a list of files that match the ``<globbing-expressions>`` and
@@ -110,6 +110,11 @@ regular expressions, but much simpler.  If ``RELATIVE`` flag is
 specified, the results will be returned as relative paths to the given
 path.  The results will be ordered lexicographically.
 
+If the ``CONFIGURE_DEPENDS`` flag is specified, CMake will add logic
+to the main build system check target to rerun the flagged ``GLOB`` commands
+at build time. If any of the outputs change, CMake will regenerate the build
+system.
+
 By default ``GLOB`` lists directories - directories are omitted in result if
 ``LIST_DIRECTORIES`` is set to false.
 
@@ -118,6 +123,10 @@ By default ``GLOB`` lists directories - directories are omitted in result if
   your source tree.  If no CMakeLists.txt file changes when a source is
   added or removed then the generated build system cannot know when to
   ask CMake to regenerate.
+  The ``CONFIGURE_DEPENDS`` flag may not work reliably on all generators, or if
+  a new generator is added in the future that cannot support it, projects using
+  it will be stuck. Even if ``CONFIGURE_DEPENDS`` works reliably, there is
+  still a cost to perform the check on every rebuild.
 
 Examples of globbing expressions include::
 

+ 6 - 0
Help/release/dev/glob_configure_depends.rst

@@ -0,0 +1,6 @@
+glob_configure_depends
+----------------------
+
+* The :command:`file(GLOB)` and :command:`file(GLOB_RECURSE)` commands
+  learned a new flag ``CONFIGURE_DEPENDS`` which enables expression of
+  build system dependency on globbed directory's contents.

+ 2 - 0
Source/CMakeLists.txt

@@ -246,6 +246,8 @@ set(SRCS
   cmGlobalGeneratorFactory.h
   cmGlobalUnixMakefileGenerator3.cxx
   cmGlobalUnixMakefileGenerator3.h
+  cmGlobVerificationManager.cxx
+  cmGlobVerificationManager.h
   cmGraphAdjacencyList.h
   cmGraphVizWriter.cxx
   cmGraphVizWriter.h

+ 38 - 0
Source/cmFileCommand.cxx

@@ -758,7 +758,11 @@ bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
   }
 
   std::vector<std::string> files;
+  bool configureDepends = false;
+  bool warnConfigureLate = false;
   bool warnFollowedSymlinks = false;
+  const cmake::WorkingMode workingMode =
+    this->Makefile->GetCMakeInstance()->GetWorkingMode();
   while (i != args.end()) {
     if (*i == "LIST_DIRECTORIES") {
       ++i;
@@ -807,6 +811,27 @@ bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
         this->SetError("GLOB requires a glob expression after the directory.");
         return false;
       }
+    } else if (*i == "CONFIGURE_DEPENDS") {
+      // Generated build system depends on glob results
+      if (!configureDepends && warnConfigureLate) {
+        this->Makefile->IssueMessage(
+          cmake::AUTHOR_WARNING,
+          "CONFIGURE_DEPENDS flag was given after a glob expression was "
+          "already evaluated.");
+      }
+      if (workingMode != cmake::NORMAL_MODE) {
+        this->Makefile->IssueMessage(
+          cmake::FATAL_ERROR,
+          "CONFIGURE_DEPENDS is invalid for script and find package modes.");
+        return false;
+      }
+      configureDepends = true;
+      ++i;
+      if (i == args.end()) {
+        this->SetError(
+          "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
+        return false;
+      }
     } else {
       std::string expr = *i;
       if (!cmsys::SystemTools::FileIsFullPath(*i)) {
@@ -849,6 +874,19 @@ bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
 
       std::vector<std::string>& foundFiles = g.GetFiles();
       files.insert(files.end(), foundFiles.begin(), foundFiles.end());
+
+      if (configureDepends) {
+        std::sort(foundFiles.begin(), foundFiles.end());
+        foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
+                         foundFiles.end());
+        this->Makefile->GetCMakeInstance()->AddGlobCacheEntry(
+          recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
+          (recurse ? g.GetRecurseThroughSymlinks() : false),
+          (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
+          this->Makefile->GetBacktrace());
+      } else {
+        warnConfigureLate = true;
+      }
       ++i;
     }
   }

+ 172 - 0
Source/cmGlobVerificationManager.cxx

@@ -0,0 +1,172 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmGlobVerificationManager.h"
+
+#include "cmsys/FStream.hxx"
+#include <sstream>
+
+#include "cmGeneratedFileStream.h"
+#include "cmListFileCache.h"
+#include "cmSystemTools.h"
+#include "cmVersion.h"
+#include "cmake.h"
+
+bool cmGlobVerificationManager::SaveVerificationScript(const std::string& path)
+{
+  if (this->Cache.empty()) {
+    return true;
+  }
+
+  std::string scriptFile = path;
+  scriptFile += cmake::GetCMakeFilesDirectory();
+  std::string stampFile = scriptFile;
+  cmSystemTools::MakeDirectory(scriptFile);
+  scriptFile += "/VerifyGlobs.cmake";
+  stampFile += "/cmake.verify_globs";
+  cmGeneratedFileStream verifyScriptFile(scriptFile.c_str());
+  verifyScriptFile.SetCopyIfDifferent(true);
+  if (!verifyScriptFile) {
+    cmSystemTools::Error("Unable to open verification script file for save. ",
+                         scriptFile.c_str());
+    cmSystemTools::ReportLastSystemError("");
+    return false;
+  }
+
+  verifyScriptFile << std::boolalpha;
+  verifyScriptFile << "# CMAKE generated file: DO NOT EDIT!\n"
+                   << "# Generated by CMake Version "
+                   << cmVersion::GetMajorVersion() << "."
+                   << cmVersion::GetMinorVersion() << "\n";
+
+  for (auto const& i : this->Cache) {
+    CacheEntryKey k = std::get<0>(i);
+    CacheEntryValue v = std::get<1>(i);
+
+    if (!v.Initialized) {
+      continue;
+    }
+
+    verifyScriptFile << "\n";
+
+    for (auto const& bt : v.Backtraces) {
+      verifyScriptFile << "# " << std::get<0>(bt);
+      std::get<1>(bt).PrintTitle(verifyScriptFile);
+      verifyScriptFile << "\n";
+    }
+
+    k.PrintGlobCommand(verifyScriptFile, "NEW_GLOB");
+    verifyScriptFile << "\n";
+
+    verifyScriptFile << "set(OLD_GLOB\n";
+    for (const std::string& file : v.Files) {
+      verifyScriptFile << "  \"" << file << "\"\n";
+    }
+    verifyScriptFile << "  )\n";
+
+    verifyScriptFile << "if(NOT \"${NEW_GLOB}\" STREQUAL \"${OLD_GLOB}\")\n"
+                     << "  message(\"-- GLOB mismatch!\")\n"
+                     << "  file(TOUCH_NOCREATE \"" << stampFile << "\")\n"
+                     << "endif()\n";
+  }
+  verifyScriptFile.Close();
+
+  cmsys::ofstream verifyStampFile(stampFile.c_str());
+  if (!verifyStampFile) {
+    cmSystemTools::Error("Unable to open verification stamp file for write. ",
+                         stampFile.c_str());
+    return false;
+  }
+  verifyStampFile << "# This file is generated by CMake for checking of the "
+                     "VerifyGlobs.cmake file\n";
+  this->VerifyScript = scriptFile;
+  this->VerifyStamp = stampFile;
+  return true;
+}
+
+bool cmGlobVerificationManager::DoWriteVerifyTarget() const
+{
+  return !this->VerifyScript.empty() && !this->VerifyStamp.empty();
+}
+
+bool cmGlobVerificationManager::CacheEntryKey::operator<(
+  const CacheEntryKey& r) const
+{
+  if (this->Recurse < r.Recurse) {
+    return true;
+  }
+  if (this->Recurse > r.Recurse) {
+    return false;
+  }
+  if (this->ListDirectories < r.ListDirectories) {
+    return true;
+  }
+  if (this->ListDirectories > r.ListDirectories) {
+    return false;
+  }
+  if (this->FollowSymlinks < r.FollowSymlinks) {
+    return true;
+  }
+  if (this->FollowSymlinks > r.FollowSymlinks) {
+    return false;
+  }
+  if (this->Relative < r.Relative) {
+    return true;
+  }
+  if (this->Relative > r.Relative) {
+    return false;
+  }
+  if (this->Expression < r.Expression) {
+    return true;
+  }
+  if (this->Expression > r.Expression) {
+    return false;
+  }
+  return false;
+}
+
+void cmGlobVerificationManager::CacheEntryKey::PrintGlobCommand(
+  std::ostream& out, const std::string& cmdVar)
+{
+  out << "file(GLOB" << (this->Recurse ? "_RECURSE " : " ");
+  out << cmdVar << " ";
+  if (this->Recurse && this->FollowSymlinks) {
+    out << "FOLLOW_SYMLINKS ";
+  }
+  out << "LIST_DIRECTORIES " << this->ListDirectories << " ";
+  if (!this->Relative.empty()) {
+    out << "RELATIVE \"" << this->Relative << "\" ";
+  }
+  out << "\"" << this->Expression << "\")";
+}
+
+void cmGlobVerificationManager::AddCacheEntry(
+  const bool recurse, const bool listDirectories, const bool followSymlinks,
+  const std::string& relative, const std::string& expression,
+  const std::vector<std::string>& files, const std::string& variable,
+  const cmListFileBacktrace& backtrace)
+{
+  CacheEntryKey key = CacheEntryKey(recurse, listDirectories, followSymlinks,
+                                    relative, expression);
+  CacheEntryValue& value = this->Cache[key];
+  if (!value.Initialized) {
+    value.Files = files;
+    value.Initialized = true;
+    value.Backtraces.emplace_back(variable, backtrace);
+  } else if (value.Initialized && value.Files != files) {
+    std::ostringstream message;
+    message << std::boolalpha;
+    message << "The glob expression\n";
+    key.PrintGlobCommand(message, variable);
+    backtrace.PrintTitle(message);
+    message << "\nwas already present in the glob cache but the directory\n"
+               "contents have changed during the configuration run.\n";
+    message << "Matching glob expressions:";
+    for (auto const& bt : value.Backtraces) {
+      message << "\n  " << std::get<0>(bt);
+      std::get<1>(bt).PrintTitle(message);
+    }
+    cmSystemTools::Error(message.str().c_str());
+  } else {
+    value.Backtraces.emplace_back(variable, backtrace);
+  }
+}

+ 89 - 0
Source/cmGlobVerificationManager.h

@@ -0,0 +1,89 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmGlobVerificationManager_h
+#define cmGlobVerificationManager_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmListFileCache.h"
+
+#include <iosfwd>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+/** \class cmGlobVerificationManager
+ * \brief Class for expressing build-time dependencies on glob expressions.
+ *
+ * Generates a CMake script which verifies glob outputs during prebuild.
+ *
+ */
+class cmGlobVerificationManager
+{
+public:
+  cmGlobVerificationManager() {}
+
+protected:
+  ///! Save verification script for given makefile.
+  ///! Saves to output <path>/<CMakeFilesDirectory>/VerifyGlobs.cmake
+  bool SaveVerificationScript(const std::string& path);
+
+  ///! Add an entry into the glob cache
+  void AddCacheEntry(bool recurse, bool listDirectories, bool followSymlinks,
+                     const std::string& relative,
+                     const std::string& expression,
+                     const std::vector<std::string>& files,
+                     const std::string& variable,
+                     const cmListFileBacktrace& bt);
+
+  ///! Check targets should be written in generated build system.
+  bool DoWriteVerifyTarget() const;
+
+  ///! Get the paths to the generated script and stamp files
+  std::string const& GetVerifyScript() const { return this->VerifyScript; }
+  std::string const& GetVerifyStamp() const { return this->VerifyStamp; }
+
+private:
+  struct CacheEntryKey
+  {
+    const bool Recurse;
+    const bool ListDirectories;
+    const bool FollowSymlinks;
+    const std::string Relative;
+    const std::string Expression;
+    CacheEntryKey(const bool rec, const bool l, const bool s,
+                  const std::string& rel, const std::string& e)
+      : Recurse(rec)
+      , ListDirectories(l)
+      , FollowSymlinks(s)
+      , Relative(rel)
+      , Expression(e)
+    {
+    }
+    bool operator<(const CacheEntryKey& r) const;
+    void PrintGlobCommand(std::ostream& out, const std::string& cmdVar);
+  };
+
+  struct CacheEntryValue
+  {
+    bool Initialized;
+    std::vector<std::string> Files;
+    std::vector<std::pair<std::string, cmListFileBacktrace>> Backtraces;
+    CacheEntryValue()
+      : Initialized(false)
+    {
+    }
+  };
+
+  typedef std::map<CacheEntryKey, CacheEntryValue> CacheEntryMap;
+  CacheEntryMap Cache;
+  std::string VerifyScript;
+  std::string VerifyStamp;
+
+  // Only cmState should be able to add cache values.
+  // cmGlobVerificationManager should never be used directly.
+  friend class cmState; // allow access to add cache values
+};
+
+#endif

+ 71 - 6
Source/cmGlobalNinjaGenerator.cxx

@@ -475,6 +475,7 @@ cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
   , PolicyCMP0058(cmPolicies::WARN)
   , NinjaSupportsConsolePool(false)
   , NinjaSupportsImplicitOuts(false)
+  , NinjaSupportsManifestRestat(false)
   , NinjaSupportsDyndeps(0)
 {
 #ifdef _WIN32
@@ -597,6 +598,9 @@ void cmGlobalNinjaGenerator::CheckNinjaFeatures()
   this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
     cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
     this->RequiredNinjaVersionForImplicitOuts().c_str());
+  this->NinjaSupportsManifestRestat = !cmSystemTools::VersionCompare(
+    cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
+    RequiredNinjaVersionForManifestRestat().c_str());
   {
     // Our ninja branch adds ".dyndep-#" to its version number,
     // where '#' is a feature-specific version number.  Extract it.
@@ -1361,6 +1365,7 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
             /*generator=*/true);
 
   cmNinjaDeps implicitDeps;
+  cmNinjaDeps explicitDeps;
   for (cmLocalGenerator* localGen : this->LocalGenerators) {
     std::vector<std::string> const& lf =
       localGen->GetMakefile()->GetListFiles();
@@ -1370,10 +1375,6 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
   }
   implicitDeps.push_back(this->CMakeCacheFile);
 
-  std::sort(implicitDeps.begin(), implicitDeps.end());
-  implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()),
-                     implicitDeps.end());
-
   cmNinjaVars variables;
   // Use 'console' pool to get non buffered output of the CMake re-run call
   // Available since Ninja 1.5
@@ -1381,12 +1382,71 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
     variables["pool"] = "console";
   }
 
+  cmake* cm = this->GetCMakeInstance();
+  if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
+    std::ostringstream verify_cmd;
+    verify_cmd << lg->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
+                                            cmOutputConverter::SHELL)
+               << " -P "
+               << lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
+                                            cmOutputConverter::SHELL);
+
+    WriteRule(*this->RulesFileStream, "VERIFY_GLOBS", verify_cmd.str(),
+              "Re-checking globbed directories...",
+              "Rule for re-checking globbed directories.",
+              /*depfile=*/"",
+              /*deptype=*/"",
+              /*rspfile=*/"",
+              /*rspcontent*/ "",
+              /*restat=*/"",
+              /*generator=*/true);
+
+    std::string verifyForce = cm->GetGlobVerifyScript() + "_force";
+    cmNinjaDeps verifyForceDeps(1, this->NinjaOutputPath(verifyForce));
+
+    this->WritePhonyBuild(os, "Phony target to force glob verification run.",
+                          verifyForceDeps, cmNinjaDeps());
+
+    variables["restat"] = "1";
+    std::string const verifyScriptFile =
+      this->NinjaOutputPath(cm->GetGlobVerifyScript());
+    std::string const verifyStampFile =
+      this->NinjaOutputPath(cm->GetGlobVerifyStamp());
+    this->WriteBuild(os,
+                     "Re-run CMake to check if globbed directories changed.",
+                     "VERIFY_GLOBS",
+                     /*outputs=*/cmNinjaDeps(1, verifyStampFile),
+                     /*implicitOuts=*/cmNinjaDeps(),
+                     /*explicitDeps=*/cmNinjaDeps(),
+                     /*implicitDeps=*/verifyForceDeps,
+                     /*orderOnlyDeps=*/cmNinjaDeps(), variables);
+
+    variables.erase("restat");
+    implicitDeps.push_back(verifyScriptFile);
+    explicitDeps.push_back(verifyStampFile);
+  } else if (!this->SupportsManifestRestat() &&
+             cm->DoWriteGlobVerifyTarget()) {
+    std::ostringstream msg;
+    msg << "The detected version of Ninja:\n"
+        << "  " << this->NinjaVersion << "\n"
+        << "is less than the version of Ninja required by CMake for adding "
+           "restat dependencies to the build.ninja manifest regeneration "
+           "target:\n"
+        << "  " << this->RequiredNinjaVersionForManifestRestat() << "\n";
+    msg << "Any pre-check scripts, such as those generated for file(GLOB "
+           "CONFIGURE_DEPENDS), will not be run by Ninja.";
+    this->GetCMakeInstance()->IssueMessage(cmake::AUTHOR_WARNING, msg.str());
+  }
+
+  std::sort(implicitDeps.begin(), implicitDeps.end());
+  implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()),
+                     implicitDeps.end());
+
   std::string const ninjaBuildFile = this->NinjaOutputPath(NINJA_BUILD_FILE);
   this->WriteBuild(os, "Re-run CMake if any of its inputs changed.",
                    "RERUN_CMAKE",
                    /*outputs=*/cmNinjaDeps(1, ninjaBuildFile),
-                   /*implicitOuts=*/cmNinjaDeps(),
-                   /*explicitDeps=*/cmNinjaDeps(), implicitDeps,
+                   /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
                    /*orderOnlyDeps=*/cmNinjaDeps(), variables);
 
   cmNinjaDeps missingInputs;
@@ -1419,6 +1479,11 @@ bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
   return this->NinjaSupportsImplicitOuts;
 }
 
+bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
+{
+  return this->NinjaSupportsManifestRestat;
+}
+
 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
 {
   WriteRule(*this->RulesFileStream, "CLEAN", ninjaCmd() + " -t clean",

+ 3 - 0
Source/cmGlobalNinjaGenerator.h

@@ -346,8 +346,10 @@ public:
   static std::string RequiredNinjaVersion() { return "1.3"; }
   static std::string RequiredNinjaVersionForConsolePool() { return "1.5"; }
   static std::string RequiredNinjaVersionForImplicitOuts() { return "1.7"; }
+  static std::string RequiredNinjaVersionForManifestRestat() { return "1.8"; }
   bool SupportsConsolePool() const;
   bool SupportsImplicitOuts() const;
+  bool SupportsManifestRestat() const;
 
   std::string NinjaOutputPath(std::string const& path) const;
   bool HasOutputPathPrefix() const { return !this->OutputPathPrefix.empty(); }
@@ -460,6 +462,7 @@ private:
   std::string NinjaVersion;
   bool NinjaSupportsConsolePool;
   bool NinjaSupportsImplicitOuts;
+  bool NinjaSupportsManifestRestat;
   unsigned long NinjaSupportsDyndeps;
 
 private:

+ 7 - 0
Source/cmGlobalUnixMakefileGenerator3.cxx

@@ -301,6 +301,13 @@ void cmGlobalUnixMakefileGenerator3::WriteMainCMakefile()
     lfiles.insert(lfiles.end(), lg->GetMakefile()->GetListFiles().begin(),
                   lg->GetMakefile()->GetListFiles().end());
   }
+
+  cmake* cm = this->GetCMakeInstance();
+  if (cm->DoWriteGlobVerifyTarget()) {
+    lfiles.push_back(cm->GetGlobVerifyScript());
+    lfiles.push_back(cm->GetGlobVerifyStamp());
+  }
+
   // Sort the list and remove duplicates.
   std::sort(lfiles.begin(), lfiles.end(), std::less<std::string>());
 #if !defined(__VMS) // The Compaq STL on VMS crashes, so accept duplicates.

+ 30 - 8
Source/cmGlobalVisualStudio8Generator.cxx

@@ -87,18 +87,18 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget()
 {
   // Add a special target on which all other targets depend that
   // checks the build system and optionally re-runs CMake.
-  const char* no_working_directory = 0;
+  // Skip the target if no regeneration is to be done.
+  if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
+    return false;
+  }
+
+  const char* no_working_directory = nullptr;
   std::vector<std::string> no_depends;
   std::vector<cmLocalGenerator*> const& generators = this->LocalGenerators;
   cmLocalVisualStudio7Generator* lg =
     static_cast<cmLocalVisualStudio7Generator*>(generators[0]);
   cmMakefile* mf = lg->GetMakefile();
 
-  // Skip the target if no regeneration is to be done.
-  if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
-    return false;
-  }
-
   cmCustomCommandLines noCommandLines;
   cmTarget* tgt = mf->AddUtilityCommand(
     CMAKE_CHECK_BUILD_SYSTEM_TARGET, cmMakefile::TargetOrigin::Generator,
@@ -144,6 +144,30 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget()
       listFiles.insert(listFiles.end(), lmf->GetListFiles().begin(),
                        lmf->GetListFiles().end());
     }
+
+    // Add a custom prebuild target to run the VerifyGlobs script.
+    cmake* cm = this->GetCMakeInstance();
+    if (cm->DoWriteGlobVerifyTarget()) {
+      cmCustomCommandLine verifyCommandLine;
+      verifyCommandLine.push_back(cmSystemTools::GetCMakeCommand());
+      verifyCommandLine.push_back("-P");
+      verifyCommandLine.push_back(cm->GetGlobVerifyScript());
+      cmCustomCommandLines verifyCommandLines;
+      verifyCommandLines.push_back(verifyCommandLine);
+      std::vector<std::string> byproducts;
+      byproducts.push_back(cm->GetGlobVerifyStamp());
+
+      mf->AddCustomCommandToTarget(CMAKE_CHECK_BUILD_SYSTEM_TARGET, byproducts,
+                                   no_depends, verifyCommandLines,
+                                   cmTarget::PRE_BUILD, "Checking File Globs",
+                                   no_working_directory, false);
+
+      // Ensure ZERO_CHECK always runs in Visual Studio using MSBuild,
+      // otherwise the prebuild command will not be run.
+      tgt->SetProperty("VS_GLOBAL_DisableFastUpToDateCheck", "true");
+      listFiles.push_back(cm->GetGlobVerifyStamp());
+    }
+
     // Sort the list of input files and remove duplicates.
     std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
     std::vector<std::string>::iterator new_end =
@@ -151,8 +175,6 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget()
     listFiles.erase(new_end, listFiles.end());
 
     // Create a rule to re-run CMake.
-    std::string stampName = cmake::GetCMakeFilesDirectoryPostSlash();
-    stampName += "generate.stamp";
     cmCustomCommandLine commandLine;
     commandLine.push_back(cmSystemTools::GetCMakeCommand());
     std::string argH = "-H";

+ 22 - 2
Source/cmGlobalXCodeGenerator.cxx

@@ -537,6 +537,12 @@ void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
   std::vector<std::string>::iterator new_end =
     std::unique(lfiles.begin(), lfiles.end());
   lfiles.erase(new_end, lfiles.end());
+
+  cmake* cm = this->GetCMakeInstance();
+  if (cm->DoWriteGlobVerifyTarget()) {
+    lfiles.emplace_back(cm->GetGlobVerifyStamp());
+  }
+
   this->CurrentReRunCMakeMakefile = root->GetCurrentBinaryDirectory();
   this->CurrentReRunCMakeMakefile += "/CMakeScripts";
   cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile.c_str());
@@ -555,14 +561,28 @@ void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
     makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
                    << this->ConvertToRelativeForMake(lfile) << "))\n";
   }
+  makefileStream << "\n";
 
   std::string checkCache = root->GetBinaryDirectory();
   checkCache += "/";
   checkCache += cmake::GetCMakeFilesDirectoryPostSlash();
   checkCache += "cmake.check_cache";
 
-  makefileStream << "\n"
-                 << this->ConvertToRelativeForMake(checkCache)
+  if (cm->DoWriteGlobVerifyTarget()) {
+    makefileStream << ".NOTPARALLEL:\n\n";
+    makefileStream << ".PHONY: all VERIFY_GLOBS\n\n";
+    makefileStream << "all: VERIFY_GLOBS "
+                   << this->ConvertToRelativeForMake(checkCache) << "\n\n";
+    makefileStream << "VERIFY_GLOBS:\n";
+    makefileStream << "\t"
+                   << this->ConvertToRelativeForMake(
+                        cmSystemTools::GetCMakeCommand())
+                   << " -P "
+                   << this->ConvertToRelativeForMake(cm->GetGlobVerifyScript())
+                   << "\n\n";
+  }
+
+  makefileStream << this->ConvertToRelativeForMake(checkCache)
                  << ": $(TARGETS)\n";
   makefileStream << "\t"
                  << this->ConvertToRelativeForMake(

+ 10 - 0
Source/cmLocalNinjaGenerator.cxx

@@ -197,6 +197,16 @@ void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
       this->GetGlobalNinjaGenerator()->RequiredNinjaVersionForConsolePool();
   }
 
+  // The Ninja generator writes rules which require support for restat
+  // when rebuilding build.ninja manifest (>= 1.8)
+  if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() &&
+      this->GetCMakeInstance()->DoWriteGlobVerifyTarget() &&
+      !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn(
+        "CMAKE_SUPPRESS_REGENERATION")) {
+    requiredVersion =
+      this->GetGlobalNinjaGenerator()->RequiredNinjaVersionForManifestRestat();
+  }
+
   cmGlobalNinjaGenerator::WriteComment(
     os, "Minimal version of Ninja required by this file");
   os << "ninja_required_version = " << requiredVersion << std::endl

+ 15 - 1
Source/cmLocalUnixMakefileGenerator3.cxx

@@ -763,6 +763,14 @@ void cmLocalUnixMakefileGenerator3::WriteSpecialTargetsBottom(
   if (!this->GlobalGenerator->GlobalSettingIsOn(
         "CMAKE_SUPPRESS_REGENERATION")) {
     // Build command to run CMake to check if anything needs regenerating.
+    std::vector<std::string> commands;
+    cmake* cm = this->GlobalGenerator->GetCMakeInstance();
+    if (cm->DoWriteGlobVerifyTarget()) {
+      std::string rescanRule = "$(CMAKE_COMMAND) -P ";
+      rescanRule += this->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
+                                                cmOutputConverter::SHELL);
+      commands.push_back(rescanRule);
+    }
     std::string cmakefileName = cmake::GetCMakeFilesDirectoryPostSlash();
     cmakefileName += "Makefile.cmake";
     std::string runRule =
@@ -773,7 +781,6 @@ void cmLocalUnixMakefileGenerator3::WriteSpecialTargetsBottom(
     runRule += " 0";
 
     std::vector<std::string> no_depends;
-    std::vector<std::string> commands;
     commands.push_back(std::move(runRule));
     if (!this->IsRootMakefile()) {
       this->CreateCDCommand(commands, this->GetBinaryDirectory(),
@@ -1666,6 +1673,13 @@ void cmLocalUnixMakefileGenerator3::WriteLocalAllRules(
     // write the depend rule, really a recompute depends rule
     depends.clear();
     commands.clear();
+    cmake* cm = this->GlobalGenerator->GetCMakeInstance();
+    if (cm->DoWriteGlobVerifyTarget()) {
+      std::string rescanRule = "$(CMAKE_COMMAND) -P ";
+      rescanRule += this->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
+                                                cmOutputConverter::SHELL);
+      commands.push_back(rescanRule);
+    }
     std::string cmakefileName = cmake::GetCMakeFilesDirectoryPostSlash();
     cmakefileName += "Makefile.cmake";
     {

+ 29 - 9
Source/cmLocalVisualStudio7Generator.cxx

@@ -160,10 +160,21 @@ void cmLocalVisualStudio7Generator::WriteStampFiles()
   depName += ".depend";
   cmsys::ofstream depFile(depName.c_str());
   depFile << "# CMake generation dependency list for this directory.\n";
-  std::vector<std::string> const& listFiles = this->Makefile->GetListFiles();
-  for (std::vector<std::string>::const_iterator lf = listFiles.begin();
-       lf != listFiles.end(); ++lf) {
-    depFile << *lf << std::endl;
+
+  std::vector<std::string> listFiles(this->Makefile->GetListFiles());
+  cmake* cm = this->GlobalGenerator->GetCMakeInstance();
+  if (cm->DoWriteGlobVerifyTarget()) {
+    listFiles.push_back(cm->GetGlobVerifyStamp());
+  }
+
+  // Sort the list of input files and remove duplicates.
+  std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
+  std::vector<std::string>::iterator new_end =
+    std::unique(listFiles.begin(), listFiles.end());
+  listFiles.erase(new_end, listFiles.end());
+
+  for (const std::string& lf : listFiles) {
+    depFile << lf << "\n";
   }
 }
 
@@ -228,6 +239,18 @@ cmSourceFile* cmLocalVisualStudio7Generator::CreateVCProjBuildRule()
     return nullptr;
   }
 
+  std::vector<std::string> listFiles = this->Makefile->GetListFiles();
+  cmake* cm = this->GlobalGenerator->GetCMakeInstance();
+  if (cm->DoWriteGlobVerifyTarget()) {
+    listFiles.push_back(cm->GetGlobVerifyStamp());
+  }
+
+  // Sort the list of input files and remove duplicates.
+  std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
+  std::vector<std::string>::iterator new_end =
+    std::unique(listFiles.begin(), listFiles.end());
+  listFiles.erase(new_end, listFiles.end());
+
   std::string stampName = this->GetCurrentBinaryDirectory();
   stampName += "/";
   stampName += cmake::GetCMakeFilesDirectoryPostSlash();
@@ -245,17 +268,14 @@ cmSourceFile* cmLocalVisualStudio7Generator::CreateVCProjBuildRule()
   commandLine.push_back(args);
   commandLine.push_back("--check-stamp-file");
   commandLine.push_back(stampName);
-
-  std::vector<std::string> const& listFiles = this->Makefile->GetListFiles();
-
   cmCustomCommandLines commandLines;
   commandLines.push_back(commandLine);
   const char* no_working_directory = 0;
   std::string fullpathStampName =
     cmSystemTools::CollapseFullPath(stampName.c_str());
   this->Makefile->AddCustomCommandToOutput(
-    fullpathStampName.c_str(), listFiles, makefileIn.c_str(), commandLines,
-    comment.c_str(), no_working_directory, true, false);
+    fullpathStampName, listFiles, makefileIn, commandLines, comment.c_str(),
+    no_working_directory, true, false);
   if (cmSourceFile* file = this->Makefile->GetSource(makefileIn.c_str())) {
     // Finalize the source file path now since we're adding this after
     // the generator validated all project-named sources.

+ 36 - 0
Source/cmState.cxx

@@ -13,6 +13,7 @@
 #include "cmCommand.h"
 #include "cmDefinitions.h"
 #include "cmDisallowedCommand.h"
+#include "cmGlobVerificationManager.h"
 #include "cmListFileCache.h"
 #include "cmStatePrivate.h"
 #include "cmStateSnapshot.h"
@@ -31,11 +32,13 @@ cmState::cmState()
   , MSYSShell(false)
 {
   this->CacheManager = new cmCacheManager;
+  this->GlobVerificationManager = new cmGlobVerificationManager;
 }
 
 cmState::~cmState()
 {
   delete this->CacheManager;
+  delete this->GlobVerificationManager;
   cmDeleteAll(this->BuiltinCommands);
   cmDeleteAll(this->ScriptedCommands);
 }
@@ -207,6 +210,39 @@ void cmState::AddCacheEntry(const std::string& key, const char* value,
   this->CacheManager->AddCacheEntry(key, value, helpString, type);
 }
 
+bool cmState::DoWriteGlobVerifyTarget() const
+{
+  return this->GlobVerificationManager->DoWriteVerifyTarget();
+}
+
+std::string const& cmState::GetGlobVerifyScript() const
+{
+  return this->GlobVerificationManager->GetVerifyScript();
+}
+
+std::string const& cmState::GetGlobVerifyStamp() const
+{
+  return this->GlobVerificationManager->GetVerifyStamp();
+}
+
+bool cmState::SaveVerificationScript(const std::string& path)
+{
+  return this->GlobVerificationManager->SaveVerificationScript(path);
+}
+
+void cmState::AddGlobCacheEntry(bool recurse, bool listDirectories,
+                                bool followSymlinks,
+                                const std::string& relative,
+                                const std::string& expression,
+                                const std::vector<std::string>& files,
+                                const std::string& variable,
+                                cmListFileBacktrace const& backtrace)
+{
+  this->GlobVerificationManager->AddCacheEntry(
+    recurse, listDirectories, followSymlinks, relative, expression, files,
+    variable, backtrace);
+}
+
 void cmState::RemoveCacheEntry(std::string const& key)
 {
   this->CacheManager->RemoveCacheEntry(key);

+ 14 - 0
Source/cmState.h

@@ -12,6 +12,7 @@
 
 #include "cmDefinitions.h"
 #include "cmLinkedTree.h"
+#include "cmListFileCache.h"
 #include "cmPolicies.h"
 #include "cmProperty.h"
 #include "cmPropertyDefinitionMap.h"
@@ -21,6 +22,7 @@
 
 class cmCacheManager;
 class cmCommand;
+class cmGlobVerificationManager;
 class cmPropertyDefinition;
 class cmStateSnapshot;
 class cmMessenger;
@@ -165,12 +167,24 @@ private:
                      const char* helpString,
                      cmStateEnums::CacheEntryType type);
 
+  bool DoWriteGlobVerifyTarget() const;
+  std::string const& GetGlobVerifyScript() const;
+  std::string const& GetGlobVerifyStamp() const;
+  bool SaveVerificationScript(const std::string& path);
+  void AddGlobCacheEntry(bool recurse, bool listDirectories,
+                         bool followSymlinks, const std::string& relative,
+                         const std::string& expression,
+                         const std::vector<std::string>& files,
+                         const std::string& variable,
+                         cmListFileBacktrace const& bt);
+
   std::map<cmProperty::ScopeType, cmPropertyDefinitionMap> PropertyDefinitions;
   std::vector<std::string> EnabledLanguages;
   std::map<std::string, cmCommand*> BuiltinCommands;
   std::map<std::string, cmCommand*> ScriptedCommands;
   cmPropertyMap GlobalProperties;
   cmCacheManager* CacheManager;
+  cmGlobVerificationManager* GlobVerificationManager;
 
   cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>
     BuildsystemDirectory;

+ 68 - 28
Source/cmake.cxx

@@ -1433,6 +1433,7 @@ int cmake::ActualConfigure()
 
   // only save the cache if there were no fatal errors
   if (this->GetWorkingMode() == NORMAL_MODE) {
+    this->State->SaveVerificationScript(this->GetHomeOutputDirectory());
     this->SaveCache(this->GetHomeOutputDirectory());
   }
   if (cmSystemTools::GetErrorOccuredFlag()) {
@@ -1647,6 +1648,33 @@ void cmake::AddCacheEntry(const std::string& key, const char* value,
   this->UnwatchUnusedCli(key);
 }
 
+bool cmake::DoWriteGlobVerifyTarget() const
+{
+  return this->State->DoWriteGlobVerifyTarget();
+}
+
+std::string const& cmake::GetGlobVerifyScript() const
+{
+  return this->State->GetGlobVerifyScript();
+}
+
+std::string const& cmake::GetGlobVerifyStamp() const
+{
+  return this->State->GetGlobVerifyStamp();
+}
+
+void cmake::AddGlobCacheEntry(bool recurse, bool listDirectories,
+                              bool followSymlinks, const std::string& relative,
+                              const std::string& expression,
+                              const std::vector<std::string>& files,
+                              const std::string& variable,
+                              cmListFileBacktrace const& backtrace)
+{
+  this->State->AddGlobCacheEntry(recurse, listDirectories, followSymlinks,
+                                 relative, expression, files, variable,
+                                 backtrace);
+}
+
 std::string cmake::StripExtension(const std::string& file) const
 {
   auto dotpos = file.rfind('.');
@@ -2434,37 +2462,49 @@ int cmake::Build(const std::string& dir, const std::string& target,
     cmGlobalVisualStudio9Generator::GetGenerateStampList();
 
   // Note that the stampList file only exists for VS generators.
-  if (cmSystemTools::FileExists(stampList) &&
-      !cmakeCheckStampList(stampList.c_str(), false)) {
-
-    // Correctly initialize the home (=source) and home output (=binary)
-    // directories, which is required for running the generation step.
-    std::string homeOrig = this->GetHomeDirectory();
-    std::string homeOutputOrig = this->GetHomeOutputDirectory();
-    this->SetDirectoriesFromFile(cachePath.c_str());
+  if (cmSystemTools::FileExists(stampList)) {
 
+    // Check if running for Visual Studio 9 - we need to explicitly run
+    // the glob verification script before starting the build
     this->AddScriptingCommands();
-    this->AddProjectCommands();
+    if (this->GlobalGenerator->MatchesGeneratorName("Visual Studio 9 2008")) {
+      std::string const globVerifyScript = cachePath + "/" +
+        GetCMakeFilesDirectoryPostSlash() + "VerifyGlobs.cmake";
+      if (cmSystemTools::FileExists(globVerifyScript)) {
+        std::vector<std::string> args;
+        this->ReadListFile(args, globVerifyScript.c_str());
+      }
+    }
 
-    int ret = this->Configure();
-    if (ret) {
-      cmSystemTools::Message("CMake Configure step failed.  "
-                             "Build files cannot be regenerated correctly.");
-      return ret;
-    }
-    ret = this->Generate();
-    if (ret) {
-      cmSystemTools::Message("CMake Generate step failed.  "
-                             "Build files cannot be regenerated correctly.");
-      return ret;
-    }
-    std::string message = "Build files have been written to: ";
-    message += this->GetHomeOutputDirectory();
-    this->UpdateProgress(message.c_str(), -1);
-
-    // Restore the previously set directories to their original value.
-    this->SetHomeDirectory(homeOrig);
-    this->SetHomeOutputDirectory(homeOutputOrig);
+    if (!cmakeCheckStampList(stampList.c_str(), false)) {
+      // Correctly initialize the home (=source) and home output (=binary)
+      // directories, which is required for running the generation step.
+      std::string homeOrig = this->GetHomeDirectory();
+      std::string homeOutputOrig = this->GetHomeOutputDirectory();
+      this->SetDirectoriesFromFile(cachePath.c_str());
+
+      this->AddProjectCommands();
+
+      int ret = this->Configure();
+      if (ret) {
+        cmSystemTools::Message("CMake Configure step failed.  "
+                               "Build files cannot be regenerated correctly.");
+        return ret;
+      }
+      ret = this->Generate();
+      if (ret) {
+        cmSystemTools::Message("CMake Generate step failed.  "
+                               "Build files cannot be regenerated correctly.");
+        return ret;
+      }
+      std::string message = "Build files have been written to: ";
+      message += this->GetHomeOutputDirectory();
+      this->UpdateProgress(message.c_str(), -1);
+
+      // Restore the previously set directories to their original value.
+      this->SetHomeDirectory(homeOrig);
+      this->SetHomeOutputDirectory(homeOutputOrig);
+    }
   }
 #endif
 

+ 10 - 0
Source/cmake.h

@@ -255,6 +255,16 @@ public:
   void AddCacheEntry(const std::string& key, const char* value,
                      const char* helpString, int type);
 
+  bool DoWriteGlobVerifyTarget() const;
+  std::string const& GetGlobVerifyScript() const;
+  std::string const& GetGlobVerifyStamp() const;
+  void AddGlobCacheEntry(bool recurse, bool listDirectories,
+                         bool followSymlinks, const std::string& relative,
+                         const std::string& expression,
+                         const std::vector<std::string>& files,
+                         const std::string& variable,
+                         cmListFileBacktrace const& bt);
+
   /**
    * Get the system information and write it to the file specified
    */

+ 1 - 0
Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-build-stdout.txt

@@ -0,0 +1 @@
+.*b9fbdd8803c036dbe9f5ea6b74db4b9670c78a72

+ 2 - 0
Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_first-stdout.txt

@@ -0,0 +1,2 @@
+.*Running CMake on GLOB-CONFIGURE_DEPENDS-RerunCMake
+.*6bc141b40c0f851d20fa9a1fe5fbdae94acc5de0

+ 2 - 0
Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_second-stdout.txt

@@ -0,0 +1,2 @@
+.*Running CMake on GLOB-CONFIGURE_DEPENDS-RerunCMake
+.*0c3ceab9daa7914fde7410c34cae4049e140aa51

+ 1 - 0
Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake-stdout.txt

@@ -0,0 +1 @@
+.*Running CMake on GLOB-CONFIGURE_DEPENDS-RerunCMake

+ 10 - 0
Tests/RunCMake/file/GLOB-CONFIGURE_DEPENDS-RerunCMake.cmake

@@ -0,0 +1,10 @@
+message(STATUS "Running CMake on GLOB-CONFIGURE_DEPENDS-RerunCMake")
+file(GLOB_RECURSE
+  CONTENT_LIST
+  CONFIGURE_DEPENDS
+  LIST_DIRECTORIES false
+  RELATIVE "${CMAKE_CURRENT_BINARY_DIR}"
+  "${CMAKE_CURRENT_BINARY_DIR}/test/*"
+  )
+string(SHA1 CONTENT_LIST_HASH "${CONTENT_LIST}")
+add_custom_target(CONTENT_ECHO ALL ${CMAKE_COMMAND} -E echo ${CONTENT_LIST_HASH})

+ 1 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE-stderr.txt

@@ -0,0 +1 @@
+.*CONFIGURE_DEPENDS is invalid for script and find package modes\.

+ 1 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE.cmake

@@ -0,0 +1 @@
+file(GLOB CONTENT_LIST CONFIGURE_DEPENDS)

+ 1 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified-result.txt

@@ -0,0 +1 @@
+1

+ 7 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified-stderr.txt

@@ -0,0 +1,7 @@
+^CMake Error: The glob expression
+.* at GLOB-error-CONFIGURE_DEPENDS-modified\.cmake:[0-9]+ \(file\)
+was already present in the glob cache but the directory
+contents have changed during the configuration run.
+Matching glob expressions:
+  CONTENT_LIST_1 at GLOB-error-CONFIGURE_DEPENDS-modified\.cmake:[0-9]+ \(file\)
+  CONTENT_LIST_2 at GLOB-error-CONFIGURE_DEPENDS-modified\.cmake:[0-9]+ \(file\)$

+ 21 - 0
Tests/RunCMake/file/GLOB-error-CONFIGURE_DEPENDS-modified.cmake

@@ -0,0 +1,21 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/first")
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/second")
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/third")
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/first/one" "one")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/second/two" "two")
+file(GLOB_RECURSE CONTENT_LIST_1
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_BINARY_DIR}/test/*"
+  )
+
+file(GLOB_RECURSE CONTENT_LIST_2
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_BINARY_DIR}/test/*"
+  )
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/third/three" "three")
+file(GLOB_RECURSE CONTENT_LIST_3
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_BINARY_DIR}/test/*"
+  )

+ 1 - 0
Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at GLOB-noexp-CONFIGURE_DEPENDS\.cmake:[0-9]+ \(file\):
+  file GLOB requires a glob expression after CONFIGURE_DEPENDS\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/file/GLOB-noexp-CONFIGURE_DEPENDS.cmake

@@ -0,0 +1 @@
+file(GLOB CONTENT_LIST CONFIGURE_DEPENDS)

+ 6 - 0
Tests/RunCMake/file/GLOB-warn-CONFIGURE_DEPENDS-late-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Warning \(dev\) at GLOB-warn-CONFIGURE_DEPENDS-late\.cmake:[0-9]+ \(file\):
+  CONFIGURE_DEPENDS flag was given after a glob expression was already
+  evaluated\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$

+ 11 - 0
Tests/RunCMake/file/GLOB-warn-CONFIGURE_DEPENDS-late.cmake

@@ -0,0 +1,11 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/first")
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/second")
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/first/one" "Hi, Mom!")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/second/two" "Love you!")
+
+file(GLOB CONTENT_LIST
+  "${CMAKE_CURRENT_BINARY_DIR}/test/first/*"
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_BINARY_DIR}/test/second/*"
+  )

+ 13 - 0
Tests/RunCMake/file/GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version-stderr.txt

@@ -0,0 +1,13 @@
+^CMake Warning \(dev\):
+  The detected version of Ninja:
+
+    .*
+
+  is less than the version of Ninja required by CMake for adding restat
+  dependencies to the build\.ninja manifest regeneration target:
+
+    1\.8
+
+  Any pre-check scripts, such as those generated for file\(GLOB
+  CONFIGURE_DEPENDS\), will not be run by Ninja\.
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$

+ 6 - 0
Tests/RunCMake/file/GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version.cmake

@@ -0,0 +1,6 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test/first")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/first/one" "one")
+file(GLOB_RECURSE CONTENT_LIST
+  CONFIGURE_DEPENDS
+  "${CMAKE_CURRENT_BINARY_DIR}/test/*"
+  )

+ 69 - 0
Tests/RunCMake/file/RunCMakeTest.cmake

@@ -43,10 +43,79 @@ run_cmake(GLOB-error-FOLLOW_SYMLINKS)
 run_cmake(GLOB-error-LIST_DIRECTORIES-not-boolean)
 run_cmake(GLOB-error-LIST_DIRECTORIES-no-arg)
 run_cmake(GLOB-error-RELATIVE-no-arg)
+run_cmake(GLOB-error-CONFIGURE_DEPENDS-modified)
+run_cmake(GLOB-noexp-CONFIGURE_DEPENDS)
 run_cmake(GLOB-noexp-LIST_DIRECTORIES)
 run_cmake(GLOB-noexp-RELATIVE)
+run_cmake_command(GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE ${CMAKE_COMMAND} -P
+  ${RunCMake_SOURCE_DIR}/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE.cmake)
 
 if(NOT WIN32 OR CYGWIN)
   run_cmake(GLOB_RECURSE-cyclic-recursion)
   run_cmake(INSTALL-SYMLINK)
 endif()
+
+if(RunCMake_GENERATOR STREQUAL "Ninja")
+  # Detect ninja version so we know what tests can be supported.
+  execute_process(
+    COMMAND "${RunCMake_MAKE_PROGRAM}" --version
+    OUTPUT_VARIABLE ninja_out
+    ERROR_VARIABLE ninja_out
+    RESULT_VARIABLE ninja_res
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+  if(ninja_res EQUAL 0 AND "x${ninja_out}" MATCHES "^x[0-9]+\\.[0-9]+")
+    set(ninja_version "${ninja_out}")
+    message(STATUS "ninja version: ${ninja_version}")
+  else()
+    message(FATAL_ERROR "'ninja --version' reported:\n${ninja_out}")
+  endif()
+
+  if("${ninja_version}" VERSION_LESS 1.8)
+    message(STATUS "Ninja is too old for GLOB CONFIGURE_DEPENDS; expect a warning.")
+  endif()
+endif()
+
+if(RunCMake_GENERATOR STREQUAL "Ninja" AND "${ninja_version}" VERSION_LESS 1.8)
+  run_cmake(GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version)
+else()
+  run_cmake(GLOB-warn-CONFIGURE_DEPENDS-late)
+
+  # Use a single build tree for a few tests without cleaning.
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/GLOB-CONFIGURE_DEPENDS-RerunCMake-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_DEFAULT_stderr ".*")
+  if(RunCMake_GENERATOR STREQUAL "Borland Makefiles" OR
+     RunCMake_GENERATOR STREQUAL "Watcom WMake")
+    set(fs_delay 3)
+  else()
+    set(fs_delay 1.125)
+  endif()
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}/test")
+  set(tf_1  "${RunCMake_TEST_BINARY_DIR}/test/1.txt")
+  file(WRITE "${tf_1}" "1")
+
+  message(STATUS "GLOB-RerunCMake: first configuration...")
+  run_cmake(GLOB-CONFIGURE_DEPENDS-RerunCMake)
+  run_cmake_command(GLOB-CONFIGURE_DEPENDS-RerunCMake-build ${CMAKE_COMMAND} --build .)
+
+  execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${fs_delay})
+  message(STATUS "GLOB-CONFIGURE_DEPENDS-RerunCMake: add another file...")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}/test/sub")
+  set(tf_2  "${RunCMake_TEST_BINARY_DIR}/test/sub/2.txt")
+  file(WRITE "${tf_2}" "2")
+  run_cmake_command(GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_first ${CMAKE_COMMAND} --build .)
+  run_cmake_command(GLOB-CONFIGURE_DEPENDS-RerunCMake-nowork ${CMAKE_COMMAND} --build .)
+
+  execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${fs_delay})
+  message(STATUS "GLOB-CONFIGURE_DEPENDS-RerunCMake: remove first test file...")
+  file(REMOVE "${RunCMake_TEST_BINARY_DIR}/test/1.txt")
+  run_cmake_command(GLOB-CONFIGURE_DEPENDS-RerunCMake-rebuild_second ${CMAKE_COMMAND} --build .)
+  run_cmake_command(GLOB-CONFIGURE_DEPENDS-RerunCMake-nowork ${CMAKE_COMMAND} --build .)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_DEFAULT_stderr)
+endif()

+ 1 - 0
bootstrap

@@ -332,6 +332,7 @@ CMAKE_CXX_SOURCES="\
   cmGlobalCommonGenerator \
   cmGlobalGenerator \
   cmGlobalUnixMakefileGenerator3 \
+  cmGlobVerificationManager \
   cmHexFileConverter \
   cmIfCommand \
   cmIncludeCommand \