This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "CMake".

The branch, master has been updated
       via  32a569e8771311348306f15644bfa048ee183892 (commit)
       via  e2e8f6b132967844a8bafc866d6cb5c90342af08 (commit)
      from  7bc03aa6e24a7120497b639148268d52df3d691d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=32a569e8771311348306f15644bfa048ee183892
commit 32a569e8771311348306f15644bfa048ee183892
Merge: 7bc03aa e2e8f6b
Author:     Brad King <brad.k...@kitware.com>
AuthorDate: Thu Mar 14 15:12:49 2019 +0000
Commit:     Kitware Robot <kwro...@kitware.com>
CommitDate: Thu Mar 14 11:13:33 2019 -0400

    Merge topic 'refactor_cmfilecopier'
    
    e2e8f6b132 cmFileCommand: Factor out cmFileCopier and cmFileInstaller
    
    Acked-by: Kitware Robot <kwro...@kitware.com>
    Merge-request: !2664


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=e2e8f6b132967844a8bafc866d6cb5c90342af08
commit e2e8f6b132967844a8bafc866d6cb5c90342af08
Author:     Bryon Bean <bryon.b...@kitware.com>
AuthorDate: Wed Mar 13 11:51:12 2019 -0400
Commit:     Brad King <brad.k...@kitware.com>
CommitDate: Wed Mar 13 14:06:32 2019 -0400

    cmFileCommand: Factor out cmFileCopier and cmFileInstaller
    
    Split these classes out into their own sources.

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 2db276d..696826f 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -224,6 +224,10 @@ set(SRCS
   cmFileAPICodemodel.h
   cmFileAPICMakeFiles.cxx
   cmFileAPICMakeFiles.h
+  cmFileCopier.cxx
+  cmFileCopier.h
+  cmFileInstaller.cxx
+  cmFileInstaller.h
   cmFileLock.cxx
   cmFileLock.h
   cmFileLockPool.cxx
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index a2018c3..d2bc851 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -3,7 +3,6 @@
 #include "cmFileCommand.h"
 
 #include "cm_kwiml.h"
-#include "cmsys/Directory.hxx"
 #include "cmsys/FStream.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
@@ -16,20 +15,18 @@
 #include <sstream>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <utility>
 #include <vector>
 
 #include "cmAlgorithms.h"
 #include "cmCommandArgumentsHelper.h"
 #include "cmCryptoHash.h"
-#include "cmFSPermissions.h"
+#include "cmFileCopier.h"
+#include "cmFileInstaller.h"
 #include "cmFileLockPool.h"
-#include "cmFileTimeComparison.h"
 #include "cmGeneratorExpression.h"
 #include "cmGlobalGenerator.h"
 #include "cmHexFileConverter.h"
-#include "cmInstallType.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -56,8 +53,6 @@
 
 class cmSystemToolsFileTime;
 
-using namespace cmFSPermissions;
-
 #if defined(_WIN32)
 // libcurl doesn't support file:// urls for unicode filenames on Windows.
 // Convert string from UTF-8 to ACP if this is a file:// URL.
@@ -1058,1085 +1053,12 @@ bool cmFileCommand::HandleDifferentCommand(
   return true;
 }
 
-// File installation helper class.
-struct cmFileCopier
-{
-  cmFileCopier(cmFileCommand* command, const char* name = "COPY")
-    : FileCommand(command)
-    , Makefile(command->GetMakefile())
-    , Name(name)
-    , Always(false)
-    , MatchlessFiles(true)
-    , FilePermissions(0)
-    , DirPermissions(0)
-    , CurrentMatchRule(nullptr)
-    , UseGivenPermissionsFile(false)
-    , UseGivenPermissionsDir(false)
-    , UseSourcePermissions(true)
-    , Doing(DoingNone)
-  {
-  }
-  virtual ~cmFileCopier() = default;
-
-  bool Run(std::vector<std::string> const& args);
-
-protected:
-  cmFileCommand* FileCommand;
-  cmMakefile* Makefile;
-  const char* Name;
-  bool Always;
-  cmFileTimeComparison FileTimes;
-
-  // Whether to install a file not matching any expression.
-  bool MatchlessFiles;
-
-  // Permissions for files and directories installed by this object.
-  mode_t FilePermissions;
-  mode_t DirPermissions;
-
-  // Properties set by pattern and regex match rules.
-  struct MatchProperties
-  {
-    bool Exclude = false;
-    mode_t Permissions = 0;
-  };
-  struct MatchRule
-  {
-    cmsys::RegularExpression Regex;
-    MatchProperties Properties;
-    std::string RegexString;
-    MatchRule(std::string const& regex)
-      : Regex(regex)
-      , RegexString(regex)
-    {
-    }
-  };
-  std::vector<MatchRule> MatchRules;
-
-  // Get the properties from rules matching this input file.
-  MatchProperties CollectMatchProperties(const std::string& file)
-  {
-// Match rules are case-insensitive on some platforms.
-#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
-    const std::string file_to_match = cmSystemTools::LowerCase(file);
-#else
-    const std::string& file_to_match = file;
-#endif
-
-    // Collect properties from all matching rules.
-    bool matched = false;
-    MatchProperties result;
-    for (MatchRule& mr : this->MatchRules) {
-      if (mr.Regex.find(file_to_match)) {
-        matched = true;
-        result.Exclude |= mr.Properties.Exclude;
-        result.Permissions |= mr.Properties.Permissions;
-      }
-    }
-    if (!matched && !this->MatchlessFiles) {
-      result.Exclude = !cmSystemTools::FileIsDirectory(file);
-    }
-    return result;
-  }
-
-  bool SetPermissions(const std::string& toFile, mode_t permissions)
-  {
-    if (permissions) {
-#ifdef WIN32
-      if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
-        // Store the mode in an NTFS alternate stream.
-        std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
-
-        // Writing to an NTFS alternate stream changes the modification
-        // time, so we need to save and restore its original value.
-        cmSystemToolsFileTime* file_time_orig = cmSystemTools::FileTimeNew();
-        cmSystemTools::FileTimeGet(toFile, file_time_orig);
-
-        cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
-
-        if (permissionStream) {
-          permissionStream << std::oct << permissions << std::endl;
-        }
-
-        permissionStream.close();
-
-        cmSystemTools::FileTimeSet(toFile, file_time_orig);
-
-        cmSystemTools::FileTimeDelete(file_time_orig);
-      }
-#endif
-
-      if (!cmSystemTools::SetPermissions(toFile, permissions)) {
-        std::ostringstream e;
-        e << this->Name << " cannot set permissions on \"" << toFile << "\"";
-        this->FileCommand->SetError(e.str());
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // Translate an argument to a permissions bit.
-  bool CheckPermissions(std::string const& arg, mode_t& permissions)
-  {
-    if (!cmFSPermissions::stringToModeT(arg, permissions)) {
-      std::ostringstream e;
-      e << this->Name << " given invalid permission \"" << arg << "\".";
-      this->FileCommand->SetError(e.str());
-      return false;
-    }
-    return true;
-  }
-
-  bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
-  bool InstallFile(const std::string& fromFile, const std::string& toFile,
-                   MatchProperties match_properties);
-  bool InstallDirectory(const std::string& source,
-                        const std::string& destination,
-                        MatchProperties match_properties);
-  virtual bool Install(const std::string& fromFile, const std::string& toFile);
-  virtual std::string const& ToName(std::string const& fromName)
-  {
-    return fromName;
-  }
-
-  enum Type
-  {
-    TypeFile,
-    TypeDir,
-    TypeLink
-  };
-  virtual void ReportCopy(const std::string&, Type, bool) {}
-  virtual bool ReportMissing(const std::string& fromFile)
-  {
-    // The input file does not exist and installation is not optional.
-    std::ostringstream e;
-    e << this->Name << " cannot find \"" << fromFile << "\".";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  MatchRule* CurrentMatchRule;
-  bool UseGivenPermissionsFile;
-  bool UseGivenPermissionsDir;
-  bool UseSourcePermissions;
-  std::string Destination;
-  std::string FilesFromDir;
-  std::vector<std::string> Files;
-  int Doing;
-
-  virtual bool Parse(std::vector<std::string> const& args);
-  enum
-  {
-    DoingNone,
-    DoingError,
-    DoingDestination,
-    DoingFilesFromDir,
-    DoingFiles,
-    DoingPattern,
-    DoingRegex,
-    DoingPermissionsFile,
-    DoingPermissionsDir,
-    DoingPermissionsMatch,
-    DoingLast1
-  };
-  virtual bool CheckKeyword(std::string const& arg);
-  virtual bool CheckValue(std::string const& arg);
-
-  void NotBeforeMatch(std::string const& arg)
-  {
-    std::ostringstream e;
-    e << "option " << arg << " may not appear before PATTERN or REGEX.";
-    this->FileCommand->SetError(e.str());
-    this->Doing = DoingError;
-  }
-  void NotAfterMatch(std::string const& arg)
-  {
-    std::ostringstream e;
-    e << "option " << arg << " may not appear after PATTERN or REGEX.";
-    this->FileCommand->SetError(e.str());
-    this->Doing = DoingError;
-  }
-  virtual void DefaultFilePermissions()
-  {
-    // Use read/write permissions.
-    this->FilePermissions = 0;
-    this->FilePermissions |= mode_owner_read;
-    this->FilePermissions |= mode_owner_write;
-    this->FilePermissions |= mode_group_read;
-    this->FilePermissions |= mode_world_read;
-  }
-  virtual void DefaultDirectoryPermissions()
-  {
-    // Use read/write/executable permissions.
-    this->DirPermissions = 0;
-    this->DirPermissions |= mode_owner_read;
-    this->DirPermissions |= mode_owner_write;
-    this->DirPermissions |= mode_owner_execute;
-    this->DirPermissions |= mode_group_read;
-    this->DirPermissions |= mode_group_execute;
-    this->DirPermissions |= mode_world_read;
-    this->DirPermissions |= mode_world_execute;
-  }
-
-  bool GetDefaultDirectoryPermissions(mode_t** mode)
-  {
-    // check if default dir creation permissions were set
-    const char* default_dir_install_permissions =
-      this->Makefile->GetDefinition(
-        "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
-    if (default_dir_install_permissions && *default_dir_install_permissions) {
-      std::vector<std::string> items;
-      cmSystemTools::ExpandListArgument(default_dir_install_permissions,
-                                        items);
-      for (const auto& arg : items) {
-        if (!this->CheckPermissions(arg, **mode)) {
-          std::ostringstream e;
-          e << this->FileCommand->GetError()
-            << " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS "
-               "variable.";
-          this->FileCommand->SetError(e.str());
-          return false;
-        }
-      }
-    } else {
-      *mode = nullptr;
-    }
-
-    return true;
-  }
-};
-
-bool cmFileCopier::Parse(std::vector<std::string> const& args)
-{
-  this->Doing = DoingFiles;
-  for (unsigned int i = 1; i < args.size(); ++i) {
-    // Check this argument.
-    if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
-      std::ostringstream e;
-      e << "called with unknown argument \"" << args[i] << "\".";
-      this->FileCommand->SetError(e.str());
-      return false;
-    }
-
-    // Quit if an argument is invalid.
-    if (this->Doing == DoingError) {
-      return false;
-    }
-  }
-
-  // Require a destination.
-  if (this->Destination.empty()) {
-    std::ostringstream e;
-    e << this->Name << " given no DESTINATION";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  // If file permissions were not specified set default permissions.
-  if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
-    this->DefaultFilePermissions();
-  }
-
-  // If directory permissions were not specified set default permissions.
-  if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
-    this->DefaultDirectoryPermissions();
-  }
-
-  return true;
-}
-
-bool cmFileCopier::CheckKeyword(std::string const& arg)
-{
-  if (arg == "DESTINATION") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingDestination;
-    }
-  } else if (arg == "FILES_FROM_DIR") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingFilesFromDir;
-    }
-  } else if (arg == "PATTERN") {
-    this->Doing = DoingPattern;
-  } else if (arg == "REGEX") {
-    this->Doing = DoingRegex;
-  } else if (arg == "EXCLUDE") {
-    // Add this property to the current match rule.
-    if (this->CurrentMatchRule) {
-      this->CurrentMatchRule->Properties.Exclude = true;
-      this->Doing = DoingNone;
-    } else {
-      this->NotBeforeMatch(arg);
-    }
-  } else if (arg == "PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->Doing = DoingPermissionsMatch;
-    } else {
-      this->NotBeforeMatch(arg);
-    }
-  } else if (arg == "FILE_PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingPermissionsFile;
-      this->UseGivenPermissionsFile = true;
-    }
-  } else if (arg == "DIRECTORY_PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingPermissionsDir;
-      this->UseGivenPermissionsDir = true;
-    }
-  } else if (arg == "USE_SOURCE_PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->UseSourcePermissions = true;
-    }
-  } else if (arg == "NO_SOURCE_PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->UseSourcePermissions = false;
-    }
-  } else if (arg == "FILES_MATCHING") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->MatchlessFiles = false;
-    }
-  } else {
-    return false;
-  }
-  return true;
-}
-
-bool cmFileCopier::CheckValue(std::string const& arg)
-{
-  switch (this->Doing) {
-    case DoingFiles:
-      this->Files.push_back(arg);
-      break;
-    case DoingDestination:
-      if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
-        this->Destination = arg;
-      } else {
-        this->Destination = this->Makefile->GetCurrentBinaryDirectory();
-        this->Destination += "/" + arg;
-      }
-      this->Doing = DoingNone;
-      break;
-    case DoingFilesFromDir:
-      if (cmSystemTools::FileIsFullPath(arg)) {
-        this->FilesFromDir = arg;
-      } else {
-        this->FilesFromDir = this->Makefile->GetCurrentSourceDirectory();
-        this->FilesFromDir += "/" + arg;
-      }
-      cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
-      this->Doing = DoingNone;
-      break;
-    case DoingPattern: {
-      // Convert the pattern to a regular expression.  Require a
-      // leading slash and trailing end-of-string in the matched
-      // string to make sure the pattern matches only whole file
-      // names.
-      std::string regex = "/";
-      regex += cmsys::Glob::PatternToRegex(arg, false);
-      regex += "$";
-      this->MatchRules.emplace_back(regex);
-      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
-      if (this->CurrentMatchRule->Regex.is_valid()) {
-        this->Doing = DoingNone;
-      } else {
-        std::ostringstream e;
-        e << "could not compile PATTERN \"" << arg << "\".";
-        this->FileCommand->SetError(e.str());
-        this->Doing = DoingError;
-      }
-    } break;
-    case DoingRegex:
-      this->MatchRules.emplace_back(arg);
-      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
-      if (this->CurrentMatchRule->Regex.is_valid()) {
-        this->Doing = DoingNone;
-      } else {
-        std::ostringstream e;
-        e << "could not compile REGEX \"" << arg << "\".";
-        this->FileCommand->SetError(e.str());
-        this->Doing = DoingError;
-      }
-      break;
-    case DoingPermissionsFile:
-      if (!this->CheckPermissions(arg, this->FilePermissions)) {
-        this->Doing = DoingError;
-      }
-      break;
-    case DoingPermissionsDir:
-      if (!this->CheckPermissions(arg, this->DirPermissions)) {
-        this->Doing = DoingError;
-      }
-      break;
-    case DoingPermissionsMatch:
-      if (!this->CheckPermissions(
-            arg, this->CurrentMatchRule->Properties.Permissions)) {
-        this->Doing = DoingError;
-      }
-      break;
-    default:
-      return false;
-  }
-  return true;
-}
-
-bool cmFileCopier::Run(std::vector<std::string> const& args)
-{
-  if (!this->Parse(args)) {
-    return false;
-  }
-
-  for (std::string const& f : this->Files) {
-    std::string file;
-    if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
-      if (!this->FilesFromDir.empty()) {
-        file = this->FilesFromDir;
-      } else {
-        file = this->Makefile->GetCurrentSourceDirectory();
-      }
-      file += "/";
-      file += f;
-    } else if (!this->FilesFromDir.empty()) {
-      this->FileCommand->SetError("option FILES_FROM_DIR requires all files "
-                                  "to be specified as relative paths.");
-      return false;
-    } else {
-      file = f;
-    }
-
-    // Split the input file into its directory and name components.
-    std::vector<std::string> fromPathComponents;
-    cmSystemTools::SplitPath(file, fromPathComponents);
-    std::string fromName = *(fromPathComponents.end() - 1);
-    std::string fromDir = cmSystemTools::JoinPath(
-      fromPathComponents.begin(), fromPathComponents.end() - 1);
-
-    // Compute the full path to the destination file.
-    std::string toFile = this->Destination;
-    if (!this->FilesFromDir.empty()) {
-      std::string dir = cmSystemTools::GetFilenamePath(f);
-      if (!dir.empty()) {
-        toFile += "/";
-        toFile += dir;
-      }
-    }
-    std::string const& toName = this->ToName(fromName);
-    if (!toName.empty()) {
-      toFile += "/";
-      toFile += toName;
-    }
-
-    // Construct the full path to the source file.  The file name may
-    // have been changed above.
-    std::string fromFile = fromDir;
-    if (!fromName.empty()) {
-      fromFile += "/";
-      fromFile += fromName;
-    }
-
-    if (!this->Install(fromFile, toFile)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool cmFileCopier::Install(const std::string& fromFile,
-                           const std::string& toFile)
-{
-  if (fromFile.empty()) {
-    std::ostringstream e;
-    e << "INSTALL encountered an empty string input file name.";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  // Collect any properties matching this file name.
-  MatchProperties match_properties = this->CollectMatchProperties(fromFile);
-
-  // Skip the file if it is excluded.
-  if (match_properties.Exclude) {
-    return true;
-  }
-
-  if (cmSystemTools::SameFile(fromFile, toFile)) {
-    return true;
-  }
-  if (cmSystemTools::FileIsSymlink(fromFile)) {
-    return this->InstallSymlink(fromFile, toFile);
-  }
-  if (cmSystemTools::FileIsDirectory(fromFile)) {
-    return this->InstallDirectory(fromFile, toFile, match_properties);
-  }
-  if (cmSystemTools::FileExists(fromFile)) {
-    return this->InstallFile(fromFile, toFile, match_properties);
-  }
-  return this->ReportMissing(fromFile);
-}
-
-bool cmFileCopier::InstallSymlink(const std::string& fromFile,
-                                  const std::string& toFile)
-{
-  // Read the original symlink.
-  std::string symlinkTarget;
-  if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
-    std::ostringstream e;
-    e << this->Name << " cannot read symlink \"" << fromFile
-      << "\" to duplicate at \"" << toFile << "\".";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  // Compare the symlink value to that at the destination if not
-  // always installing.
-  bool copy = true;
-  if (!this->Always) {
-    std::string oldSymlinkTarget;
-    if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
-      if (symlinkTarget == oldSymlinkTarget) {
-        copy = false;
-      }
-    }
-  }
-
-  // Inform the user about this file installation.
-  this->ReportCopy(toFile, TypeLink, copy);
-
-  if (copy) {
-    // Remove the destination file so we can always create the symlink.
-    cmSystemTools::RemoveFile(toFile);
-
-    // Create destination directory if it doesn't exist
-    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
-
-    // Create the symlink.
-    if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
-      std::ostringstream e;
-      e << this->Name << " cannot duplicate symlink \"" << fromFile
-        << "\" at \"" << toFile << "\".";
-      this->FileCommand->SetError(e.str());
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool cmFileCopier::InstallFile(const std::string& fromFile,
-                               const std::string& toFile,
-                               MatchProperties match_properties)
-{
-  // Determine whether we will copy the file.
-  bool copy = true;
-  if (!this->Always) {
-    // If both files exist with the same time do not copy.
-    if (!this->FileTimes.FileTimesDiffer(fromFile, toFile)) {
-      copy = false;
-    }
-  }
-
-  // Inform the user about this file installation.
-  this->ReportCopy(toFile, TypeFile, copy);
-
-  // Copy the file.
-  if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
-    std::ostringstream e;
-    e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
-      << toFile << "\".";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  // Set the file modification time of the destination file.
-  if (copy && !this->Always) {
-    // Add write permission so we can set the file time.
-    // Permissions are set unconditionally below anyway.
-    mode_t perm = 0;
-    if (cmSystemTools::GetPermissions(toFile, perm)) {
-      cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
-    }
-    if (!cmSystemTools::CopyFileTime(fromFile, toFile)) {
-      std::ostringstream e;
-      e << this->Name << " cannot set modification time on \"" << toFile
-        << "\"";
-      this->FileCommand->SetError(e.str());
-      return false;
-    }
-  }
-
-  // Set permissions of the destination file.
-  mode_t permissions =
-    (match_properties.Permissions ? match_properties.Permissions
-                                  : this->FilePermissions);
-  if (!permissions) {
-    // No permissions were explicitly provided but the user requested
-    // that the source file permissions be used.
-    cmSystemTools::GetPermissions(fromFile, permissions);
-  }
-  return this->SetPermissions(toFile, permissions);
-}
-
-bool cmFileCopier::InstallDirectory(const std::string& source,
-                                    const std::string& destination,
-                                    MatchProperties match_properties)
-{
-  // Inform the user about this directory installation.
-  this->ReportCopy(destination, TypeDir,
-                   !cmSystemTools::FileIsDirectory(destination));
-
-  // check if default dir creation permissions were set
-  mode_t default_dir_mode_v = 0;
-  mode_t* default_dir_mode = &default_dir_mode_v;
-  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
-    return false;
-  }
-
-  // Make sure the destination directory exists.
-  if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
-    std::ostringstream e;
-    e << this->Name << " cannot make directory \"" << destination
-      << "\": " << cmSystemTools::GetLastSystemError();
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-
-  // Compute the requested permissions for the destination directory.
-  mode_t permissions =
-    (match_properties.Permissions ? match_properties.Permissions
-                                  : this->DirPermissions);
-  if (!permissions) {
-    // No permissions were explicitly provided but the user requested
-    // that the source directory permissions be used.
-    cmSystemTools::GetPermissions(source, permissions);
-  }
-
-  // Compute the set of permissions required on this directory to
-  // recursively install files and subdirectories safely.
-  mode_t required_permissions =
-    mode_owner_read | mode_owner_write | mode_owner_execute;
-
-  // If the required permissions are specified it is safe to set the
-  // final permissions now.  Otherwise we must add the required
-  // permissions temporarily during file installation.
-  mode_t permissions_before = 0;
-  mode_t permissions_after = 0;
-  if ((permissions & required_permissions) == required_permissions) {
-    permissions_before = permissions;
-  } else {
-    permissions_before = permissions | required_permissions;
-    permissions_after = permissions;
-  }
-
-  // Set the required permissions of the destination directory.
-  if (!this->SetPermissions(destination, permissions_before)) {
-    return false;
-  }
-
-  // Load the directory contents to traverse it recursively.
-  cmsys::Directory dir;
-  if (!source.empty()) {
-    dir.Load(source);
-  }
-  unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
-  for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
-    if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
-          strcmp(dir.GetFile(fileNum), "..") == 0)) {
-      std::string fromPath = source;
-      fromPath += "/";
-      fromPath += dir.GetFile(fileNum);
-      std::string toPath = destination;
-      toPath += "/";
-      toPath += dir.GetFile(fileNum);
-      if (!this->Install(fromPath, toPath)) {
-        return false;
-      }
-    }
-  }
-
-  // Set the requested permissions of the destination directory.
-  return this->SetPermissions(destination, permissions_after);
-}
-
 bool cmFileCommand::HandleCopyCommand(std::vector<std::string> const& args)
 {
   cmFileCopier copier(this);
   return copier.Run(args);
 }
 
-struct cmFileInstaller : public cmFileCopier
-{
-  cmFileInstaller(cmFileCommand* command)
-    : cmFileCopier(command, "INSTALL")
-    , InstallType(cmInstallType_FILES)
-    , Optional(false)
-    , MessageAlways(false)
-    , MessageLazy(false)
-    , MessageNever(false)
-    , DestDirLength(0)
-  {
-    // Installation does not use source permissions by default.
-    this->UseSourcePermissions = false;
-    // Check whether to copy files always or only if they have changed.
-    std::string install_always;
-    if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
-      this->Always = cmSystemTools::IsOn(install_always);
-    }
-    // Get the current manifest.
-    this->Manifest =
-      this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
-  }
-  ~cmFileInstaller() override
-  {
-    // Save the updated install manifest.
-    this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
-                                  this->Manifest.c_str());
-  }
-
-protected:
-  cmInstallType InstallType;
-  bool Optional;
-  bool MessageAlways;
-  bool MessageLazy;
-  bool MessageNever;
-  int DestDirLength;
-  std::string Rename;
-
-  std::string Manifest;
-  void ManifestAppend(std::string const& file)
-  {
-    if (!this->Manifest.empty()) {
-      this->Manifest += ";";
-    }
-    this->Manifest += file.substr(this->DestDirLength);
-  }
-
-  std::string const& ToName(std::string const& fromName) override
-  {
-    return this->Rename.empty() ? fromName : this->Rename;
-  }
-
-  void ReportCopy(const std::string& toFile, Type type, bool copy) override
-  {
-    if (!this->MessageNever && (copy || !this->MessageLazy)) {
-      std::string message = (copy ? "Installing: " : "Up-to-date: ");
-      message += toFile;
-      this->Makefile->DisplayStatus(message, -1);
-    }
-    if (type != TypeDir) {
-      // Add the file to the manifest.
-      this->ManifestAppend(toFile);
-    }
-  }
-  bool ReportMissing(const std::string& fromFile) override
-  {
-    return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
-  }
-  bool Install(const std::string& fromFile, const std::string& toFile) override
-  {
-    // Support installing from empty source to make a directory.
-    if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
-      return this->InstallDirectory(fromFile, toFile, MatchProperties());
-    }
-    return this->cmFileCopier::Install(fromFile, toFile);
-  }
-
-  bool Parse(std::vector<std::string> const& args) override;
-  enum
-  {
-    DoingType = DoingLast1,
-    DoingRename,
-    DoingLast2
-  };
-  bool CheckKeyword(std::string const& arg) override;
-  bool CheckValue(std::string const& arg) override;
-  void DefaultFilePermissions() override
-  {
-    this->cmFileCopier::DefaultFilePermissions();
-    // Add execute permissions based on the target type.
-    switch (this->InstallType) {
-      case cmInstallType_SHARED_LIBRARY:
-      case cmInstallType_MODULE_LIBRARY:
-        if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
-          break;
-        }
-        CM_FALLTHROUGH;
-      case cmInstallType_EXECUTABLE:
-      case cmInstallType_PROGRAMS:
-        this->FilePermissions |= mode_owner_execute;
-        this->FilePermissions |= mode_group_execute;
-        this->FilePermissions |= mode_world_execute;
-        break;
-      default:
-        break;
-    }
-  }
-  bool GetTargetTypeFromString(const std::string& stype);
-  bool HandleInstallDestination();
-};
-
-bool cmFileInstaller::Parse(std::vector<std::string> const& args)
-{
-  if (!this->cmFileCopier::Parse(args)) {
-    return false;
-  }
-
-  if (!this->Rename.empty()) {
-    if (!this->FilesFromDir.empty()) {
-      this->FileCommand->SetError("INSTALL option RENAME may not be "
-                                  "combined with FILES_FROM_DIR.");
-      return false;
-    }
-    if (this->InstallType != cmInstallType_FILES &&
-        this->InstallType != cmInstallType_PROGRAMS) {
-      this->FileCommand->SetError("INSTALL option RENAME may be used "
-                                  "only with FILES or PROGRAMS.");
-      return false;
-    }
-    if (this->Files.size() > 1) {
-      this->FileCommand->SetError("INSTALL option RENAME may be used "
-                                  "only with one file.");
-      return false;
-    }
-  }
-
-  if (!this->HandleInstallDestination()) {
-    return false;
-  }
-
-  if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
-       (this->MessageNever ? 1 : 0)) > 1) {
-    this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, "
-                                "MESSAGE_LAZY, and MESSAGE_NEVER "
-                                "are mutually exclusive.");
-    return false;
-  }
-
-  return true;
-}
-
-bool cmFileInstaller::CheckKeyword(std::string const& arg)
-{
-  if (arg == "TYPE") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingType;
-    }
-  } else if (arg == "FILES") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingFiles;
-    }
-  } else if (arg == "RENAME") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingRename;
-    }
-  } else if (arg == "OPTIONAL") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->Optional = true;
-    }
-  } else if (arg == "MESSAGE_ALWAYS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->MessageAlways = true;
-    }
-  } else if (arg == "MESSAGE_LAZY") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->MessageLazy = true;
-    }
-  } else if (arg == "MESSAGE_NEVER") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      this->Doing = DoingNone;
-      this->MessageNever = true;
-    }
-  } else if (arg == "PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->Doing = DoingPermissionsMatch;
-    } else {
-      // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
-      this->Doing = DoingPermissionsFile;
-      this->UseGivenPermissionsFile = true;
-    }
-  } else if (arg == "DIR_PERMISSIONS") {
-    if (this->CurrentMatchRule) {
-      this->NotAfterMatch(arg);
-    } else {
-      // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
-      this->Doing = DoingPermissionsDir;
-      this->UseGivenPermissionsDir = true;
-    }
-  } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
-             arg == "PROPERTIES") {
-    std::ostringstream e;
-    e << "INSTALL called with old-style " << arg << " argument.  "
-      << "This script was generated with an older version of CMake.  "
-      << "Re-run this cmake version on your build tree.";
-    this->FileCommand->SetError(e.str());
-    this->Doing = DoingError;
-  } else {
-    return this->cmFileCopier::CheckKeyword(arg);
-  }
-  return true;
-}
-
-bool cmFileInstaller::CheckValue(std::string const& arg)
-{
-  switch (this->Doing) {
-    case DoingType:
-      if (!this->GetTargetTypeFromString(arg)) {
-        this->Doing = DoingError;
-      }
-      break;
-    case DoingRename:
-      this->Rename = arg;
-      break;
-    default:
-      return this->cmFileCopier::CheckValue(arg);
-  }
-  return true;
-}
-
-bool cmFileInstaller::GetTargetTypeFromString(const std::string& stype)
-{
-  if (stype == "EXECUTABLE") {
-    this->InstallType = cmInstallType_EXECUTABLE;
-  } else if (stype == "FILE") {
-    this->InstallType = cmInstallType_FILES;
-  } else if (stype == "PROGRAM") {
-    this->InstallType = cmInstallType_PROGRAMS;
-  } else if (stype == "STATIC_LIBRARY") {
-    this->InstallType = cmInstallType_STATIC_LIBRARY;
-  } else if (stype == "SHARED_LIBRARY") {
-    this->InstallType = cmInstallType_SHARED_LIBRARY;
-  } else if (stype == "MODULE") {
-    this->InstallType = cmInstallType_MODULE_LIBRARY;
-  } else if (stype == "DIRECTORY") {
-    this->InstallType = cmInstallType_DIRECTORY;
-  } else {
-    std::ostringstream e;
-    e << "Option TYPE given unknown value \"" << stype << "\".";
-    this->FileCommand->SetError(e.str());
-    return false;
-  }
-  return true;
-}
-
-bool cmFileInstaller::HandleInstallDestination()
-{
-  std::string& destination = this->Destination;
-
-  // allow for / to be a valid destination
-  if (destination.size() < 2 && destination != "/") {
-    this->FileCommand->SetError("called with inappropriate arguments. "
-                                "No DESTINATION provided or .");
-    return false;
-  }
-
-  std::string sdestdir;
-  if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
-    cmSystemTools::ConvertToUnixSlashes(sdestdir);
-    char ch1 = destination[0];
-    char ch2 = destination[1];
-    char ch3 = 0;
-    if (destination.size() > 2) {
-      ch3 = destination[2];
-    }
-    int skip = 0;
-    if (ch1 != '/') {
-      int relative = 0;
-      if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
-          ch2 == ':') {
-        // Assume windows
-        // let's do some destdir magic:
-        skip = 2;
-        if (ch3 != '/') {
-          relative = 1;
-        }
-      } else {
-        relative = 1;
-      }
-      if (relative) {
-        // This is relative path on unix or windows. Since we are doing
-        // destdir, this case does not make sense.
-        this->FileCommand->SetError(
-          "called with relative DESTINATION. This "
-          "does not make sense when using DESTDIR. Specify "
-          "absolute path or remove DESTDIR environment variable.");
-        return false;
-      }
-    } else {
-      if (ch2 == '/') {
-        // looks like a network path.
-        std::string message =
-          "called with network path DESTINATION. This "
-          "does not make sense when using DESTDIR. Specify local "
-          "absolute path or remove DESTDIR environment variable."
-          "\nDESTINATION=\n";
-        message += destination;
-        this->FileCommand->SetError(message);
-        return false;
-      }
-    }
-    destination = sdestdir + (destination.c_str() + skip);
-    this->DestDirLength = int(sdestdir.size());
-  }
-
-  // check if default dir creation permissions were set
-  mode_t default_dir_mode_v = 0;
-  mode_t* default_dir_mode = &default_dir_mode_v;
-  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
-    return false;
-  }
-
-  if (this->InstallType != cmInstallType_DIRECTORY) {
-    if (!cmSystemTools::FileExists(destination)) {
-      if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
-        std::string errstring = "cannot create directory: " + destination +
-          ". Maybe need administrative privileges.";
-        this->FileCommand->SetError(errstring);
-        return false;
-      }
-    }
-    if (!cmSystemTools::FileIsDirectory(destination)) {
-      std::string errstring =
-        "INSTALL destination: " + destination + " is not a directory.";
-      this->FileCommand->SetError(errstring);
-      return false;
-    }
-  }
-  return true;
-}
-
 bool cmFileCommand::HandleRPathChangeCommand(
   std::vector<std::string> const& args)
 {
diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx
new file mode 100644
index 0000000..560d893
--- /dev/null
+++ b/Source/cmFileCopier.cxx
@@ -0,0 +1,660 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmFileCopier.h"
+
+#include "cmFSPermissions.h"
+#include "cmFileCommand.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+#include "cmsys/Directory.hxx"
+#include "cmsys/Glob.hxx"
+
+#ifdef _WIN32
+#  include "cmsys/FStream.hxx"
+#endif
+
+#include <sstream>
+#include <string.h>
+
+using namespace cmFSPermissions;
+
+cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name)
+  : FileCommand(command)
+  , Makefile(command->GetMakefile())
+  , Name(name)
+  , Always(false)
+  , MatchlessFiles(true)
+  , FilePermissions(0)
+  , DirPermissions(0)
+  , CurrentMatchRule(nullptr)
+  , UseGivenPermissionsFile(false)
+  , UseGivenPermissionsDir(false)
+  , UseSourcePermissions(true)
+  , Doing(DoingNone)
+{
+}
+
+cmFileCopier::~cmFileCopier() = default;
+
+cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
+  const std::string& file)
+{
+  // Match rules are case-insensitive on some platforms.
+#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
+  const std::string file_to_match = cmSystemTools::LowerCase(file);
+#else
+  const std::string& file_to_match = file;
+#endif
+
+  // Collect properties from all matching rules.
+  bool matched = false;
+  MatchProperties result;
+  for (MatchRule& mr : this->MatchRules) {
+    if (mr.Regex.find(file_to_match)) {
+      matched = true;
+      result.Exclude |= mr.Properties.Exclude;
+      result.Permissions |= mr.Properties.Permissions;
+    }
+  }
+  if (!matched && !this->MatchlessFiles) {
+    result.Exclude = !cmSystemTools::FileIsDirectory(file);
+  }
+  return result;
+}
+
+bool cmFileCopier::SetPermissions(const std::string& toFile,
+                                  mode_t permissions)
+{
+  if (permissions) {
+#ifdef WIN32
+    if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
+      // Store the mode in an NTFS alternate stream.
+      std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
+
+      // Writing to an NTFS alternate stream changes the modification
+      // time, so we need to save and restore its original value.
+      cmSystemToolsFileTime* file_time_orig = cmSystemTools::FileTimeNew();
+      cmSystemTools::FileTimeGet(toFile, file_time_orig);
+
+      cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
+
+      if (permissionStream) {
+        permissionStream << std::oct << permissions << std::endl;
+      }
+
+      permissionStream.close();
+
+      cmSystemTools::FileTimeSet(toFile, file_time_orig);
+
+      cmSystemTools::FileTimeDelete(file_time_orig);
+    }
+#endif
+
+    if (!cmSystemTools::SetPermissions(toFile, permissions)) {
+      std::ostringstream e;
+      e << this->Name << " cannot set permissions on \"" << toFile << "\"";
+      this->FileCommand->SetError(e.str());
+      return false;
+    }
+  }
+  return true;
+}
+
+// Translate an argument to a permissions bit.
+bool cmFileCopier::CheckPermissions(std::string const& arg,
+                                    mode_t& permissions)
+{
+  if (!cmFSPermissions::stringToModeT(arg, permissions)) {
+    std::ostringstream e;
+    e << this->Name << " given invalid permission \"" << arg << "\".";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+  return true;
+}
+
+std::string const& cmFileCopier::ToName(std::string const& fromName)
+{
+  return fromName;
+}
+
+bool cmFileCopier::ReportMissing(const std::string& fromFile)
+{
+  // The input file does not exist and installation is not optional.
+  std::ostringstream e;
+  e << this->Name << " cannot find \"" << fromFile << "\".";
+  this->FileCommand->SetError(e.str());
+  return false;
+}
+
+void cmFileCopier::NotBeforeMatch(std::string const& arg)
+{
+  std::ostringstream e;
+  e << "option " << arg << " may not appear before PATTERN or REGEX.";
+  this->FileCommand->SetError(e.str());
+  this->Doing = DoingError;
+}
+
+void cmFileCopier::NotAfterMatch(std::string const& arg)
+{
+  std::ostringstream e;
+  e << "option " << arg << " may not appear after PATTERN or REGEX.";
+  this->FileCommand->SetError(e.str());
+  this->Doing = DoingError;
+}
+
+void cmFileCopier::DefaultFilePermissions()
+{
+  // Use read/write permissions.
+  this->FilePermissions = 0;
+  this->FilePermissions |= mode_owner_read;
+  this->FilePermissions |= mode_owner_write;
+  this->FilePermissions |= mode_group_read;
+  this->FilePermissions |= mode_world_read;
+}
+
+void cmFileCopier::DefaultDirectoryPermissions()
+{
+  // Use read/write/executable permissions.
+  this->DirPermissions = 0;
+  this->DirPermissions |= mode_owner_read;
+  this->DirPermissions |= mode_owner_write;
+  this->DirPermissions |= mode_owner_execute;
+  this->DirPermissions |= mode_group_read;
+  this->DirPermissions |= mode_group_execute;
+  this->DirPermissions |= mode_world_read;
+  this->DirPermissions |= mode_world_execute;
+}
+
+bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
+{
+  // check if default dir creation permissions were set
+  const char* default_dir_install_permissions = this->Makefile->GetDefinition(
+    "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
+  if (default_dir_install_permissions && *default_dir_install_permissions) {
+    std::vector<std::string> items;
+    cmSystemTools::ExpandListArgument(default_dir_install_permissions, items);
+    for (const auto& arg : items) {
+      if (!this->CheckPermissions(arg, **mode)) {
+        std::ostringstream e;
+        e << this->FileCommand->GetError()
+          << " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS "
+             "variable.";
+        this->FileCommand->SetError(e.str());
+        return false;
+      }
+    }
+  } else {
+    *mode = nullptr;
+  }
+
+  return true;
+}
+
+bool cmFileCopier::Parse(std::vector<std::string> const& args)
+{
+  this->Doing = DoingFiles;
+  for (unsigned int i = 1; i < args.size(); ++i) {
+    // Check this argument.
+    if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
+      std::ostringstream e;
+      e << "called with unknown argument \"" << args[i] << "\".";
+      this->FileCommand->SetError(e.str());
+      return false;
+    }
+
+    // Quit if an argument is invalid.
+    if (this->Doing == DoingError) {
+      return false;
+    }
+  }
+
+  // Require a destination.
+  if (this->Destination.empty()) {
+    std::ostringstream e;
+    e << this->Name << " given no DESTINATION";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+
+  // If file permissions were not specified set default permissions.
+  if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
+    this->DefaultFilePermissions();
+  }
+
+  // If directory permissions were not specified set default permissions.
+  if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
+    this->DefaultDirectoryPermissions();
+  }
+
+  return true;
+}
+
+bool cmFileCopier::CheckKeyword(std::string const& arg)
+{
+  if (arg == "DESTINATION") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingDestination;
+    }
+  } else if (arg == "FILES_FROM_DIR") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingFilesFromDir;
+    }
+  } else if (arg == "PATTERN") {
+    this->Doing = DoingPattern;
+  } else if (arg == "REGEX") {
+    this->Doing = DoingRegex;
+  } else if (arg == "EXCLUDE") {
+    // Add this property to the current match rule.
+    if (this->CurrentMatchRule) {
+      this->CurrentMatchRule->Properties.Exclude = true;
+      this->Doing = DoingNone;
+    } else {
+      this->NotBeforeMatch(arg);
+    }
+  } else if (arg == "PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->Doing = DoingPermissionsMatch;
+    } else {
+      this->NotBeforeMatch(arg);
+    }
+  } else if (arg == "FILE_PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingPermissionsFile;
+      this->UseGivenPermissionsFile = true;
+    }
+  } else if (arg == "DIRECTORY_PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingPermissionsDir;
+      this->UseGivenPermissionsDir = true;
+    }
+  } else if (arg == "USE_SOURCE_PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->UseSourcePermissions = true;
+    }
+  } else if (arg == "NO_SOURCE_PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->UseSourcePermissions = false;
+    }
+  } else if (arg == "FILES_MATCHING") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->MatchlessFiles = false;
+    }
+  } else {
+    return false;
+  }
+  return true;
+}
+
+bool cmFileCopier::CheckValue(std::string const& arg)
+{
+  switch (this->Doing) {
+    case DoingFiles:
+      this->Files.push_back(arg);
+      break;
+    case DoingDestination:
+      if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
+        this->Destination = arg;
+      } else {
+        this->Destination = this->Makefile->GetCurrentBinaryDirectory();
+        this->Destination += "/" + arg;
+      }
+      this->Doing = DoingNone;
+      break;
+    case DoingFilesFromDir:
+      if (cmSystemTools::FileIsFullPath(arg)) {
+        this->FilesFromDir = arg;
+      } else {
+        this->FilesFromDir = this->Makefile->GetCurrentSourceDirectory();
+        this->FilesFromDir += "/" + arg;
+      }
+      cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
+      this->Doing = DoingNone;
+      break;
+    case DoingPattern: {
+      // Convert the pattern to a regular expression.  Require a
+      // leading slash and trailing end-of-string in the matched
+      // string to make sure the pattern matches only whole file
+      // names.
+      std::string regex = "/";
+      regex += cmsys::Glob::PatternToRegex(arg, false);
+      regex += "$";
+      this->MatchRules.emplace_back(regex);
+      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+      if (this->CurrentMatchRule->Regex.is_valid()) {
+        this->Doing = DoingNone;
+      } else {
+        std::ostringstream e;
+        e << "could not compile PATTERN \"" << arg << "\".";
+        this->FileCommand->SetError(e.str());
+        this->Doing = DoingError;
+      }
+    } break;
+    case DoingRegex:
+      this->MatchRules.emplace_back(arg);
+      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+      if (this->CurrentMatchRule->Regex.is_valid()) {
+        this->Doing = DoingNone;
+      } else {
+        std::ostringstream e;
+        e << "could not compile REGEX \"" << arg << "\".";
+        this->FileCommand->SetError(e.str());
+        this->Doing = DoingError;
+      }
+      break;
+    case DoingPermissionsFile:
+      if (!this->CheckPermissions(arg, this->FilePermissions)) {
+        this->Doing = DoingError;
+      }
+      break;
+    case DoingPermissionsDir:
+      if (!this->CheckPermissions(arg, this->DirPermissions)) {
+        this->Doing = DoingError;
+      }
+      break;
+    case DoingPermissionsMatch:
+      if (!this->CheckPermissions(
+            arg, this->CurrentMatchRule->Properties.Permissions)) {
+        this->Doing = DoingError;
+      }
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+bool cmFileCopier::Run(std::vector<std::string> const& args)
+{
+  if (!this->Parse(args)) {
+    return false;
+  }
+
+  for (std::string const& f : this->Files) {
+    std::string file;
+    if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
+      if (!this->FilesFromDir.empty()) {
+        file = this->FilesFromDir;
+      } else {
+        file = this->Makefile->GetCurrentSourceDirectory();
+      }
+      file += "/";
+      file += f;
+    } else if (!this->FilesFromDir.empty()) {
+      this->FileCommand->SetError("option FILES_FROM_DIR requires all files "
+                                  "to be specified as relative paths.");
+      return false;
+    } else {
+      file = f;
+    }
+
+    // Split the input file into its directory and name components.
+    std::vector<std::string> fromPathComponents;
+    cmSystemTools::SplitPath(file, fromPathComponents);
+    std::string fromName = *(fromPathComponents.end() - 1);
+    std::string fromDir = cmSystemTools::JoinPath(
+      fromPathComponents.begin(), fromPathComponents.end() - 1);
+
+    // Compute the full path to the destination file.
+    std::string toFile = this->Destination;
+    if (!this->FilesFromDir.empty()) {
+      std::string dir = cmSystemTools::GetFilenamePath(f);
+      if (!dir.empty()) {
+        toFile += "/";
+        toFile += dir;
+      }
+    }
+    std::string const& toName = this->ToName(fromName);
+    if (!toName.empty()) {
+      toFile += "/";
+      toFile += toName;
+    }
+
+    // Construct the full path to the source file.  The file name may
+    // have been changed above.
+    std::string fromFile = fromDir;
+    if (!fromName.empty()) {
+      fromFile += "/";
+      fromFile += fromName;
+    }
+
+    if (!this->Install(fromFile, toFile)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool cmFileCopier::Install(const std::string& fromFile,
+                           const std::string& toFile)
+{
+  if (fromFile.empty()) {
+    std::ostringstream e;
+    e << "INSTALL encountered an empty string input file name.";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+
+  // Collect any properties matching this file name.
+  MatchProperties match_properties = this->CollectMatchProperties(fromFile);
+
+  // Skip the file if it is excluded.
+  if (match_properties.Exclude) {
+    return true;
+  }
+
+  if (cmSystemTools::SameFile(fromFile, toFile)) {
+    return true;
+  }
+  if (cmSystemTools::FileIsSymlink(fromFile)) {
+    return this->InstallSymlink(fromFile, toFile);
+  }
+  if (cmSystemTools::FileIsDirectory(fromFile)) {
+    return this->InstallDirectory(fromFile, toFile, match_properties);
+  }
+  if (cmSystemTools::FileExists(fromFile)) {
+    return this->InstallFile(fromFile, toFile, match_properties);
+  }
+  return this->ReportMissing(fromFile);
+}
+
+bool cmFileCopier::InstallSymlink(const std::string& fromFile,
+                                  const std::string& toFile)
+{
+  // Read the original symlink.
+  std::string symlinkTarget;
+  if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
+    std::ostringstream e;
+    e << this->Name << " cannot read symlink \"" << fromFile
+      << "\" to duplicate at \"" << toFile << "\".";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+
+  // Compare the symlink value to that at the destination if not
+  // always installing.
+  bool copy = true;
+  if (!this->Always) {
+    std::string oldSymlinkTarget;
+    if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+      if (symlinkTarget == oldSymlinkTarget) {
+        copy = false;
+      }
+    }
+  }
+
+  // Inform the user about this file installation.
+  this->ReportCopy(toFile, TypeLink, copy);
+
+  if (copy) {
+    // Remove the destination file so we can always create the symlink.
+    cmSystemTools::RemoveFile(toFile);
+
+    // Create destination directory if it doesn't exist
+    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
+
+    // Create the symlink.
+    if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
+      std::ostringstream e;
+      e << this->Name << " cannot duplicate symlink \"" << fromFile
+        << "\" at \"" << toFile << "\".";
+      this->FileCommand->SetError(e.str());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool cmFileCopier::InstallFile(const std::string& fromFile,
+                               const std::string& toFile,
+                               MatchProperties match_properties)
+{
+  // Determine whether we will copy the file.
+  bool copy = true;
+  if (!this->Always) {
+    // If both files exist with the same time do not copy.
+    if (!this->FileTimes.FileTimesDiffer(fromFile, toFile)) {
+      copy = false;
+    }
+  }
+
+  // Inform the user about this file installation.
+  this->ReportCopy(toFile, TypeFile, copy);
+
+  // Copy the file.
+  if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
+    std::ostringstream e;
+    e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
+      << toFile << "\".";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+
+  // Set the file modification time of the destination file.
+  if (copy && !this->Always) {
+    // Add write permission so we can set the file time.
+    // Permissions are set unconditionally below anyway.
+    mode_t perm = 0;
+    if (cmSystemTools::GetPermissions(toFile, perm)) {
+      cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
+    }
+    if (!cmSystemTools::CopyFileTime(fromFile, toFile)) {
+      std::ostringstream e;
+      e << this->Name << " cannot set modification time on \"" << toFile
+        << "\"";
+      this->FileCommand->SetError(e.str());
+      return false;
+    }
+  }
+
+  // Set permissions of the destination file.
+  mode_t permissions =
+    (match_properties.Permissions ? match_properties.Permissions
+                                  : this->FilePermissions);
+  if (!permissions) {
+    // No permissions were explicitly provided but the user requested
+    // that the source file permissions be used.
+    cmSystemTools::GetPermissions(fromFile, permissions);
+  }
+  return this->SetPermissions(toFile, permissions);
+}
+
+bool cmFileCopier::InstallDirectory(const std::string& source,
+                                    const std::string& destination,
+                                    MatchProperties match_properties)
+{
+  // Inform the user about this directory installation.
+  this->ReportCopy(destination, TypeDir,
+                   !cmSystemTools::FileIsDirectory(destination));
+
+  // check if default dir creation permissions were set
+  mode_t default_dir_mode_v = 0;
+  mode_t* default_dir_mode = &default_dir_mode_v;
+  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+    return false;
+  }
+
+  // Make sure the destination directory exists.
+  if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+    std::ostringstream e;
+    e << this->Name << " cannot make directory \"" << destination
+      << "\": " << cmSystemTools::GetLastSystemError();
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+
+  // Compute the requested permissions for the destination directory.
+  mode_t permissions =
+    (match_properties.Permissions ? match_properties.Permissions
+                                  : this->DirPermissions);
+  if (!permissions) {
+    // No permissions were explicitly provided but the user requested
+    // that the source directory permissions be used.
+    cmSystemTools::GetPermissions(source, permissions);
+  }
+
+  // Compute the set of permissions required on this directory to
+  // recursively install files and subdirectories safely.
+  mode_t required_permissions =
+    mode_owner_read | mode_owner_write | mode_owner_execute;
+
+  // If the required permissions are specified it is safe to set the
+  // final permissions now.  Otherwise we must add the required
+  // permissions temporarily during file installation.
+  mode_t permissions_before = 0;
+  mode_t permissions_after = 0;
+  if ((permissions & required_permissions) == required_permissions) {
+    permissions_before = permissions;
+  } else {
+    permissions_before = permissions | required_permissions;
+    permissions_after = permissions;
+  }
+
+  // Set the required permissions of the destination directory.
+  if (!this->SetPermissions(destination, permissions_before)) {
+    return false;
+  }
+
+  // Load the directory contents to traverse it recursively.
+  cmsys::Directory dir;
+  if (!source.empty()) {
+    dir.Load(source);
+  }
+  unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
+  for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
+    if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
+          strcmp(dir.GetFile(fileNum), "..") == 0)) {
+      std::string fromPath = source;
+      fromPath += "/";
+      fromPath += dir.GetFile(fileNum);
+      std::string toPath = destination;
+      toPath += "/";
+      toPath += dir.GetFile(fileNum);
+      if (!this->Install(fromPath, toPath)) {
+        return false;
+      }
+    }
+  }
+
+  // Set the requested permissions of the destination directory.
+  return this->SetPermissions(destination, permissions_after);
+}
diff --git a/Source/cmFileCopier.h b/Source/cmFileCopier.h
new file mode 100644
index 0000000..a11c371
--- /dev/null
+++ b/Source/cmFileCopier.h
@@ -0,0 +1,120 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmFileCopier_h
+#define cmFileCopier_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileTimeComparison.h"
+#include "cm_sys_stat.h"
+#include "cmsys/RegularExpression.hxx"
+
+#include <string>
+#include <vector>
+
+class cmFileCommand;
+class cmMakefile;
+
+// File installation helper class.
+struct cmFileCopier
+{
+  cmFileCopier(cmFileCommand* command, const char* name = "COPY");
+  virtual ~cmFileCopier();
+
+  bool Run(std::vector<std::string> const& args);
+
+protected:
+  cmFileCommand* FileCommand;
+  cmMakefile* Makefile;
+  const char* Name;
+  bool Always;
+  cmFileTimeComparison FileTimes;
+
+  // Whether to install a file not matching any expression.
+  bool MatchlessFiles;
+
+  // Permissions for files and directories installed by this object.
+  mode_t FilePermissions;
+  mode_t DirPermissions;
+
+  // Properties set by pattern and regex match rules.
+  struct MatchProperties
+  {
+    bool Exclude = false;
+    mode_t Permissions = 0;
+  };
+  struct MatchRule
+  {
+    cmsys::RegularExpression Regex;
+    MatchProperties Properties;
+    std::string RegexString;
+    MatchRule(std::string const& regex)
+      : Regex(regex)
+      , RegexString(regex)
+    {
+    }
+  };
+  std::vector<MatchRule> MatchRules;
+
+  // Get the properties from rules matching this input file.
+  MatchProperties CollectMatchProperties(const std::string& file);
+
+  bool SetPermissions(const std::string& toFile, mode_t permissions);
+
+  // Translate an argument to a permissions bit.
+  bool CheckPermissions(std::string const& arg, mode_t& permissions);
+
+  bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
+  bool InstallFile(const std::string& fromFile, const std::string& toFile,
+                   MatchProperties match_properties);
+  bool InstallDirectory(const std::string& source,
+                        const std::string& destination,
+                        MatchProperties match_properties);
+  virtual bool Install(const std::string& fromFile, const std::string& toFile);
+  virtual std::string const& ToName(std::string const& fromName);
+
+  enum Type
+  {
+    TypeFile,
+    TypeDir,
+    TypeLink
+  };
+  virtual void ReportCopy(const std::string&, Type, bool) {}
+  virtual bool ReportMissing(const std::string& fromFile);
+
+  MatchRule* CurrentMatchRule;
+  bool UseGivenPermissionsFile;
+  bool UseGivenPermissionsDir;
+  bool UseSourcePermissions;
+  std::string Destination;
+  std::string FilesFromDir;
+  std::vector<std::string> Files;
+  int Doing;
+
+  virtual bool Parse(std::vector<std::string> const& args);
+  enum
+  {
+    DoingNone,
+    DoingError,
+    DoingDestination,
+    DoingFilesFromDir,
+    DoingFiles,
+    DoingPattern,
+    DoingRegex,
+    DoingPermissionsFile,
+    DoingPermissionsDir,
+    DoingPermissionsMatch,
+    DoingLast1
+  };
+  virtual bool CheckKeyword(std::string const& arg);
+  virtual bool CheckValue(std::string const& arg);
+
+  void NotBeforeMatch(std::string const& arg);
+  void NotAfterMatch(std::string const& arg);
+  virtual void DefaultFilePermissions();
+  virtual void DefaultDirectoryPermissions();
+
+  bool GetDefaultDirectoryPermissions(mode_t** mode);
+};
+
+#endif
diff --git a/Source/cmFileInstaller.cxx b/Source/cmFileInstaller.cxx
new file mode 100644
index 0000000..f3544c1
--- /dev/null
+++ b/Source/cmFileInstaller.cxx
@@ -0,0 +1,350 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmFileInstaller.h"
+
+#include "cmFSPermissions.h"
+#include "cmFileCommand.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+
+#include "cm_sys_stat.h"
+
+#include <sstream>
+
+using namespace cmFSPermissions;
+
+cmFileInstaller::cmFileInstaller(cmFileCommand* command)
+  : cmFileCopier(command, "INSTALL")
+  , InstallType(cmInstallType_FILES)
+  , Optional(false)
+  , MessageAlways(false)
+  , MessageLazy(false)
+  , MessageNever(false)
+  , DestDirLength(0)
+{
+  // Installation does not use source permissions by default.
+  this->UseSourcePermissions = false;
+  // Check whether to copy files always or only if they have changed.
+  std::string install_always;
+  if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
+    this->Always = cmSystemTools::IsOn(install_always);
+  }
+  // Get the current manifest.
+  this->Manifest =
+    this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
+}
+cmFileInstaller::~cmFileInstaller()
+{
+  // Save the updated install manifest.
+  this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
+                                this->Manifest.c_str());
+}
+
+void cmFileInstaller::ManifestAppend(std::string const& file)
+{
+  if (!this->Manifest.empty()) {
+    this->Manifest += ";";
+  }
+  this->Manifest += file.substr(this->DestDirLength);
+}
+
+std::string const& cmFileInstaller::ToName(std::string const& fromName)
+{
+  return this->Rename.empty() ? fromName : this->Rename;
+}
+
+void cmFileInstaller::ReportCopy(const std::string& toFile, Type type,
+                                 bool copy)
+{
+  if (!this->MessageNever && (copy || !this->MessageLazy)) {
+    std::string message = (copy ? "Installing: " : "Up-to-date: ");
+    message += toFile;
+    this->Makefile->DisplayStatus(message, -1);
+  }
+  if (type != TypeDir) {
+    // Add the file to the manifest.
+    this->ManifestAppend(toFile);
+  }
+}
+bool cmFileInstaller::ReportMissing(const std::string& fromFile)
+{
+  return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
+}
+bool cmFileInstaller::Install(const std::string& fromFile,
+                              const std::string& toFile)
+{
+  // Support installing from empty source to make a directory.
+  if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
+    return this->InstallDirectory(fromFile, toFile, MatchProperties());
+  }
+  return this->cmFileCopier::Install(fromFile, toFile);
+}
+
+void cmFileInstaller::DefaultFilePermissions()
+{
+  this->cmFileCopier::DefaultFilePermissions();
+  // Add execute permissions based on the target type.
+  switch (this->InstallType) {
+    case cmInstallType_SHARED_LIBRARY:
+    case cmInstallType_MODULE_LIBRARY:
+      if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
+        break;
+      }
+      CM_FALLTHROUGH;
+    case cmInstallType_EXECUTABLE:
+    case cmInstallType_PROGRAMS:
+      this->FilePermissions |= mode_owner_execute;
+      this->FilePermissions |= mode_group_execute;
+      this->FilePermissions |= mode_world_execute;
+      break;
+    default:
+      break;
+  }
+}
+
+bool cmFileInstaller::Parse(std::vector<std::string> const& args)
+{
+  if (!this->cmFileCopier::Parse(args)) {
+    return false;
+  }
+
+  if (!this->Rename.empty()) {
+    if (!this->FilesFromDir.empty()) {
+      this->FileCommand->SetError("INSTALL option RENAME may not be "
+                                  "combined with FILES_FROM_DIR.");
+      return false;
+    }
+    if (this->InstallType != cmInstallType_FILES &&
+        this->InstallType != cmInstallType_PROGRAMS) {
+      this->FileCommand->SetError("INSTALL option RENAME may be used "
+                                  "only with FILES or PROGRAMS.");
+      return false;
+    }
+    if (this->Files.size() > 1) {
+      this->FileCommand->SetError("INSTALL option RENAME may be used "
+                                  "only with one file.");
+      return false;
+    }
+  }
+
+  if (!this->HandleInstallDestination()) {
+    return false;
+  }
+
+  if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
+       (this->MessageNever ? 1 : 0)) > 1) {
+    this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, "
+                                "MESSAGE_LAZY, and MESSAGE_NEVER "
+                                "are mutually exclusive.");
+    return false;
+  }
+
+  return true;
+}
+
+bool cmFileInstaller::CheckKeyword(std::string const& arg)
+{
+  if (arg == "TYPE") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingType;
+    }
+  } else if (arg == "FILES") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingFiles;
+    }
+  } else if (arg == "RENAME") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingRename;
+    }
+  } else if (arg == "OPTIONAL") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->Optional = true;
+    }
+  } else if (arg == "MESSAGE_ALWAYS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->MessageAlways = true;
+    }
+  } else if (arg == "MESSAGE_LAZY") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->MessageLazy = true;
+    }
+  } else if (arg == "MESSAGE_NEVER") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      this->Doing = DoingNone;
+      this->MessageNever = true;
+    }
+  } else if (arg == "PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->Doing = DoingPermissionsMatch;
+    } else {
+      // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
+      this->Doing = DoingPermissionsFile;
+      this->UseGivenPermissionsFile = true;
+    }
+  } else if (arg == "DIR_PERMISSIONS") {
+    if (this->CurrentMatchRule) {
+      this->NotAfterMatch(arg);
+    } else {
+      // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
+      this->Doing = DoingPermissionsDir;
+      this->UseGivenPermissionsDir = true;
+    }
+  } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
+             arg == "PROPERTIES") {
+    std::ostringstream e;
+    e << "INSTALL called with old-style " << arg << " argument.  "
+      << "This script was generated with an older version of CMake.  "
+      << "Re-run this cmake version on your build tree.";
+    this->FileCommand->SetError(e.str());
+    this->Doing = DoingError;
+  } else {
+    return this->cmFileCopier::CheckKeyword(arg);
+  }
+  return true;
+}
+
+bool cmFileInstaller::CheckValue(std::string const& arg)
+{
+  switch (this->Doing) {
+    case DoingType:
+      if (!this->GetTargetTypeFromString(arg)) {
+        this->Doing = DoingError;
+      }
+      break;
+    case DoingRename:
+      this->Rename = arg;
+      break;
+    default:
+      return this->cmFileCopier::CheckValue(arg);
+  }
+  return true;
+}
+
+bool cmFileInstaller::GetTargetTypeFromString(const std::string& stype)
+{
+  if (stype == "EXECUTABLE") {
+    this->InstallType = cmInstallType_EXECUTABLE;
+  } else if (stype == "FILE") {
+    this->InstallType = cmInstallType_FILES;
+  } else if (stype == "PROGRAM") {
+    this->InstallType = cmInstallType_PROGRAMS;
+  } else if (stype == "STATIC_LIBRARY") {
+    this->InstallType = cmInstallType_STATIC_LIBRARY;
+  } else if (stype == "SHARED_LIBRARY") {
+    this->InstallType = cmInstallType_SHARED_LIBRARY;
+  } else if (stype == "MODULE") {
+    this->InstallType = cmInstallType_MODULE_LIBRARY;
+  } else if (stype == "DIRECTORY") {
+    this->InstallType = cmInstallType_DIRECTORY;
+  } else {
+    std::ostringstream e;
+    e << "Option TYPE given unknown value \"" << stype << "\".";
+    this->FileCommand->SetError(e.str());
+    return false;
+  }
+  return true;
+}
+
+bool cmFileInstaller::HandleInstallDestination()
+{
+  std::string& destination = this->Destination;
+
+  // allow for / to be a valid destination
+  if (destination.size() < 2 && destination != "/") {
+    this->FileCommand->SetError("called with inappropriate arguments. "
+                                "No DESTINATION provided or .");
+    return false;
+  }
+
+  std::string sdestdir;
+  if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
+    cmSystemTools::ConvertToUnixSlashes(sdestdir);
+    char ch1 = destination[0];
+    char ch2 = destination[1];
+    char ch3 = 0;
+    if (destination.size() > 2) {
+      ch3 = destination[2];
+    }
+    int skip = 0;
+    if (ch1 != '/') {
+      int relative = 0;
+      if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
+          ch2 == ':') {
+        // Assume windows
+        // let's do some destdir magic:
+        skip = 2;
+        if (ch3 != '/') {
+          relative = 1;
+        }
+      } else {
+        relative = 1;
+      }
+      if (relative) {
+        // This is relative path on unix or windows. Since we are doing
+        // destdir, this case does not make sense.
+        this->FileCommand->SetError(
+          "called with relative DESTINATION. This "
+          "does not make sense when using DESTDIR. Specify "
+          "absolute path or remove DESTDIR environment variable.");
+        return false;
+      }
+    } else {
+      if (ch2 == '/') {
+        // looks like a network path.
+        std::string message =
+          "called with network path DESTINATION. This "
+          "does not make sense when using DESTDIR. Specify local "
+          "absolute path or remove DESTDIR environment variable."
+          "\nDESTINATION=\n";
+        message += destination;
+        this->FileCommand->SetError(message);
+        return false;
+      }
+    }
+    destination = sdestdir + (destination.c_str() + skip);
+    this->DestDirLength = int(sdestdir.size());
+  }
+
+  // check if default dir creation permissions were set
+  mode_t default_dir_mode_v = 0;
+  mode_t* default_dir_mode = &default_dir_mode_v;
+  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+    return false;
+  }
+
+  if (this->InstallType != cmInstallType_DIRECTORY) {
+    if (!cmSystemTools::FileExists(destination)) {
+      if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+        std::string errstring = "cannot create directory: " + destination +
+          ". Maybe need administrative privileges.";
+        this->FileCommand->SetError(errstring);
+        return false;
+      }
+    }
+    if (!cmSystemTools::FileIsDirectory(destination)) {
+      std::string errstring =
+        "INSTALL destination: " + destination + " is not a directory.";
+      this->FileCommand->SetError(errstring);
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/Source/cmFileInstaller.h b/Source/cmFileInstaller.h
new file mode 100644
index 0000000..312529a
--- /dev/null
+++ b/Source/cmFileInstaller.h
@@ -0,0 +1,55 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmFileInstaller_h
+#define cmFileInstaller_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileCopier.h"
+
+#include "cmInstallType.h"
+
+#include <string>
+#include <vector>
+
+class cmFileCommand;
+
+struct cmFileInstaller : public cmFileCopier
+{
+  cmFileInstaller(cmFileCommand* command);
+  ~cmFileInstaller() override;
+
+protected:
+  cmInstallType InstallType;
+  bool Optional;
+  bool MessageAlways;
+  bool MessageLazy;
+  bool MessageNever;
+  int DestDirLength;
+  std::string Rename;
+
+  std::string Manifest;
+  void ManifestAppend(std::string const& file);
+
+  std::string const& ToName(std::string const& fromName) override;
+
+  void ReportCopy(const std::string& toFile, Type type, bool copy) override;
+  bool ReportMissing(const std::string& fromFile) override;
+  bool Install(const std::string& fromFile,
+               const std::string& toFile) override;
+
+  bool Parse(std::vector<std::string> const& args) override;
+  enum
+  {
+    DoingType = DoingLast1,
+    DoingRename,
+    DoingLast2
+  };
+  bool CheckKeyword(std::string const& arg) override;
+  bool CheckValue(std::string const& arg) override;
+  void DefaultFilePermissions() override;
+  bool GetTargetTypeFromString(const std::string& stype);
+  bool HandleInstallDestination();
+};
+
+#endif
diff --git a/bootstrap b/bootstrap
index 26c5212..a3cd6f0 100755
--- a/bootstrap
+++ b/bootstrap
@@ -302,6 +302,8 @@ CMAKE_CXX_SOURCES="\
   cmExprParserHelper \
   cmExternalMakefileProjectGenerator \
   cmFileCommand \
+  cmFileCopier \
+  cmFileInstaller \
   cmFileTimeComparison \
   cmFindBase \
   cmFindCommon \

-----------------------------------------------------------------------

Summary of changes:
 Source/CMakeLists.txt      |    4 +
 Source/cmFileCommand.cxx   | 1082 +-------------------------------------------
 Source/cmFileCopier.cxx    |  660 +++++++++++++++++++++++++++
 Source/cmFileCopier.h      |  120 +++++
 Source/cmFileInstaller.cxx |  350 ++++++++++++++
 Source/cmFileInstaller.h   |   55 +++
 bootstrap                  |    2 +
 7 files changed, 1193 insertions(+), 1080 deletions(-)
 create mode 100644 Source/cmFileCopier.cxx
 create mode 100644 Source/cmFileCopier.h
 create mode 100644 Source/cmFileInstaller.cxx
 create mode 100644 Source/cmFileInstaller.h


hooks/post-receive
-- 
CMake
_______________________________________________
Cmake-commits mailing list
Cmake-commits@cmake.org
https://cmake.org/mailman/listinfo/cmake-commits

Reply via email to