1
0
Эх сурвалжийг харах

Merge topic 'additional_clean_files'

b5bf369ec6 Release notes: Add release notes for ADDITIONAL_CLEAN_FILES properties
4e2ce0a67a Doc: Update and deprecate ADDITIONAL_MAKE_CLEAN_FILES directory property
338994d65d Doc: Add documentation for ADDITIONAL_CLEAN_FILES properties
c11f089d73 Tests: Extend MakeClean test to cover ADDITIONAL_CLEAN_FILES
012d599e26 Ninja: Add support for ADDITIONAL_CLEAN_FILES target property
890a1b9dc3 Ninja: Add support for ADDITIONAL_CLEAN_FILES directory property
7b23001f20 Ninja: Add support for additional clean files
d745df4b01 Makefiles: Add support for ADDITIONAL_CLEAN_FILES target property
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !3318
Brad King 6 жил өмнө
parent
commit
66efdbd21a

+ 2 - 0
Help/manual/cmake-properties.7.rst

@@ -61,6 +61,7 @@ Properties on Directories
 .. toctree::
    :maxdepth: 1
 
+   /prop_dir/ADDITIONAL_CLEAN_FILES
    /prop_dir/ADDITIONAL_MAKE_CLEAN_FILES
    /prop_dir/BINARY_DIR
    /prop_dir/BUILDSYSTEM_TARGETS
@@ -102,6 +103,7 @@ Properties on Targets
 .. toctree::
    :maxdepth: 1
 
+   /prop_tgt/ADDITIONAL_CLEAN_FILES
    /prop_tgt/ALIASED_TARGET
    /prop_tgt/ANDROID_ANT_ADDITIONAL_OPTIONS
    /prop_tgt/ANDROID_API

+ 16 - 0
Help/prop_dir/ADDITIONAL_CLEAN_FILES.rst

@@ -0,0 +1,16 @@
+ADDITIONAL_CLEAN_FILES
+----------------------
+
+Additional files to remove during the clean stage.
+
+A :ref:`;-list <CMake Language Lists>` of files that will be removed as a
+part of the ``clean`` target.
+
+Relative paths are allowed and are interpreted relative to the
+current binary directory.
+
+Arguments to :prop_dir:`ADDITIONAL_CLEAN_FILES` may use
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property only works for the :generator:`Ninja` and the Makefile
+generators.  It is ignored on other generators.

+ 13 - 3
Help/prop_dir/ADDITIONAL_MAKE_CLEAN_FILES.rst

@@ -1,7 +1,17 @@
 ADDITIONAL_MAKE_CLEAN_FILES
 ---------------------------
 
-Additional files to clean during the make clean stage.
+.. deprecated:: 3.15
 
-A list of files that will be cleaned as a part of the ``make clean``
-stage.
+  Use :prop_dir:`ADDITIONAL_CLEAN_FILES` instead.
+
+Additional files to remove during the clean stage.
+
+A :ref:`;-list <CMake Language Lists>` of files that will be removed as a
+part of the ``make clean`` target.
+
+Arguments to :prop_dir:`ADDITIONAL_MAKE_CLEAN_FILES` may use
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property only works for the the Makefile generators.
+It is ignored on other generators.

+ 16 - 0
Help/prop_tgt/ADDITIONAL_CLEAN_FILES.rst

@@ -0,0 +1,16 @@
+ADDITIONAL_CLEAN_FILES
+----------------------
+
+Additional files to remove during the clean stage.
+
+A :ref:`;-list <CMake Language Lists>` of files that will be removed as a
+part of the ``clean`` target.
+
+Relative paths are allowed and are interpreted relative to the
+current binary directory.
+
+Arguments to :prop_tgt:`ADDITIONAL_CLEAN_FILES` may use
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property only works for the :generator:`Ninja` and the Makefile
+generators.  It is ignored on other generators.

+ 10 - 0
Help/release/dev/additional_clean_files.rst

@@ -0,0 +1,10 @@
+additional_clean_files
+----------------------
+
+* New target property :prop_tgt:`ADDITIONAL_CLEAN_FILES` and directory property
+  :prop_dir:`ADDITIONAL_CLEAN_FILES` were added.  They allow to register
+  additional files that should be removed during the clean stage.
+
+* Directory property :prop_dir:`ADDITIONAL_MAKE_CLEAN_FILES` was marked
+  deprecated.  The new directory property :prop_dir:`ADDITIONAL_CLEAN_FILES`
+  should be used instead.

+ 95 - 7
Source/cmGlobalNinjaGenerator.cxx

@@ -855,6 +855,11 @@ std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
     .first->second;
 }
 
+void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName)
+{
+  this->AdditionalCleanFiles.emplace(std::move(fileName));
+}
+
 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
   const std::string& commandLine, const std::string& sourceFile)
 {
@@ -1468,8 +1473,80 @@ bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
   return this->NinjaSupportsMultilineDepfile;
 }
 
+bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
+{
+  cmLocalGenerator* lgr = this->LocalGenerators.at(0);
+  std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
+  std::string cleanScriptAbs = lgr->GetBinaryDirectory();
+  cleanScriptAbs += '/';
+  cleanScriptAbs += cleanScriptRel;
+
+  // Check if there are additional files to clean
+  if (this->AdditionalCleanFiles.empty()) {
+    // Remove cmake clean script file if it exists
+    cmSystemTools::RemoveFile(cleanScriptAbs);
+    return false;
+  }
+
+  // Write cmake clean script file
+  {
+    cmGeneratedFileStream fout(cleanScriptAbs);
+    if (!fout) {
+      return false;
+    }
+    fout << "# Additional clean files\n\n";
+    fout << "file(REMOVE_RECURSE\n";
+    for (std::string const& acf : this->AdditionalCleanFiles) {
+      fout << "  "
+           << cmOutputConverter::EscapeForCMake(ConvertToNinjaPath(acf))
+           << '\n';
+    }
+    fout << ")\n";
+  }
+  // Register clean script file
+  lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
+
+  // Write rule
+  {
+    std::string cmd = CMakeCmd();
+    cmd += " -P ";
+    cmd += lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
+                                      cmOutputConverter::SHELL);
+    WriteRule(*this->RulesFileStream, "CLEAN_ADDITIONAL", cmd,
+              "Cleaning additional files...",
+              "Rule for cleaning additional files.",
+              /*depfile=*/"",
+              /*deptype=*/"",
+              /*rspfile=*/"",
+              /*rspcontent*/ "",
+              /*restat=*/"",
+              /*generator=*/false);
+  }
+
+  // Write build
+  {
+    cmNinjaDeps outputs;
+    outputs.emplace_back(
+      this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
+    WriteBuild(os, "Clean additional files.", "CLEAN_ADDITIONAL",
+               /*outputs=*/outputs,
+               /*implicitOuts=*/cmNinjaDeps(),
+               /*explicitDeps=*/cmNinjaDeps(),
+               /*implicitDeps=*/cmNinjaDeps(),
+               /*orderOnlyDeps=*/cmNinjaDeps(),
+               /*variables=*/cmNinjaVars());
+  }
+  // Return success
+  return true;
+}
+
 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
 {
+  // -- Additional clean target
+  bool additionalFiles = WriteTargetCleanAdditional(os);
+
+  // -- Default clean target
+  // Write rule
   WriteRule(*this->RulesFileStream, "CLEAN", NinjaCmd() + " -t clean",
             "Cleaning all built files...",
             "Rule for cleaning all built files.",
@@ -1479,13 +1556,24 @@ void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
             /*rspcontent*/ "",
             /*restat=*/"",
             /*generator=*/false);
-  WriteBuild(os, "Clean all the built files.", "CLEAN",
-             /*outputs=*/cmNinjaDeps(1, this->NinjaOutputPath("clean")),
-             /*implicitOuts=*/cmNinjaDeps(),
-             /*explicitDeps=*/cmNinjaDeps(),
-             /*implicitDeps=*/cmNinjaDeps(),
-             /*orderOnlyDeps=*/cmNinjaDeps(),
-             /*variables=*/cmNinjaVars());
+
+  // Write build
+  {
+    cmNinjaDeps explicitDeps;
+    if (additionalFiles) {
+      explicitDeps.emplace_back(
+        this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
+    }
+    cmNinjaDeps outputs;
+    outputs.emplace_back(this->NinjaOutputPath(this->GetCleanTargetName()));
+    WriteBuild(os, "Clean all the built files.", "CLEAN",
+               /*outputs=*/outputs,
+               /*implicitOuts=*/cmNinjaDeps(),
+               /*explicitDeps=*/explicitDeps,
+               /*implicitDeps=*/cmNinjaDeps(),
+               /*orderOnlyDeps=*/cmNinjaDeps(),
+               /*variables=*/cmNinjaVars());
+  }
 }
 
 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)

+ 9 - 0
Source/cmGlobalNinjaGenerator.h

@@ -258,6 +258,13 @@ public:
   };
   MapToNinjaPathImpl MapToNinjaPath() { return MapToNinjaPathImpl(this); }
 
+  // -- Additional clean files
+  void AddAdditionalCleanFile(std::string fileName);
+  const char* GetAdditionalCleanTargetName() const
+  {
+    return "CMakeFiles/clean.additional";
+  }
+
   void AddCXXCompileCommand(const std::string& commandLine,
                             const std::string& sourceFile);
 
@@ -399,6 +406,7 @@ private:
   void WriteBuiltinTargets(std::ostream& os);
   void WriteTargetAll(std::ostream& os);
   void WriteTargetRebuildManifest(std::ostream& os);
+  bool WriteTargetCleanAdditional(std::ostream& os);
   void WriteTargetClean(std::ostream& os);
   void WriteTargetHelp(std::ostream& os);
 
@@ -471,6 +479,7 @@ private:
   std::string OutputPathPrefix;
   std::string TargetAll;
   std::string CMakeCacheFile;
+  std::set<std::string> AdditionalCleanFiles;
 };
 
 #endif // ! cmGlobalNinjaGenerator_h

+ 25 - 0
Source/cmLocalNinjaGenerator.cxx

@@ -14,6 +14,7 @@
 #include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
@@ -94,6 +95,7 @@ void cmLocalNinjaGenerator::Generate()
   }
 
   this->WriteCustomCommandBuildStatements();
+  this->AdditionalCleanFiles();
 }
 
 // TODO: Picked up from cmLocalUnixMakefileGenerator3.  Refactor it.
@@ -598,3 +600,26 @@ std::string cmLocalNinjaGenerator::MakeCustomLauncher(
 
   return launcher;
 }
+
+void cmLocalNinjaGenerator::AdditionalCleanFiles()
+{
+  if (const char* prop_value =
+        this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    std::vector<std::string> cleanFiles;
+    {
+      cmGeneratorExpression ge;
+      auto cge = ge.Parse(prop_value);
+      cmSystemTools::ExpandListArgument(
+        cge->Evaluate(this,
+                      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")),
+        cleanFiles);
+    }
+    std::string const& binaryDir = this->GetCurrentBinaryDirectory();
+    cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
+    for (std::string const& cleanFile : cleanFiles) {
+      // Support relative paths
+      gg->AddAdditionalCleanFile(
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir));
+    }
+  }
+}

+ 2 - 0
Source/cmLocalNinjaGenerator.h

@@ -105,6 +105,8 @@ private:
                                  std::string const& customStep,
                                  cmGeneratorTarget const* target) const;
 
+  void AdditionalCleanFiles();
+
   std::string HomeRelativeOutputPath;
 
   typedef std::map<cmCustomCommand const*, std::set<cmGeneratorTarget*>>

+ 1 - 1
Source/cmLocalUnixMakefileGenerator3.cxx

@@ -1040,7 +1040,7 @@ void cmLocalUnixMakefileGenerator3::AppendCustomCommand(
 }
 
 void cmLocalUnixMakefileGenerator3::AppendCleanCommand(
-  std::vector<std::string>& commands, const std::vector<std::string>& files,
+  std::vector<std::string>& commands, const std::set<std::string>& files,
   cmGeneratorTarget* target, const char* filename)
 {
   std::string currentBinDir = this->GetCurrentBinaryDirectory();

+ 1 - 1
Source/cmLocalUnixMakefileGenerator3.h

@@ -224,7 +224,7 @@ protected:
                            bool echo_comment = false,
                            std::ostream* content = nullptr);
   void AppendCleanCommand(std::vector<std::string>& commands,
-                          const std::vector<std::string>& files,
+                          const std::set<std::string>& files,
                           cmGeneratorTarget* target,
                           const char* filename = nullptr);
 

+ 4 - 5
Source/cmMakefileExecutableTargetGenerator.cxx

@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <memory> // IWYU pragma: keep
+#include <set>
 #include <sstream>
 #include <string>
 #include <utility>
@@ -291,8 +292,7 @@ void cmMakefileExecutableTargetGenerator::WriteDeviceExecutableRule(
   this->WriteTargetDriverRule(targetOutputReal, relink);
 
   // Clean all the possible executable names and symlinks.
-  this->CleanFiles.insert(this->CleanFiles.end(), exeCleanFiles.begin(),
-                          exeCleanFiles.end());
+  this->CleanFiles.insert(exeCleanFiles.begin(), exeCleanFiles.end());
 #else
   static_cast<void>(relink);
 #endif
@@ -480,7 +480,7 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
   // List the PDB for cleaning only when the whole target is
   // cleaned.  We do not want to delete the .pdb file just before
   // linking the target.
-  this->CleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+  this->CleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
     this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathPDB));
 
   // Add the pre-build and pre-link rules building but not when relinking.
@@ -695,6 +695,5 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
   this->WriteTargetDriverRule(targetFullPath, relink);
 
   // Clean all the possible executable names and symlinks.
-  this->CleanFiles.insert(this->CleanFiles.end(), exeCleanFiles.begin(),
-                          exeCleanFiles.end());
+  this->CleanFiles.insert(exeCleanFiles.begin(), exeCleanFiles.end());
 }

+ 13 - 14
Source/cmMakefileLibraryTargetGenerator.cxx

@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <memory> // IWYU pragma: keep
+#include <set>
 #include <sstream>
 #include <stddef.h>
 #include <utility>
@@ -304,8 +305,8 @@ void cmMakefileLibraryTargetGenerator::WriteDeviceLibraryRules(
       commands, buildEcho, cmLocalUnixMakefileGenerator3::EchoLink, &progress);
   }
   // Clean files associated with this library.
-  std::vector<std::string> libCleanFiles;
-  libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+  std::set<std::string> libCleanFiles;
+  libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
     this->LocalGenerator->GetCurrentBinaryDirectory(), targetOutputReal));
 
   // Determine whether a link script will be used.
@@ -412,8 +413,7 @@ void cmMakefileLibraryTargetGenerator::WriteDeviceLibraryRules(
     this->LocalGenerator->SetLinkScriptShell(false);
 
     // Clean all the possible library names and symlinks.
-    this->CleanFiles.insert(this->CleanFiles.end(), libCleanFiles.begin(),
-                            libCleanFiles.end());
+    this->CleanFiles.insert(libCleanFiles.begin(), libCleanFiles.end());
   }
 
   std::vector<std::string> commands1;
@@ -593,8 +593,8 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
   }
 
   // Clean files associated with this library.
-  std::vector<std::string> libCleanFiles;
-  libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+  std::set<std::string> libCleanFiles;
+  libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
     this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathReal));
 
   std::vector<std::string> commands1;
@@ -611,22 +611,22 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
   }
 
   if (this->TargetNames.Output != this->TargetNames.Real) {
-    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+    libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
       this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPath));
   }
   if (this->TargetNames.SharedObject != this->TargetNames.Real &&
       this->TargetNames.SharedObject != this->TargetNames.Output) {
-    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+    libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
       this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathSO));
   }
   if (!this->TargetNames.ImportLibrary.empty()) {
-    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+    libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
       this->LocalGenerator->GetCurrentBinaryDirectory(),
       targetFullPathImport));
     std::string implib;
     if (this->GeneratorTarget->GetImplibGNUtoMS(
           this->ConfigName, targetFullPathImport, implib)) {
-      libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+      libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
         this->LocalGenerator->GetCurrentBinaryDirectory(), implib));
     }
   }
@@ -634,14 +634,14 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
   // List the PDB for cleaning only when the whole target is
   // cleaned.  We do not want to delete the .pdb file just before
   // linking the target.
-  this->CleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+  this->CleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
     this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathPDB));
 
 #ifdef _WIN32
   // There may be a manifest file for this target.  Add it to the
   // clean set just in case.
   if (this->GeneratorTarget->GetType() != cmStateEnums::STATIC_LIBRARY) {
-    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
+    libCleanFiles.insert(this->LocalGenerator->MaybeConvertToRelativePath(
       this->LocalGenerator->GetCurrentBinaryDirectory(),
       targetFullPath + ".manifest"));
   }
@@ -992,6 +992,5 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
   this->WriteTargetDriverRule(targetFullPath, relink);
 
   // Clean all the possible library names and symlinks.
-  this->CleanFiles.insert(this->CleanFiles.end(), libCleanFiles.begin(),
-                          libCleanFiles.end());
+  this->CleanFiles.insert(libCleanFiles.begin(), libCleanFiles.end());
 }

+ 46 - 15
Source/cmMakefileTargetGenerator.cxx

@@ -147,21 +147,53 @@ void cmMakefileTargetGenerator::CreateRuleFile()
 
 void cmMakefileTargetGenerator::WriteTargetBuildRules()
 {
+  // -- Write the custom commands for this target
+
   const std::string& config =
     this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
 
-  // write the custom commands for this target
-  // Look for files registered for cleaning in this directory.
-  if (const char* additional_clean_files =
-        this->Makefile->GetProperty("ADDITIONAL_MAKE_CLEAN_FILES")) {
+  // Evaluates generator expressions and expands prop_value
+  auto evaluatedFiles =
+    [this, &config](const char* prop_value) -> std::vector<std::string> {
+    std::vector<std::string> files;
     cmGeneratorExpression ge;
-    std::unique_ptr<cmCompiledGeneratorExpression> cge =
-      ge.Parse(additional_clean_files);
-
+    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(prop_value);
     cmSystemTools::ExpandListArgument(
       cge->Evaluate(this->LocalGenerator, config, false, this->GeneratorTarget,
                     nullptr, nullptr),
-      this->CleanFiles);
+      files);
+    return files;
+  };
+
+  // Look for additional files registered for cleaning in this directory.
+  if (const char* prop_value =
+        this->Makefile->GetProperty("ADDITIONAL_MAKE_CLEAN_FILES")) {
+    std::vector<std::string> const files = evaluatedFiles(prop_value);
+    this->CleanFiles.insert(files.begin(), files.end());
+  }
+
+  // Look for additional files registered for cleaning in this directory.
+  if (const char* prop_value =
+        this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    std::vector<std::string> const files = evaluatedFiles(prop_value);
+    // For relative path support
+    std::string const& binaryDir =
+      this->LocalGenerator->GetCurrentBinaryDirectory();
+    for (std::string const& cfl : files) {
+      this->CleanFiles.insert(cmSystemTools::CollapseFullPath(cfl, binaryDir));
+    }
+  }
+
+  // Look for additional files registered for cleaning in this target.
+  if (const char* prop_value =
+        this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    std::vector<std::string> const files = evaluatedFiles(prop_value);
+    // For relative path support
+    std::string const& binaryDir =
+      this->LocalGenerator->GetCurrentBinaryDirectory();
+    for (std::string const& cfl : files) {
+      this->CleanFiles.insert(cmSystemTools::CollapseFullPath(cfl, binaryDir));
+    }
   }
 
   // add custom commands to the clean rules?
@@ -181,13 +213,13 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
     if (clean) {
       const std::vector<std::string>& outputs = ccg.GetOutputs();
       for (std::string const& output : outputs) {
-        this->CleanFiles.push_back(
+        this->CleanFiles.insert(
           this->LocalGenerator->MaybeConvertToRelativePath(currentBinDir,
                                                            output));
       }
       const std::vector<std::string>& byproducts = ccg.GetByproducts();
       for (std::string const& byproduct : byproducts) {
-        this->CleanFiles.push_back(
+        this->CleanFiles.insert(
           this->LocalGenerator->MaybeConvertToRelativePath(currentBinDir,
                                                            byproduct));
       }
@@ -211,7 +243,7 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
     for (const auto& be : buildEventCommands) {
       const std::vector<std::string>& byproducts = be.GetByproducts();
       for (std::string const& byproduct : byproducts) {
-        this->CleanFiles.push_back(
+        this->CleanFiles.insert(
           this->LocalGenerator->MaybeConvertToRelativePath(currentBinDir,
                                                            byproduct));
       }
@@ -350,7 +382,7 @@ void cmMakefileTargetGenerator::MacOSXContentGeneratorType::operator()(
   std::string output = macdir;
   output += "/";
   output += cmSystemTools::GetFilenameName(input);
-  this->Generator->CleanFiles.push_back(
+  this->Generator->CleanFiles.insert(
     this->Generator->LocalGenerator->MaybeConvertToRelativePath(
       this->Generator->LocalGenerator->GetCurrentBinaryDirectory(), output));
   output = this->Generator->LocalGenerator->MaybeConvertToRelativePath(
@@ -415,7 +447,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
 
   // Save this in the target's list of object files.
   this->Objects.push_back(obj);
-  this->CleanFiles.push_back(obj);
+  this->CleanFiles.insert(obj);
 
   // TODO: Remove
   // std::string relativeObj
@@ -804,8 +836,7 @@ void cmMakefileTargetGenerator::WriteObjectBuildFile(
   if (const char* extra_outputs_str = source.GetProperty("OBJECT_OUTPUTS")) {
     // Register these as extra files to clean.
     cmSystemTools::ExpandListArgument(extra_outputs_str, outputs);
-    this->CleanFiles.insert(this->CleanFiles.end(), outputs.begin() + 1,
-                            outputs.end());
+    this->CleanFiles.insert(outputs.begin() + 1, outputs.end());
   }
 
   // Write the rule.

+ 1 - 1
Source/cmMakefileTargetGenerator.h

@@ -210,7 +210,7 @@ protected:
   cmGeneratedFileStream* InfoFileStream;
 
   // files to clean
-  std::vector<std::string> CleanFiles;
+  std::set<std::string> CleanFiles;
 
   // objects used by this target
   std::vector<std::string> Objects;

+ 2 - 0
Source/cmNinjaNormalTargetGenerator.cxx

@@ -83,6 +83,8 @@ void cmNinjaNormalTargetGenerator::Generate()
     this->WriteDeviceLinkStatement();
     this->WriteLinkStatement();
   }
+
+  this->AdditionalCleanFiles();
 }
 
 void cmNinjaNormalTargetGenerator::WriteLanguagesRules()

+ 25 - 0
Source/cmNinjaTargetGenerator.cxx

@@ -1310,6 +1310,31 @@ void cmNinjaTargetGenerator::ExportObjectCompileCommand(
   this->GetGlobalGenerator()->AddCXXCompileCommand(cmdLine, sourceFileName);
 }
 
+void cmNinjaTargetGenerator::AdditionalCleanFiles()
+{
+  if (const char* prop_value =
+        this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    cmLocalNinjaGenerator* lg = this->LocalGenerator;
+    std::vector<std::string> cleanFiles;
+    {
+      cmGeneratorExpression ge;
+      auto cge = ge.Parse(prop_value);
+      cmSystemTools::ExpandListArgument(
+        cge->Evaluate(lg,
+                      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"),
+                      false, this->GeneratorTarget, nullptr, nullptr),
+        cleanFiles);
+    }
+    std::string const& binaryDir = lg->GetCurrentBinaryDirectory();
+    cmGlobalNinjaGenerator* gg = lg->GetGlobalNinjaGenerator();
+    for (std::string const& cleanFile : cleanFiles) {
+      // Support relative paths
+      gg->AddAdditionalCleanFile(
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir));
+    }
+  }
+}
+
 void cmNinjaTargetGenerator::EnsureDirectoryExists(
   const std::string& path) const
 {

+ 2 - 0
Source/cmNinjaTargetGenerator.h

@@ -134,6 +134,8 @@ protected:
     std::string const& objectFileDir, std::string const& flags,
     std::string const& defines, std::string const& includes);
 
+  void AdditionalCleanFiles();
+
   cmNinjaDeps GetObjects() const { return this->Objects; }
 
   void EnsureDirectoryExists(const std::string& dir) const;

+ 2 - 1
Tests/CMakeLists.txt

@@ -2007,7 +2007,8 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
 
   ADD_TEST_MACRO(CheckCompilerRelatedVariables CheckCompilerRelatedVariables)
 
-  if("${CMAKE_GENERATOR}" MATCHES "Makefile")
+  if("${CMAKE_GENERATOR}" MATCHES "Makefile" OR
+     "${CMAKE_GENERATOR}" MATCHES "Ninja")
     add_test(MakeClean ${CMAKE_CTEST_COMMAND}
       --build-and-test
       "${CMake_SOURCE_DIR}/Tests/MakeClean"

+ 84 - 28
Tests/MakeClean/ToClean/CMakeLists.txt

@@ -1,42 +1,98 @@
-cmake_minimum_required(VERSION 2.6)
+cmake_minimum_required(VERSION 3.14)
 project(ToClean)
 
-# Build a simple project.
-add_executable(toclean toclean.cxx)
+# Utility variables
+set(TSD ${ToClean_SOURCE_DIR})
+set(TBD ${ToClean_BINARY_DIR})
+set(CLEAN_FILE_CONTENT "File registered for cleaning.\n")
 
-# List some build-time-generated files.
-set(TOCLEAN_FILES ${TOCLEAN_FILES}
-  "${ToClean_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toclean.dir/toclean.cxx${CMAKE_CXX_OUTPUT_EXTENSION}")
+# Lists build-time-generated files that should be cleaned away
+set(TOCLEAN_FILES)
 
-# Create a file that must be registered for cleaning.
-file(WRITE "${ToClean_BINARY_DIR}/Registered.txt"
-  "File registered for cleaning.\n")
-set_directory_properties(PROPERTIES
-  ADDITIONAL_MAKE_CLEAN_FILES "${ToClean_BINARY_DIR}/Registered.txt")
-set(TOCLEAN_FILES ${TOCLEAN_FILES} "${ToClean_BINARY_DIR}/Registered.txt")
+# Build a simple project whose compiled objects should be cleaned.
+add_executable(toclean toclean.cxx)
+list(APPEND TOCLEAN_FILES
+  "${TBD}${CMAKE_FILES_DIRECTORY}/toclean.dir/toclean.cxx${CMAKE_CXX_OUTPUT_EXTENSION}")
 
 # Create a custom command whose output should be cleaned.
-add_custom_command(OUTPUT ${ToClean_BINARY_DIR}/generated.txt
-  DEPENDS ${ToClean_SOURCE_DIR}/toclean.cxx
+set(CustomCommandFile "${TBD}/CustomCommandFile.txt")
+add_custom_command(OUTPUT ${CustomCommandFile}
+  DEPENDS ${TSD}/toclean.cxx
   COMMAND ${CMAKE_COMMAND}
-  ARGS -E copy ${ToClean_SOURCE_DIR}/toclean.cxx
-               ${ToClean_BINARY_DIR}/generated.txt
-  )
-add_custom_target(generate ALL DEPENDS ${ToClean_BINARY_DIR}/generated.txt)
-set(TOCLEAN_FILES ${TOCLEAN_FILES} "${ToClean_BINARY_DIR}/generated.txt")
+  ARGS -E copy ${TSD}/toclean.cxx ${CustomCommandFile})
+add_custom_target(generate ALL DEPENDS ${CustomCommandFile})
+list(APPEND TOCLEAN_FILES ${CustomCommandFile})
+
+
+### Tests ADDITIONAL_MAKE_CLEAN_FILES directory property
+if("${CMAKE_GENERATOR}" MATCHES "Makefile")
+  # Create a file that must be registered for cleaning.
+  set(MakeDirPropFile "${TBD}/MakeDirPropFile.txt")
+  file(WRITE "${MakeDirPropFile}" ${CLEAN_FILE_CONTENT})
+  set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${MakeDirPropFile}")
+  list(APPEND TOCLEAN_FILES "${MakeDirPropFile}")
+
+  # Create a custom command whose output should be cleaned, but whose name
+  # is not known until generate-time
+  set(MakeDirPropExpFileRel "MakeDirProp_copy${CMAKE_EXECUTABLE_SUFFIX}")
+  set(MakeDirPropExpFile "$<TARGET_FILE_DIR:toclean>/${MakeDirPropExpFileRel}")
+  add_custom_command(TARGET toclean POST_BUILD
+    COMMAND ${CMAKE_COMMAND}
+    ARGS -E copy $<TARGET_FILE:toclean> ${MakeDirPropExpFile})
+  set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${MakeDirPropExpFile})
+  list(APPEND TOCLEAN_FILES "${TBD}/${MakeDirPropExpFileRel}")
+endif()
+
+
+### Tests ADDITIONAL_CLEAN_FILES directory property
+
+# Register a file path relative to the build directory
+set(DirPropFileRel "DirPropFileRel.txt")
+file(WRITE "${TBD}/${DirPropFileRel}" ${CLEAN_FILE_CONTENT})
+set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES ${DirPropFileRel})
+list(APPEND TOCLEAN_FILES "${TBD}/${DirPropFileRel}")
+
+# Register an absolute file path
+set(DirPropFileAbs "${TBD}/DirPropFileAbs.txt")
+file(WRITE "${DirPropFileAbs}" ${CLEAN_FILE_CONTENT})
+set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${DirPropFileAbs})
+list(APPEND TOCLEAN_FILES "${DirPropFileAbs}")
 
 # Create a custom command whose output should be cleaned, but whose name
 # is not known until generate-time
-set(copied_exe "$<TARGET_FILE_DIR:toclean>/toclean_copy${CMAKE_EXECUTABLE_SUFFIX}")
+set(DirPropExpFileRel "DirProp_copy${CMAKE_EXECUTABLE_SUFFIX}")
+set(DirPropExpFile "$<TARGET_FILE_DIR:toclean>/${DirPropExpFileRel}")
 add_custom_command(TARGET toclean POST_BUILD
   COMMAND ${CMAKE_COMMAND}
-  ARGS -E copy $<TARGET_FILE:toclean>
-               ${copied_exe}
-  )
-set_property(DIRECTORY APPEND PROPERTY
-  ADDITIONAL_MAKE_CLEAN_FILES ${copied_exe})
-list(APPEND TOCLEAN_FILES "${ToClean_BINARY_DIR}/toclean_copy${CMAKE_EXECUTABLE_SUFFIX}")
+  ARGS -E copy $<TARGET_FILE:toclean> ${DirPropExpFile})
+set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${DirPropExpFile})
+list(APPEND TOCLEAN_FILES "${TBD}/${DirPropExpFileRel}")
+
+
+### Tests ADDITIONAL_CLEAN_FILES target property
+
+# Register a file path relative to the build directory
+set(TgtPropFileRel "TargetPropFileRel.txt")
+file(WRITE "${TBD}/${TgtPropFileRel}" ${CLEAN_FILE_CONTENT})
+set_target_properties(toclean PROPERTIES ADDITIONAL_CLEAN_FILES ${TgtPropFileRel})
+list(APPEND TOCLEAN_FILES "${TBD}/${TgtPropFileRel}")
+
+# Register an absolute file path
+set(TgtPropFileAbs "${TBD}/TargetPropFileAbs.txt")
+file(WRITE "${TgtPropFileAbs}" ${CLEAN_FILE_CONTENT})
+set_property(TARGET toclean APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${TgtPropFileAbs})
+list(APPEND TOCLEAN_FILES "${TgtPropFileAbs}")
+
+# Create a custom command whose output should be cleaned, but whose name
+# is not known until generate-time
+set(TgtPropExpFileRel "TgtProp_copy${CMAKE_EXECUTABLE_SUFFIX}")
+set(TgtPropExpFile "$<TARGET_FILE_DIR:toclean>/${TgtPropExpFileRel}")
+add_custom_command(TARGET toclean POST_BUILD
+  COMMAND ${CMAKE_COMMAND}
+  ARGS -E copy $<TARGET_FILE:toclean> ${TgtPropExpFile})
+set_property(TARGET toclean APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${TgtPropExpFile})
+list(APPEND TOCLEAN_FILES "${TBD}/${TgtPropExpFileRel}")
+
 
 # Configure a file listing these build-time-generated files.
-configure_file(${ToClean_SOURCE_DIR}/ToCleanFiles.cmake.in
-               ${ToClean_BINARY_DIR}/ToCleanFiles.cmake @ONLY)
+configure_file(${TSD}/ToCleanFiles.cmake.in ${TBD}/ToCleanFiles.cmake @ONLY)

+ 1 - 1
Tests/MakeClean/check_clean.c.in

@@ -18,7 +18,7 @@ int main()
     if(pf)
       {
       fclose(pf);
-      fprintf(stderr, "File \"%s\" exists!", *f);
+      fprintf(stderr, "File \"%s\" still exists!\n", *f);
       result = 1;
       }
     }