/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmQtAutoMocUic.h"

#include <algorithm>
#include <atomic>
#include <cstddef>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>

#include <cm3p/json/value.h>

#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"

#include "cmCryptoHash.h"
#include "cmFileTime.h"
#include "cmGccDepfileReader.h"
#include "cmGeneratedFileStream.h"
#include "cmQtAutoGen.h"
#include "cmQtAutoGenerator.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmWorkerPool.h"

#if defined(__APPLE__)
#  include <unistd.h>
#endif

namespace {

constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_"
constexpr std::size_t UiUnderscoreLength = 3;  // Length of "ui_"

/** \class cmQtAutoMocUicT
 * \brief AUTOMOC and AUTOUIC generator
 */
class cmQtAutoMocUicT : public cmQtAutoGenerator
{
public:
  cmQtAutoMocUicT();
  ~cmQtAutoMocUicT() override;

  cmQtAutoMocUicT(cmQtAutoMocUicT const&) = delete;
  cmQtAutoMocUicT& operator=(cmQtAutoMocUicT const&) = delete;

  // -- Types

  /** Include string with sub parts.  */
  struct IncludeKeyT
  {
    IncludeKeyT(std::string const& key, std::size_t basePrefixLength);

    std::string Key;  // Full include string
    std::string Dir;  // Include directory
    std::string Base; // Base part of the include file name
  };

  /** Search key plus regular expression pair.  */
  struct KeyExpT
  {
    KeyExpT(std::string key, std::string const& exp)
      : Key(std::move(key))
      , Exp(exp)
    {
    }

    std::string Key;
    cmsys::RegularExpression Exp;
  };

  /** Source file parsing cache.  */
  class ParseCacheT
  {
  public:
    // -- Types

    /** Entry of the file parsing cache.  */
    struct FileT
    {
      void Clear();

      struct MocT
      {
        std::string Macro;
        struct IncludeT
        {
          std::vector<IncludeKeyT> Underscore;
          std::vector<IncludeKeyT> Dot;
        } Include;
        std::vector<std::string> Depends;
      } Moc;

      struct UicT
      {
        std::vector<IncludeKeyT> Include;
        std::vector<std::string> Depends;
      } Uic;
    };
    using FileHandleT = std::shared_ptr<FileT>;
    using GetOrInsertT = std::pair<FileHandleT, bool>;

    ParseCacheT();
    ~ParseCacheT();

    bool ReadFromFile(std::string const& fileName);
    bool WriteToFile(std::string const& fileName);

    //! Always returns a valid handle
    GetOrInsertT GetOrInsert(std::string const& fileName);

  private:
    std::unordered_map<std::string, FileHandleT> Map_;
  };

  /** Source file data.  */
  class SourceFileT
  {
  public:
    SourceFileT(std::string fileName)
      : FileName(std::move(fileName))
    {
    }

    std::string FileName;
    cmFileTime FileTime;
    ParseCacheT::FileHandleT ParseData;
    std::string BuildPath;
    bool IsHeader = false;
    bool Moc = false;
    bool Uic = false;
  };
  using SourceFileHandleT = std::shared_ptr<SourceFileT>;
  using SourceFileMapT = std::map<std::string, SourceFileHandleT>;

  /** Meta compiler file mapping information.  */
  struct MappingT
  {
    SourceFileHandleT SourceFile;
    std::string OutputFile;
    std::string IncludeString;
    std::vector<SourceFileHandleT> IncluderFiles;
  };
  using MappingHandleT = std::shared_ptr<MappingT>;
  using MappingMapT = std::map<std::string, MappingHandleT>;

  /** Common settings.  */
  class BaseSettingsT
  {
  public:
    // -- Constructors
    BaseSettingsT();
    ~BaseSettingsT();

    BaseSettingsT(BaseSettingsT const&) = delete;
    BaseSettingsT& operator=(BaseSettingsT const&) = delete;

    // -- Attributes
    // - Config
    bool MultiConfig = false;
    IntegerVersion QtVersion = { 4, 0 };
    unsigned int ThreadCount = 0;
    // - Directories
    std::string AutogenBuildDir;
    std::string AutogenIncludeDir;
    // - Files
    std::string CMakeExecutable;
    cmFileTime CMakeExecutableTime;
    std::string ParseCacheFile;
    std::string DepFile;
    std::string DepFileRuleName;
    std::vector<std::string> HeaderExtensions;
    std::vector<std::string> ListFiles;
  };

  /** Shared common variables.  */
  class BaseEvalT
  {
  public:
    // -- Parse Cache
    std::atomic<bool> ParseCacheChanged = ATOMIC_VAR_INIT(false);
    cmFileTime ParseCacheTime;
    ParseCacheT ParseCache;

    // -- Sources
    SourceFileMapT Headers;
    SourceFileMapT Sources;
  };

  /** Moc settings.  */
  class MocSettingsT
  {
  public:
    // -- Constructors
    MocSettingsT();
    ~MocSettingsT();

    MocSettingsT(MocSettingsT const&) = delete;
    MocSettingsT& operator=(MocSettingsT const&) = delete;

    // -- Const methods
    bool skipped(std::string const& fileName) const;
    std::string MacrosString() const;

    // -- Attributes
    bool Enabled = false;
    bool SettingsChanged = false;
    bool RelaxedMode = false;
    bool PathPrefix = false;
    bool CanOutputDependencies = false;
    cmFileTime ExecutableTime;
    std::string Executable;
    std::string CompFileAbs;
    std::string PredefsFileAbs;
    std::unordered_set<std::string> SkipList;
    std::vector<std::string> IncludePaths;
    std::vector<std::string> Definitions;
    std::vector<std::string> OptionsIncludes;
    std::vector<std::string> OptionsDefinitions;
    std::vector<std::string> OptionsExtra;
    std::vector<std::string> PredefsCmd;
    std::vector<KeyExpT> DependFilters;
    std::vector<KeyExpT> MacroFilters;
    cmsys::RegularExpression RegExpInclude;
  };

  /** Moc shared variables.  */
  class MocEvalT
  {
  public:
    // -- predefines file
    cmFileTime PredefsTime;
    // -- Mappings
    MappingMapT HeaderMappings;
    MappingMapT SourceMappings;
    MappingMapT Includes;
    // -- Discovered files
    SourceFileMapT HeadersDiscovered;
    // -- Output directories
    std::unordered_set<std::string> OutputDirs;
    // -- Mocs compilation
    bool CompUpdated = false;
    std::vector<std::string> CompFiles;
  };

  /** Uic settings.  */
  class UicSettingsT
  {
  public:
    struct UiFile
    {
      std::vector<std::string> Options;
    };

    UicSettingsT();
    ~UicSettingsT();

    UicSettingsT(UicSettingsT const&) = delete;
    UicSettingsT& operator=(UicSettingsT const&) = delete;

    // -- Const methods
    bool skipped(std::string const& fileName) const;

    // -- Attributes
    bool Enabled = false;
    bool SettingsChanged = false;
    cmFileTime ExecutableTime;
    std::string Executable;
    std::unordered_set<std::string> SkipList;
    std::vector<std::string> Options;
    std::unordered_map<std::string, UiFile> UiFiles;
    std::vector<std::string> SearchPaths;
    cmsys::RegularExpression RegExpInclude;
  };

  /** Uic shared variables.  */
  class UicEvalT
  {
  public:
    // -- Discovered files
    SourceFileMapT UiFiles;
    // -- Mappings
    MappingMapT Includes;
    // -- Output directories
    std::unordered_set<std::string> OutputDirs;
  };

  /** Abstract job class for concurrent job processing.  */
  class JobT : public cmWorkerPool::JobT
  {
  protected:
    /** Protected default constructor.  */
    JobT(bool fence = false)
      : cmWorkerPool::JobT(fence)
    {
    }

    //! Get the generator. Only valid during Process() call!
    cmQtAutoMocUicT* Gen() const
    {
      return static_cast<cmQtAutoMocUicT*>(this->UserData());
    };

    // -- Accessors. Only valid during Process() call!
    Logger const& Log() const { return this->Gen()->Log(); }
    BaseSettingsT const& BaseConst() const { return this->Gen()->BaseConst(); }
    BaseEvalT& BaseEval() const { return this->Gen()->BaseEval(); }
    MocSettingsT const& MocConst() const { return this->Gen()->MocConst(); }
    MocEvalT& MocEval() const { return this->Gen()->MocEval(); }
    UicSettingsT const& UicConst() const { return this->Gen()->UicConst(); }
    UicEvalT& UicEval() const { return this->Gen()->UicEval(); }

    // -- Logging
    std::string MessagePath(cm::string_view path) const
    {
      return this->Gen()->MessagePath(path);
    }
    // - Error logging with automatic abort
    void LogError(GenT genType, cm::string_view message) const;
    void LogCommandError(GenT genType, cm::string_view message,
                         std::vector<std::string> const& command,
                         std::string const& output) const;

    /** @brief Run an external process. Use only during Process() call!  */
    bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
                    std::vector<std::string> const& command,
                    std::string* infoMessage = nullptr);
  };

  /** Fence job utility class.  */
  class JobFenceT : public JobT
  {
  public:
    JobFenceT()
      : JobT(true)
    {
    }
    void Process() override{};
  };

  /** Generate moc_predefs.h.  */
  class JobMocPredefsT : public JobFenceT
  {
    void Process() override;
    bool Update(std::string* reason) const;
  };

  /** File parse job base class.  */
  class JobParseT : public JobT
  {
  public:
    JobParseT(SourceFileHandleT fileHandle)
      : FileHandle(std::move(fileHandle))
    {
    }

  protected:
    bool ReadFile();
    void CreateKeys(std::vector<IncludeKeyT>& container,
                    std::set<std::string> const& source,
                    std::size_t basePrefixLength);
    void MocMacro();
    void MocDependecies();
    void MocIncludes();
    void UicIncludes();

    SourceFileHandleT FileHandle;
    std::string Content;
  };

  /** Header file parse job.  */
  class JobParseHeaderT : public JobParseT
  {
  public:
    using JobParseT::JobParseT;
    void Process() override;
  };

  /** Source file parse job.  */
  class JobParseSourceT : public JobParseT
  {
  public:
    using JobParseT::JobParseT;
    void Process() override;
  };

  /** Evaluate cached file parse data - moc.  */
  class JobEvalCacheT : public JobT
  {
  protected:
    std::string MessageSearchLocations() const;
    std::vector<std::string> SearchLocations;
  };

  /** Evaluate cached file parse data - moc.  */
  class JobEvalCacheMocT : public JobEvalCacheT
  {
    void Process() override;
    bool EvalHeader(SourceFileHandleT source);
    bool EvalSource(SourceFileHandleT const& source);
    bool FindIncludedHeader(SourceFileHandleT& headerHandle,
                            cm::string_view includerDir,
                            cm::string_view includeBase);
    bool RegisterIncluded(std::string const& includeString,
                          SourceFileHandleT includerFileHandle,
                          SourceFileHandleT sourceFileHandle) const;
    void RegisterMapping(MappingHandleT mappingHandle) const;
    std::string MessageHeader(cm::string_view headerBase) const;
  };

  /** Evaluate cached file parse data - uic.  */
  class JobEvalCacheUicT : public JobEvalCacheT
  {
    void Process() override;
    bool EvalFile(SourceFileHandleT const& sourceFileHandle);
    bool FindIncludedUi(cm::string_view sourceDirPrefix,
                        cm::string_view includePrefix);
    bool RegisterMapping(std::string const& includeString,
                         SourceFileHandleT includerFileHandle);

    std::string UiName;
    SourceFileHandleT UiFileHandle;
  };

  /** Evaluate cached file parse data - finish  */
  class JobEvalCacheFinishT : public JobFenceT
  {
    void Process() override;
  };

  /** Dependency probing base job.  */
  class JobProbeDepsT : public JobT
  {
  };

  /** Probes file dependencies and generates moc compile jobs.  */
  class JobProbeDepsMocT : public JobProbeDepsT
  {
    void Process() override;
    bool Generate(MappingHandleT const& mapping, bool compFile) const;
    bool Probe(MappingT const& mapping, std::string* reason) const;
    std::pair<std::string, cmFileTime> FindDependency(
      std::string const& sourceDir, std::string const& includeString) const;
  };

  /** Probes file dependencies and generates uic compile jobs.  */
  class JobProbeDepsUicT : public JobProbeDepsT
  {
    void Process() override;
    bool Probe(MappingT const& mapping, std::string* reason) const;
  };

  /** Dependency probing finish job.  */
  class JobProbeDepsFinishT : public JobFenceT
  {
    void Process() override;
  };

  /** Meta compiler base job.  */
  class JobCompileT : public JobT
  {
  public:
    JobCompileT(MappingHandleT uicMapping, std::unique_ptr<std::string> reason)
      : Mapping(std::move(uicMapping))
      , Reason(std::move(reason))
    {
    }

  protected:
    MappingHandleT Mapping;
    std::unique_ptr<std::string> Reason;
  };

  /** moc compiles a file.  */
  class JobCompileMocT : public JobCompileT
  {
  public:
    JobCompileMocT(MappingHandleT uicMapping,
                   std::unique_ptr<std::string> reason,
                   ParseCacheT::FileHandleT cacheEntry)
      : JobCompileT(std::move(uicMapping), std::move(reason))
      , CacheEntry(std::move(cacheEntry))
    {
    }
    void Process() override;

  protected:
    ParseCacheT::FileHandleT CacheEntry;
  };

  /** uic compiles a file.  */
  class JobCompileUicT : public JobCompileT
  {
  public:
    using JobCompileT::JobCompileT;
    void Process() override;
  };

  /** Generate mocs_compilation.cpp.  */
  class JobMocsCompilationT : public JobFenceT
  {
  private:
    void Process() override;
  };

  class JobDepFilesMergeT : public JobFenceT
  {
  private:
    std::vector<std::string> initialDependencies() const;
    void Process() override;
  };

  /** @brief The last job.  */
  class JobFinishT : public JobFenceT
  {
  private:
    void Process() override;
  };

  // -- Const settings interface
  BaseSettingsT const& BaseConst() const { return this->BaseConst_; }
  BaseEvalT& BaseEval() { return this->BaseEval_; }
  MocSettingsT const& MocConst() const { return this->MocConst_; }
  MocEvalT& MocEval() { return this->MocEval_; }
  UicSettingsT const& UicConst() const { return this->UicConst_; }
  UicEvalT& UicEval() { return this->UicEval_; }

  // -- Parallel job processing interface
  cmWorkerPool& WorkerPool() { return this->WorkerPool_; }
  void AbortError() { this->Abort(true); }
  void AbortSuccess() { this->Abort(false); }

  // -- Utility
  std::string AbsoluteBuildPath(cm::string_view relativePath) const;
  std::string AbsoluteIncludePath(cm::string_view relativePath) const;
  template <class JOBTYPE>
  void CreateParseJobs(SourceFileMapT const& sourceMap);
  std::string CollapseFullPathTS(std::string const& path) const;

private:
  // -- Abstract processing interface
  bool InitFromInfo(InfoT const& info) override;
  void InitJobs();
  bool Process() override;
  // -- Settings file
  void SettingsFileRead();
  bool SettingsFileWrite();
  // -- Parse cache
  void ParseCacheRead();
  bool ParseCacheWrite();
  // -- Thread processing
  void Abort(bool error);
  // -- Generation
  bool CreateDirectories();
  // -- Support for depfiles
  static std::vector<std::string> dependenciesFromDepFile(
    const char* filePath);

  // -- Settings
  BaseSettingsT BaseConst_;
  BaseEvalT BaseEval_;
  MocSettingsT MocConst_;
  MocEvalT MocEval_;
  UicSettingsT UicConst_;
  UicEvalT UicEval_;
  // -- Settings file
  std::string SettingsFile_;
  std::string SettingsStringMoc_;
  std::string SettingsStringUic_;
  // -- Worker thread pool
  std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
  cmWorkerPool WorkerPool_;
  // -- Concurrent processing
  mutable std::mutex CMakeLibMutex_;
};

cmQtAutoMocUicT::IncludeKeyT::IncludeKeyT(std::string const& key,
                                          std::size_t basePrefixLength)
  : Key(key)
  , Dir(SubDirPrefix(key))
  , Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
{
  if (basePrefixLength != 0) {
    this->Base = this->Base.substr(basePrefixLength);
  }
}

void cmQtAutoMocUicT::ParseCacheT::FileT::Clear()
{
  this->Moc.Macro.clear();
  this->Moc.Include.Underscore.clear();
  this->Moc.Include.Dot.clear();
  this->Moc.Depends.clear();

  this->Uic.Include.clear();
  this->Uic.Depends.clear();
}

cmQtAutoMocUicT::ParseCacheT::GetOrInsertT
cmQtAutoMocUicT::ParseCacheT::GetOrInsert(std::string const& fileName)
{
  // Find existing entry
  {
    auto it = this->Map_.find(fileName);
    if (it != this->Map_.end()) {
      return GetOrInsertT{ it->second, false };
    }
  }

  // Insert new entry
  return GetOrInsertT{
    this->Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
  };
}

cmQtAutoMocUicT::ParseCacheT::ParseCacheT() = default;
cmQtAutoMocUicT::ParseCacheT::~ParseCacheT() = default;

bool cmQtAutoMocUicT::ParseCacheT::ReadFromFile(std::string const& fileName)
{
  cmsys::ifstream fin(fileName.c_str());
  if (!fin) {
    return false;
  }
  FileHandleT fileHandle;

  std::string line;
  while (std::getline(fin, line)) {
    // Check if this an empty or a comment line
    if (line.empty() || line.front() == '#') {
      continue;
    }
    // Drop carriage return character at the end
    if (line.back() == '\r') {
      line.pop_back();
      if (line.empty()) {
        continue;
      }
    }
    // Check if this a file name line
    if (line.front() != ' ') {
      fileHandle = this->GetOrInsert(line).first;
      continue;
    }

    // Bad line or bad file handle
    if (!fileHandle || (line.size() < 6)) {
      continue;
    }

    constexpr std::size_t offset = 5;
    if (cmHasLiteralPrefix(line, " mmc:")) {
      fileHandle->Moc.Macro = line.substr(offset);
      continue;
    }
    if (cmHasLiteralPrefix(line, " miu:")) {
      fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset),
                                                      MocUnderscoreLength);
      continue;
    }
    if (cmHasLiteralPrefix(line, " mid:")) {
      fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0);
      continue;
    }
    if (cmHasLiteralPrefix(line, " mdp:")) {
      fileHandle->Moc.Depends.emplace_back(line.substr(offset));
      continue;
    }
    if (cmHasLiteralPrefix(line, " uic:")) {
      fileHandle->Uic.Include.emplace_back(line.substr(offset),
                                           UiUnderscoreLength);
      continue;
    }
    if (cmHasLiteralPrefix(line, " udp:")) {
      fileHandle->Uic.Depends.emplace_back(line.substr(offset));
      continue;
    }
  }
  return true;
}

bool cmQtAutoMocUicT::ParseCacheT::WriteToFile(std::string const& fileName)
{
  cmGeneratedFileStream ofs(fileName);
  if (!ofs) {
    return false;
  }
  ofs << "# Generated by CMake. Changes will be overwritten.\n";
  for (auto const& pair : this->Map_) {
    ofs << pair.first << '\n';
    FileT const& file = *pair.second;
    if (!file.Moc.Macro.empty()) {
      ofs << " mmc:" << file.Moc.Macro << '\n';
    }
    for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
      ofs << " miu:" << item.Key << '\n';
    }
    for (IncludeKeyT const& item : file.Moc.Include.Dot) {
      ofs << " mid:" << item.Key << '\n';
    }
    for (std::string const& item : file.Moc.Depends) {
      ofs << " mdp:" << item << '\n';
    }
    for (IncludeKeyT const& item : file.Uic.Include) {
      ofs << " uic:" << item.Key << '\n';
    }
    for (std::string const& item : file.Uic.Depends) {
      ofs << " udp:" << item << '\n';
    }
  }
  return ofs.Close();
}

cmQtAutoMocUicT::BaseSettingsT::BaseSettingsT() = default;
cmQtAutoMocUicT::BaseSettingsT::~BaseSettingsT() = default;

cmQtAutoMocUicT::MocSettingsT::MocSettingsT()
{
  this->RegExpInclude.compile(
    "(^|\n)[ \t]*#[ \t]*include[ \t]+"
    "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
}

cmQtAutoMocUicT::MocSettingsT::~MocSettingsT() = default;

bool cmQtAutoMocUicT::MocSettingsT::skipped(std::string const& fileName) const
{
  return (!this->Enabled ||
          (this->SkipList.find(fileName) != this->SkipList.end()));
}

std::string cmQtAutoMocUicT::MocSettingsT::MacrosString() const
{
  std::string res;
  const auto itB = this->MacroFilters.cbegin();
  const auto itE = this->MacroFilters.cend();
  const auto itL = itE - 1;
  auto itC = itB;
  for (; itC != itE; ++itC) {
    // Separator
    if (itC != itB) {
      if (itC != itL) {
        res += ", ";
      } else {
        res += " or ";
      }
    }
    // Key
    res += itC->Key;
  }
  return res;
}

cmQtAutoMocUicT::UicSettingsT::UicSettingsT()
{
  this->RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
                              "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
}

cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;

bool cmQtAutoMocUicT::UicSettingsT::skipped(std::string const& fileName) const
{
  return (!this->Enabled ||
          (this->SkipList.find(fileName) != this->SkipList.end()));
}

void cmQtAutoMocUicT::JobT::LogError(GenT genType,
                                     cm::string_view message) const
{
  this->Gen()->AbortError();
  this->Gen()->Log().Error(genType, message);
}

void cmQtAutoMocUicT::JobT::LogCommandError(
  GenT genType, cm::string_view message,
  std::vector<std::string> const& command, std::string const& output) const
{
  this->Gen()->AbortError();
  this->Gen()->Log().ErrorCommand(genType, message, command, output);
}

bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType,
                                       cmWorkerPool::ProcessResultT& result,
                                       std::vector<std::string> const& command,
                                       std::string* infoMessage)
{
  // Log command
  if (this->Log().Verbose()) {
    cm::string_view info;
    if (infoMessage != nullptr) {
      info = *infoMessage;
    }
    this->Log().Info(
      genType,
      cmStrCat(info, info.empty() || cmHasSuffix(info, '\n') ? "" : "\n",
               QuotedCommand(command), '\n'));
  }
  // Run command
  return this->cmWorkerPool::JobT::RunProcess(
    result, command, this->BaseConst().AutogenBuildDir);
}

void cmQtAutoMocUicT::JobMocPredefsT::Process()
{
  // (Re)generate moc_predefs.h on demand
  std::unique_ptr<std::string> reason;
  if (this->Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (!this->Update(reason.get())) {
    return;
  }
  std::string const& predefsFileAbs = this->MocConst().PredefsFileAbs;
  {
    cmWorkerPool::ProcessResultT result;
    {
      // Compose command
      std::vector<std::string> cmd = this->MocConst().PredefsCmd;
      // Add definitions
      cm::append(cmd, this->MocConst().OptionsDefinitions);
      // Add includes
      cm::append(cmd, this->MocConst().OptionsIncludes);
      // Execute command
      if (!this->RunProcess(GenT::MOC, result, cmd, reason.get())) {
        this->LogCommandError(GenT::MOC,
                              cmStrCat("The content generation command for ",
                                       this->MessagePath(predefsFileAbs),
                                       " failed.\n", result.ErrorMessage),
                              cmd, result.StdOut);
        return;
      }
    }

    // (Re)write predefs file only on demand
    if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
      if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
        this->LogError(
          GenT::MOC,
          cmStrCat("Writing ", this->MessagePath(predefsFileAbs), " failed."));
        return;
      }
    } else {
      // Touch to update the time stamp
      if (this->Log().Verbose()) {
        this->Log().Info(GenT::MOC,
                         "Touching " + this->MessagePath(predefsFileAbs));
      }
      if (!cmSystemTools::Touch(predefsFileAbs, false)) {
        this->LogError(GenT::MOC,
                       cmStrCat("Touching ", this->MessagePath(predefsFileAbs),
                                " failed."));
        return;
      }
    }
  }

  // Read file time afterwards
  if (!this->MocEval().PredefsTime.Load(predefsFileAbs)) {
    this->LogError(GenT::MOC,
                   cmStrCat("Reading the file time of ",
                            this->MessagePath(predefsFileAbs), " failed."));
    return;
  }
}

bool cmQtAutoMocUicT::JobMocPredefsT::Update(std::string* reason) const
{
  // Test if the file exists
  if (!this->MocEval().PredefsTime.Load(this->MocConst().PredefsFileAbs)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ",
                         this->MessagePath(this->MocConst().PredefsFileAbs),
                         ", because it doesn't exist.");
    }
    return true;
  }

  // Test if the settings changed
  if (this->MocConst().SettingsChanged) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ",
                         this->MessagePath(this->MocConst().PredefsFileAbs),
                         ", because the moc settings changed.");
    }
    return true;
  }

  // Test if the executable is newer
  {
    std::string const& exec = this->MocConst().PredefsCmd.at(0);
    cmFileTime execTime;
    if (execTime.Load(exec)) {
      if (this->MocEval().PredefsTime.Older(execTime)) {
        if (reason != nullptr) {
          *reason = cmStrCat(
            "Generating ", this->MessagePath(this->MocConst().PredefsFileAbs),
            " because it is older than ", this->MessagePath(exec), '.');
        }
        return true;
      }
    }
  }

  return false;
}

bool cmQtAutoMocUicT::JobParseT::ReadFile()
{
  // Clear old parse information
  this->FileHandle->ParseData->Clear();
  std::string const& fileName = this->FileHandle->FileName;
  // Write info
  if (this->Log().Verbose()) {
    this->Log().Info(GenT::GEN,
                     cmStrCat("Parsing ", this->MessagePath(fileName)));
  }
  // Read file content
  {
    std::string error;
    if (!cmQtAutoGenerator::FileRead(this->Content, fileName, &error)) {
      this->LogError(GenT::GEN,
                     cmStrCat("Could not read ", this->MessagePath(fileName),
                              ".\n", error));
      return false;
    }
  }
  // Warn if empty
  if (this->Content.empty()) {
    this->Log().Warning(GenT::GEN,
                        cmStrCat(this->MessagePath(fileName), " is empty."));
    return false;
  }
  return true;
}

void cmQtAutoMocUicT::JobParseT::CreateKeys(
  std::vector<IncludeKeyT>& container, std::set<std::string> const& source,
  std::size_t basePrefixLength)
{
  if (source.empty()) {
    return;
  }
  container.reserve(source.size());
  for (std::string const& src : source) {
    container.emplace_back(src, basePrefixLength);
  }
}

void cmQtAutoMocUicT::JobParseT::MocMacro()
{
  for (KeyExpT const& filter : this->MocConst().MacroFilters) {
    // Run a simple find string check
    if (this->Content.find(filter.Key) == std::string::npos) {
      continue;
    }
    // Run the expensive regular expression check loop
    cmsys::RegularExpressionMatch match;
    if (filter.Exp.find(this->Content.c_str(), match)) {
      // Keep detected macro name
      this->FileHandle->ParseData->Moc.Macro = filter.Key;
      return;
    }
  }
}

void cmQtAutoMocUicT::JobParseT::MocDependecies()
{
  if (this->MocConst().DependFilters.empty() ||
      this->MocConst().CanOutputDependencies) {
    return;
  }

  // Find dependency strings
  std::set<std::string> parseDepends;
  for (KeyExpT const& filter : this->MocConst().DependFilters) {
    // Run a simple find string check
    if (this->Content.find(filter.Key) == std::string::npos) {
      continue;
    }
    // Run the expensive regular expression check loop
    const char* contentChars = this->Content.c_str();
    cmsys::RegularExpressionMatch match;
    while (filter.Exp.find(contentChars, match)) {
      {
        std::string dep = match.match(1);
        if (!dep.empty()) {
          parseDepends.emplace(std::move(dep));
        }
      }
      contentChars += match.end();
    }
  }

  // Store dependency strings
  {
    auto& Depends = this->FileHandle->ParseData->Moc.Depends;
    Depends.reserve(parseDepends.size());
    for (std::string const& item : parseDepends) {
      Depends.emplace_back(item);
      // Replace end of line characters in filenames
      std::string& path = Depends.back();
      std::replace(path.begin(), path.end(), '\n', ' ');
      std::replace(path.begin(), path.end(), '\r', ' ');
    }
  }
}

void cmQtAutoMocUicT::JobParseT::MocIncludes()
{
  if (this->Content.find("moc") == std::string::npos) {
    return;
  }

  std::set<std::string> underscore;
  std::set<std::string> dot;
  {
    const char* contentChars = this->Content.c_str();
    cmsys::RegularExpression const& regExp = this->MocConst().RegExpInclude;
    cmsys::RegularExpressionMatch match;
    while (regExp.find(contentChars, match)) {
      std::string incString = match.match(2);
      std::string const incBase =
        cmSystemTools::GetFilenameWithoutLastExtension(incString);
      if (cmHasLiteralPrefix(incBase, "moc_")) {
        // moc_<BASE>.cpp
        // Remove the moc_ part from the base name
        underscore.emplace(std::move(incString));
      } else {
        // <BASE>.moc
        dot.emplace(std::move(incString));
      }
      // Forward content pointer
      contentChars += match.end();
    }
  }
  auto& Include = this->FileHandle->ParseData->Moc.Include;
  this->CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
  this->CreateKeys(Include.Dot, dot, 0);
}

void cmQtAutoMocUicT::JobParseT::UicIncludes()
{
  if (this->Content.find("ui_") == std::string::npos) {
    return;
  }

  std::set<std::string> includes;
  {
    const char* contentChars = this->Content.c_str();
    cmsys::RegularExpression const& regExp = this->UicConst().RegExpInclude;
    cmsys::RegularExpressionMatch match;
    while (regExp.find(contentChars, match)) {
      includes.emplace(match.match(2));
      // Forward content pointer
      contentChars += match.end();
    }
  }
  this->CreateKeys(this->FileHandle->ParseData->Uic.Include, includes,
                   UiUnderscoreLength);
}

void cmQtAutoMocUicT::JobParseHeaderT::Process()
{
  if (!this->ReadFile()) {
    return;
  }
  // Moc parsing
  if (this->FileHandle->Moc) {
    this->MocMacro();
    this->MocDependecies();
  }
  // Uic parsing
  if (this->FileHandle->Uic) {
    this->UicIncludes();
  }
}

void cmQtAutoMocUicT::JobParseSourceT::Process()
{
  if (!this->ReadFile()) {
    return;
  }
  // Moc parsing
  if (this->FileHandle->Moc) {
    this->MocMacro();
    this->MocDependecies();
    this->MocIncludes();
  }
  // Uic parsing
  if (this->FileHandle->Uic) {
    this->UicIncludes();
  }
}

std::string cmQtAutoMocUicT::JobEvalCacheT::MessageSearchLocations() const
{
  std::string res;
  res.reserve(512);
  for (std::string const& path : this->SearchLocations) {
    res += "  ";
    res += this->MessagePath(path);
    res += '\n';
  }
  return res;
}

void cmQtAutoMocUicT::JobEvalCacheMocT::Process()
{
  // Evaluate headers
  for (auto const& pair : this->BaseEval().Headers) {
    if (!this->EvalHeader(pair.second)) {
      return;
    }
  }
  // Evaluate sources
  for (auto const& pair : this->BaseEval().Sources) {
    if (!this->EvalSource(pair.second)) {
      return;
    }
  }
}

bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(SourceFileHandleT source)
{
  SourceFileT const& sourceFile = *source;
  auto const& parseData = sourceFile.ParseData->Moc;
  if (!source->Moc) {
    return true;
  }

  if (!parseData.Macro.empty()) {
    // Create a new mapping
    MappingHandleT handle = std::make_shared<MappingT>();
    handle->SourceFile = std::move(source);

    // Absolute build path
    if (this->BaseConst().MultiConfig) {
      handle->OutputFile =
        this->Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
    } else {
      handle->OutputFile =
        this->Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
    }

    // Register mapping in headers map
    this->RegisterMapping(handle);
  }

  return true;
}

bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalSource(
  SourceFileHandleT const& source)
{
  SourceFileT const& sourceFile = *source;
  auto const& parseData = sourceFile.ParseData->Moc;
  if (!sourceFile.Moc ||
      (parseData.Macro.empty() && parseData.Include.Underscore.empty() &&
       parseData.Include.Dot.empty())) {
    return true;
  }

  std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
  std::string const sourceBase =
    cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName);

  // For relaxed mode check if the own "moc_" or ".moc" file is included
  bool const relaxedMode = this->MocConst().RelaxedMode;
  bool sourceIncludesMocUnderscore = false;
  bool sourceIncludesDotMoc = false;
  // Check if the sources own "moc_" or ".moc" file is included
  if (relaxedMode) {
    for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
      if (incKey.Base == sourceBase) {
        sourceIncludesMocUnderscore = true;
        break;
      }
    }
  }
  for (IncludeKeyT const& incKey : parseData.Include.Dot) {
    if (incKey.Base == sourceBase) {
      sourceIncludesDotMoc = true;
      break;
    }
  }

  // Check if this source needs to be moc processed but doesn't.
  if (!sourceIncludesDotMoc && !parseData.Macro.empty() &&
      !(relaxedMode && sourceIncludesMocUnderscore)) {
    this->LogError(GenT::MOC,
                   cmStrCat(this->MessagePath(sourceFile.FileName),
                            "\ncontains a ", Quoted(parseData.Macro),
                            " macro, but does not include ",
                            this->MessagePath(sourceBase + ".moc"),
                            "!\nConsider to\n  - add #include \"", sourceBase,
                            ".moc\"\n  - enable SKIP_AUTOMOC for this file"));
    return false;
  }

  // Evaluate "moc_" includes
  for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
    SourceFileHandleT headerHandle;
    {
      std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
      if (!this->FindIncludedHeader(headerHandle, sourceDirPrefix,
                                    headerBase)) {
        this->LogError(
          GenT::MOC,
          cmStrCat(this->MessagePath(sourceFile.FileName),
                   "\nincludes the moc file ", this->MessagePath(incKey.Key),
                   ",\nbut a header ", this->MessageHeader(headerBase),
                   "\ncould not be found "
                   "in the following directories\n",
                   this->MessageSearchLocations()));
        return false;
      }
    }
    // The include might be handled differently in relaxed mode
    if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() &&
        (incKey.Base == sourceBase)) {
      // The <BASE>.cpp file includes a Qt macro but does not include the
      // <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably
      // be generated from <BASE>.cpp instead of <BASE>.h, because otherwise
      // it won't build. But warn, since this is not how it is supposed to be
      // used. This is for KDE4 compatibility.

      // Issue a warning
      this->Log().Warning(
        GenT::MOC,
        cmStrCat(this->MessagePath(sourceFile.FileName), "\ncontains a ",
                 Quoted(parseData.Macro), " macro, but does not include ",
                 this->MessagePath(sourceBase + ".moc"),
                 ".\nInstead it includes ", this->MessagePath(incKey.Key),
                 ".\nRunning moc on the source\n  ",
                 this->MessagePath(sourceFile.FileName), "!\nBetter include ",
                 this->MessagePath(sourceBase + ".moc"),
                 " for compatibility with regular mode.\n",
                 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));

      // Create mapping
      if (!this->RegisterIncluded(incKey.Key, source, source)) {
        return false;
      }
      continue;
    }

    // Check if header is skipped
    if (this->MocConst().skipped(headerHandle->FileName)) {
      continue;
    }
    // Create mapping
    if (!this->RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
      return false;
    }
  }

  // Evaluate ".moc" includes
  if (relaxedMode) {
    // Relaxed mode
    for (IncludeKeyT const& incKey : parseData.Include.Dot) {
      // Check if this is the sources own .moc file
      bool const ownMoc = (incKey.Base == sourceBase);
      if (ownMoc && !parseData.Macro.empty()) {
        // Create mapping for the regular use case
        if (!this->RegisterIncluded(incKey.Key, source, source)) {
          return false;
        }
        continue;
      }
      // Try to find a header instead but issue a warning.
      // This is for KDE4 compatibility.
      SourceFileHandleT headerHandle;
      {
        std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
        if (!this->FindIncludedHeader(headerHandle, sourceDirPrefix,
                                      headerBase)) {
          this->LogError(
            GenT::MOC,
            cmStrCat(
              this->MessagePath(sourceFile.FileName),
              "\nincludes the moc file ", this->MessagePath(incKey.Key),
              ",\nwhich seems to be the moc file from a different source "
              "file.\nCMAKE_AUTOMOC_RELAXED_MODE:\nAlso a matching header ",
              this->MessageHeader(headerBase),
              "\ncould not be found in the following directories\n",
              this->MessageSearchLocations()));
          return false;
        }
      }
      // Check if header is skipped
      if (this->MocConst().skipped(headerHandle->FileName)) {
        continue;
      }
      // Issue a warning
      if (ownMoc && parseData.Macro.empty()) {
        this->Log().Warning(
          GenT::MOC,
          cmStrCat(
            this->MessagePath(sourceFile.FileName), "\nincludes the moc file ",
            this->MessagePath(incKey.Key), ", but does not contain a\n",
            this->MocConst().MacrosString(),
            " macro.\nRunning moc on the header\n  ",
            this->MessagePath(headerHandle->FileName), "!\nBetter include ",
            this->MessagePath("moc_" + incKey.Base + ".cpp"),
            " for a compatibility with regular mode.\n",
            "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
      } else {
        this->Log().Warning(
          GenT::MOC,
          cmStrCat(
            this->MessagePath(sourceFile.FileName), "\nincludes the moc file ",
            this->MessagePath(incKey.Key), " instead of ",
            this->MessagePath("moc_" + incKey.Base + ".cpp"),
            ".\nRunning moc on the header\n  ",
            this->MessagePath(headerHandle->FileName), "!\nBetter include ",
            this->MessagePath("moc_" + incKey.Base + ".cpp"),
            " for compatibility with regular mode.\n",
            "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
      }
      // Create mapping
      if (!this->RegisterIncluded(incKey.Key, source,
                                  std::move(headerHandle))) {
        return false;
      }
    }
  } else {
    // Strict mode
    for (IncludeKeyT const& incKey : parseData.Include.Dot) {
      // Check if this is the sources own .moc file
      bool const ownMoc = (incKey.Base == sourceBase);
      if (!ownMoc) {
        // Don't allow <BASE>.moc include other than own in regular mode
        this->LogError(
          GenT::MOC,
          cmStrCat(this->MessagePath(sourceFile.FileName),
                   "\nincludes the moc file ", this->MessagePath(incKey.Key),
                   ",\nwhich seems to be the moc file from a different "
                   "source file.\nThis is not supported.  Include ",
                   this->MessagePath(sourceBase + ".moc"),
                   " to run moc on this source file."));
        return false;
      }
      // Accept but issue a warning if moc isn't required
      if (parseData.Macro.empty()) {
        this->Log().Warning(
          GenT::MOC,
          cmStrCat(this->MessagePath(sourceFile.FileName),
                   "\nincludes the moc file ", this->MessagePath(incKey.Key),
                   ", but does not contain a ",
                   this->MocConst().MacrosString(), " macro."));
      }
      // Create mapping
      if (!this->RegisterIncluded(incKey.Key, source, source)) {
        return false;
      }
    }
  }

  return true;
}

bool cmQtAutoMocUicT::JobEvalCacheMocT::FindIncludedHeader(
  SourceFileHandleT& headerHandle, cm::string_view includerDir,
  cm::string_view includeBase)
{
  // Clear search locations
  this->SearchLocations.clear();

  auto findHeader = [this,
                     &headerHandle](std::string const& basePath) -> bool {
    bool found = false;
    for (std::string const& ext : this->BaseConst().HeaderExtensions) {
      std::string const testPath =
        this->Gen()->CollapseFullPathTS(cmStrCat(basePath, '.', ext));
      cmFileTime fileTime;
      if (!fileTime.Load(testPath)) {
        // File not found
        continue;
      }

      // Return a known file if it exists already
      {
        auto it = this->BaseEval().Headers.find(testPath);
        if (it != this->BaseEval().Headers.end()) {
          headerHandle = it->second;
          found = true;
          break;
        }
      }

      // Created and return discovered file entry
      {
        SourceFileHandleT& handle =
          this->MocEval().HeadersDiscovered[testPath];
        if (!handle) {
          handle = std::make_shared<SourceFileT>(testPath);
          handle->FileTime = fileTime;
          handle->IsHeader = true;
          handle->Moc = true;
        }
        headerHandle = handle;
        found = true;
        break;
      }
    }
    if (!found) {
      this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(basePath));
    }
    return found;
  };

  // Search in vicinity of the source
  if (findHeader(cmStrCat(includerDir, includeBase))) {
    return true;
  }
  // Search in include directories
  auto const& includePaths = this->MocConst().IncludePaths;
  return std::any_of(
    includePaths.begin(), includePaths.end(),
    [&findHeader, &includeBase](std::string const& path) -> bool {
      return findHeader(cmStrCat(path, '/', includeBase));
    });
}

bool cmQtAutoMocUicT::JobEvalCacheMocT::RegisterIncluded(
  std::string const& includeString, SourceFileHandleT includerFileHandle,
  SourceFileHandleT sourceFileHandle) const
{
  // Check if this file is already included
  MappingHandleT& handle = this->MocEval().Includes[includeString];
  if (handle) {
    // Check if the output file would be generated from different source files
    if (handle->SourceFile != sourceFileHandle) {
      std::string files =
        cmStrCat("  ", this->MessagePath(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        files += cmStrCat("  ", this->MessagePath(item->FileName), '\n');
      }
      this->LogError(
        GenT::MOC,
        cmStrCat("The source files\n", files,
                 "contain the same include string ",
                 this->MessagePath(includeString),
                 ", but\nthe moc file would be generated from different "
                 "source files\n  ",
                 this->MessagePath(sourceFileHandle->FileName), " and\n  ",
                 this->MessagePath(handle->SourceFile->FileName),
                 ".\nConsider to\n"
                 "  - not include the \"moc_<NAME>.cpp\" file\n"
                 "  - add a directory prefix to a \"<NAME>.moc\" include "
                 "(e.g \"sub/<NAME>.moc\")\n"
                 "  - rename the source file(s)\n"));
      return false;
    }

    // The same mapping already exists. Just add to the includers list.
    handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
    return true;
  }

  // Create a new mapping
  handle = std::make_shared<MappingT>();
  handle->IncludeString = includeString;
  handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
  handle->SourceFile = std::move(sourceFileHandle);
  handle->OutputFile = this->Gen()->AbsoluteIncludePath(includeString);

  // Register mapping in sources/headers map
  this->RegisterMapping(handle);
  return true;
}

void cmQtAutoMocUicT::JobEvalCacheMocT::RegisterMapping(
  MappingHandleT mappingHandle) const
{
  auto& regMap = mappingHandle->SourceFile->IsHeader
    ? this->MocEval().HeaderMappings
    : this->MocEval().SourceMappings;
  // Check if source file already gets mapped
  auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
  if (!regHandle) {
    // Yet unknown mapping
    regHandle = std::move(mappingHandle);
  } else {
    // Mappings with include string override those without
    if (!mappingHandle->IncludeString.empty()) {
      regHandle = std::move(mappingHandle);
    }
  }
}

std::string cmQtAutoMocUicT::JobEvalCacheMocT::MessageHeader(
  cm::string_view headerBase) const
{
  return this->MessagePath(cmStrCat(
    headerBase, ".{", cmJoin(this->BaseConst().HeaderExtensions, ","), '}'));
}

void cmQtAutoMocUicT::JobEvalCacheUicT::Process()
{
  // Prepare buffers
  this->SearchLocations.reserve((this->UicConst().SearchPaths.size() + 1) * 2);

  // Evaluate headers
  for (auto const& pair : this->BaseEval().Headers) {
    if (!this->EvalFile(pair.second)) {
      return;
    }
  }
  // Evaluate sources
  for (auto const& pair : this->BaseEval().Sources) {
    if (!this->EvalFile(pair.second)) {
      return;
    }
  }
}

bool cmQtAutoMocUicT::JobEvalCacheUicT::EvalFile(
  SourceFileHandleT const& sourceFileHandle)
{
  SourceFileT const& sourceFile = *sourceFileHandle;
  auto const& Include = sourceFile.ParseData->Uic.Include;
  if (!sourceFile.Uic || Include.empty()) {
    return true;
  }

  std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
  return std::all_of(
    Include.begin(), Include.end(),
    [this, &sourceDirPrefix, &sourceFile,
     &sourceFileHandle](IncludeKeyT const& incKey) -> bool {
      // Find .ui file
      this->UiName = cmStrCat(incKey.Base, ".ui");
      if (!this->FindIncludedUi(sourceDirPrefix, incKey.Dir)) {
        this->LogError(
          GenT::UIC,
          cmStrCat(this->MessagePath(sourceFile.FileName),
                   "\nincludes the uic file ", this->MessagePath(incKey.Key),
                   ",\nbut the user interface file ",
                   this->MessagePath(this->UiName),
                   "\ncould not be found in the following directories\n",
                   this->MessageSearchLocations()));
        return false;
      }
      // Check if the file is skipped
      if (this->UicConst().skipped(this->UiFileHandle->FileName)) {
        return true;
      }
      // Register mapping
      return this->RegisterMapping(incKey.Key, sourceFileHandle);
    });
}

bool cmQtAutoMocUicT::JobEvalCacheUicT::FindIncludedUi(
  cm::string_view sourceDirPrefix, cm::string_view includePrefix)
{
  // Clear locations buffer
  this->SearchLocations.clear();

  auto findUi = [this](std::string const& testPath) -> bool {
    std::string const fullPath = this->Gen()->CollapseFullPathTS(testPath);
    cmFileTime fileTime;
    if (!fileTime.Load(fullPath)) {
      this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(fullPath));
      return false;
    }
    // .ui file found in files system!
    // Get or create .ui file handle
    SourceFileHandleT& handle = this->UicEval().UiFiles[fullPath];
    if (!handle) {
      // The file wasn't registered, yet
      handle = std::make_shared<SourceFileT>(fullPath);
      handle->FileTime = fileTime;
    }
    this->UiFileHandle = handle;
    return true;
  };

  // Vicinity of the source
  if (!includePrefix.empty()) {
    if (findUi(cmStrCat(sourceDirPrefix, includePrefix, this->UiName))) {
      return true;
    }
  }
  if (findUi(cmStrCat(sourceDirPrefix, this->UiName))) {
    return true;
  }
  // Additional AUTOUIC search paths
  auto const& searchPaths = this->UicConst().SearchPaths;
  if (!searchPaths.empty()) {
    for (std::string const& sPath : searchPaths) {
      if (findUi(cmStrCat(sPath, '/', this->UiName))) {
        return true;
      }
    }
    if (!includePrefix.empty()) {
      for (std::string const& sPath : searchPaths) {
        if (findUi(cmStrCat(sPath, '/', includePrefix, this->UiName))) {
          return true;
        }
      }
    }
  }

  return false;
}

bool cmQtAutoMocUicT::JobEvalCacheUicT::RegisterMapping(
  std::string const& includeString, SourceFileHandleT includerFileHandle)
{
  auto& Includes = this->Gen()->UicEval().Includes;
  auto it = Includes.find(includeString);
  if (it != Includes.end()) {
    MappingHandleT const& handle = it->second;
    if (handle->SourceFile != this->UiFileHandle) {
      // The output file already gets generated - from a different .ui file!
      std::string files =
        cmStrCat("  ", this->MessagePath(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        files += cmStrCat("  ", this->MessagePath(item->FileName), '\n');
      }
      this->LogError(
        GenT::UIC,
        cmStrCat(
          "The source files\n", files, "contain the same include string ",
          Quoted(includeString),
          ", but\nthe uic file would be generated from different "
          "user interface files\n  ",
          this->MessagePath(this->UiFileHandle->FileName), " and\n  ",
          this->MessagePath(handle->SourceFile->FileName),
          ".\nConsider to\n"
          "  - add a directory prefix to a \"ui_<NAME>.h\" include "
          "(e.g \"sub/ui_<NAME>.h\")\n"
          "  - rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" "
          "include(s)\n"));
      return false;
    }
    // Add includer file to existing mapping
    handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
  } else {
    // New mapping handle
    MappingHandleT handle = std::make_shared<MappingT>();
    handle->IncludeString = includeString;
    handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
    handle->SourceFile = this->UiFileHandle;
    handle->OutputFile = this->Gen()->AbsoluteIncludePath(includeString);
    // Register mapping
    Includes.emplace(includeString, std::move(handle));
  }
  return true;
}

void cmQtAutoMocUicT::JobEvalCacheFinishT::Process()
{
  // Add discovered header parse jobs
  this->Gen()->CreateParseJobs<JobParseHeaderT>(
    this->MocEval().HeadersDiscovered);

  // Add dependency probing jobs
  {
    // Add fence job to ensure all parsing has finished
    this->Gen()->WorkerPool().EmplaceJob<JobFenceT>();
    if (this->MocConst().Enabled) {
      this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsMocT>();
    }
    if (this->UicConst().Enabled) {
      this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsUicT>();
    }
    // Add probe finish job
    this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsFinishT>();
  }
}

void cmQtAutoMocUicT::JobProbeDepsMocT::Process()
{
  // Create moc header jobs
  for (auto const& pair : this->MocEval().HeaderMappings) {
    // Register if this mapping is a candidate for mocs_compilation.cpp
    bool const compFile = pair.second->IncludeString.empty();
    if (compFile) {
      this->MocEval().CompFiles.emplace_back(
        pair.second->SourceFile->BuildPath);
    }
    if (!this->Generate(pair.second, compFile)) {
      return;
    }
  }

  // Create moc source jobs
  for (auto const& pair : this->MocEval().SourceMappings) {
    if (!this->Generate(pair.second, false)) {
      return;
    }
  }
}

bool cmQtAutoMocUicT::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
                                                 bool compFile) const
{
  std::unique_ptr<std::string> reason;
  if (this->Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (this->Probe(*mapping, reason.get())) {
    // Register the parent directory for creation
    this->MocEval().OutputDirs.emplace(
      cmQtAutoGen::ParentDir(mapping->OutputFile));
    // Fetch the cache entry for the source file
    std::string const& sourceFile = mapping->SourceFile->FileName;
    ParseCacheT::GetOrInsertT cacheEntry =
      this->BaseEval().ParseCache.GetOrInsert(sourceFile);
    // Add moc job
    this->Gen()->WorkerPool().EmplaceJob<JobCompileMocT>(
      mapping, std::move(reason), std::move(cacheEntry.first));
    // Check if a moc job for a mocs_compilation.cpp entry was generated
    if (compFile) {
      this->MocEval().CompUpdated = true;
    }
  }
  return true;
}

bool cmQtAutoMocUicT::JobProbeDepsMocT::Probe(MappingT const& mapping,
                                              std::string* reason) const
{
  std::string const& sourceFile = mapping.SourceFile->FileName;
  std::string const& outputFile = mapping.OutputFile;

  // Test if the output file exists
  cmFileTime outputFileTime;
  if (!outputFileTime.Load(outputFile)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because it doesn't exist, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if any setting changed
  if (this->MocConst().SettingsChanged) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because the uic settings changed, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if the source file is newer
  if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because it's older than its source file, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if the moc_predefs file is newer
  if (!this->MocConst().PredefsFileAbs.empty()) {
    if (outputFileTime.Older(this->MocEval().PredefsTime)) {
      if (reason != nullptr) {
        *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                           ", because it's older than ",
                           this->MessagePath(this->MocConst().PredefsFileAbs),
                           ", from ", this->MessagePath(sourceFile));
      }
      return true;
    }
  }

  // Test if the moc executable is newer
  if (outputFileTime.Older(this->MocConst().ExecutableTime)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because it's older than the moc executable, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if a dependency file is newer
  {
    // Check dependency timestamps
    std::string const sourceDir = SubDirPrefix(sourceFile);
    auto& dependencies = mapping.SourceFile->ParseData->Moc.Depends;
    for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
      auto& dep = *it;

      // Find dependency file
      auto const depMatch = this->FindDependency(sourceDir, dep);
      if (depMatch.first.empty()) {
        if (reason != nullptr) {
          *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                             " from ", this->MessagePath(sourceFile),
                             ", because its dependency ",
                             this->MessagePath(dep), " vanished.");
        }
        dependencies.erase(it);
        this->BaseEval().ParseCacheChanged = true;
        return true;
      }

      // Test if dependency file is older
      if (outputFileTime.Older(depMatch.second)) {
        if (reason != nullptr) {
          *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                             ", because it's older than its dependency file ",
                             this->MessagePath(depMatch.first), ", from ",
                             this->MessagePath(sourceFile));
        }
        return true;
      }
    }
  }

  return false;
}

std::pair<std::string, cmFileTime>
cmQtAutoMocUicT::JobProbeDepsMocT::FindDependency(
  std::string const& sourceDir, std::string const& includeString) const
{
  using ResPair = std::pair<std::string, cmFileTime>;
  // moc's dependency file contains absolute paths
  if (this->MocConst().CanOutputDependencies) {
    ResPair res{ includeString, {} };
    if (res.second.Load(res.first)) {
      return res;
    }
    return {};
  }
  // Search in vicinity of the source
  {
    ResPair res{ sourceDir + includeString, {} };
    if (res.second.Load(res.first)) {
      return res;
    }
  }
  // Search in include directories
  for (std::string const& includePath : this->MocConst().IncludePaths) {
    ResPair res{ cmStrCat(includePath, '/', includeString), {} };
    if (res.second.Load(res.first)) {
      return res;
    }
  }
  // Return empty
  return ResPair();
}

void cmQtAutoMocUicT::JobProbeDepsUicT::Process()
{
  for (auto const& pair : this->Gen()->UicEval().Includes) {
    MappingHandleT const& mapping = pair.second;
    std::unique_ptr<std::string> reason;
    if (this->Log().Verbose()) {
      reason = cm::make_unique<std::string>();
    }
    if (!this->Probe(*mapping, reason.get())) {
      continue;
    }

    // Register the parent directory for creation
    this->UicEval().OutputDirs.emplace(
      cmQtAutoGen::ParentDir(mapping->OutputFile));
    // Add uic job
    this->Gen()->WorkerPool().EmplaceJob<JobCompileUicT>(mapping,
                                                         std::move(reason));
  }
}

bool cmQtAutoMocUicT::JobProbeDepsUicT::Probe(MappingT const& mapping,
                                              std::string* reason) const
{
  std::string const& sourceFile = mapping.SourceFile->FileName;
  std::string const& outputFile = mapping.OutputFile;

  // Test if the build file exists
  cmFileTime outputFileTime;
  if (!outputFileTime.Load(outputFile)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because it doesn't exist, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if the uic settings changed
  if (this->UicConst().SettingsChanged) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because the uic settings changed, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if the source file is newer
  if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         " because it's older than the source file ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  // Test if the uic executable is newer
  if (outputFileTime.Older(this->UicConst().ExecutableTime)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", this->MessagePath(outputFile),
                         ", because it's older than the uic executable, from ",
                         this->MessagePath(sourceFile));
    }
    return true;
  }

  return false;
}

void cmQtAutoMocUicT::JobProbeDepsFinishT::Process()
{
  // Create output directories
  {
    using StringSet = std::unordered_set<std::string>;
    auto createDirs = [this](GenT genType, StringSet const& dirSet) {
      for (std::string const& dirName : dirSet) {
        if (!cmSystemTools::MakeDirectory(dirName)) {
          this->LogError(genType,
                         cmStrCat("Creating directory ",
                                  this->MessagePath(dirName), " failed."));
          return;
        }
      }
    };
    if (this->MocConst().Enabled && this->UicConst().Enabled) {
      StringSet outputDirs = this->MocEval().OutputDirs;
      outputDirs.insert(this->UicEval().OutputDirs.begin(),
                        this->UicEval().OutputDirs.end());
      createDirs(GenT::GEN, outputDirs);
    } else if (this->MocConst().Enabled) {
      createDirs(GenT::MOC, this->MocEval().OutputDirs);
    } else if (this->UicConst().Enabled) {
      createDirs(GenT::UIC, this->UicEval().OutputDirs);
    }
  }

  if (this->MocConst().Enabled) {
    // Add mocs compilations job
    this->Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
  }

  if (!this->BaseConst().DepFile.empty()) {
    // Add job to merge dep files
    this->Gen()->WorkerPool().EmplaceJob<JobDepFilesMergeT>();
  }

  // Add finish job
  this->Gen()->WorkerPool().EmplaceJob<JobFinishT>();
}

void cmQtAutoMocUicT::JobCompileMocT::Process()
{
  std::string const& sourceFile = this->Mapping->SourceFile->FileName;
  std::string const& outputFile = this->Mapping->OutputFile;

  // Compose moc command
  std::vector<std::string> cmd;
  {
    // Reserve large enough
    cmd.reserve(this->MocConst().OptionsDefinitions.size() +
                this->MocConst().OptionsIncludes.size() +
                this->MocConst().OptionsExtra.size() + 16);
    cmd.push_back(this->MocConst().Executable);
    // Add definitions
    cm::append(cmd, this->MocConst().OptionsDefinitions);
    // Add includes
    cm::append(cmd, this->MocConst().OptionsIncludes);
    // Add predefs include
    if (!this->MocConst().PredefsFileAbs.empty()) {
      cmd.emplace_back("--include");
      cmd.push_back(this->MocConst().PredefsFileAbs);
    }
    // Add path prefix on demand
    if (this->MocConst().PathPrefix && this->Mapping->SourceFile->IsHeader) {
      for (std::string const& dir : this->MocConst().IncludePaths) {
        cm::string_view prefix = sourceFile;
        if (cmHasPrefix(prefix, dir)) {
          prefix.remove_prefix(dir.size());
          if (cmHasPrefix(prefix, '/')) {
            prefix.remove_prefix(1);
            auto slashPos = prefix.rfind('/');
            if (slashPos != cm::string_view::npos) {
              cmd.emplace_back("-p");
              cmd.emplace_back(prefix.substr(0, slashPos));
            } else {
              cmd.emplace_back("-p");
              cmd.emplace_back("./");
            }
            break;
          }
        }
      }
    }
    // Add extra options
    cm::append(cmd, this->MocConst().OptionsExtra);
    if (this->MocConst().CanOutputDependencies) {
      cmd.emplace_back("--output-dep-file");
    }
    // Add output file
    cmd.emplace_back("-o");
    cmd.push_back(outputFile);
    // Add source file
    cmd.push_back(sourceFile);
  }

  // Execute moc command
  cmWorkerPool::ProcessResultT result;
  if (!this->RunProcess(GenT::MOC, result, cmd, this->Reason.get())) {
    // Moc command failed
    std::string includers;
    if (!this->Mapping->IncluderFiles.empty()) {
      includers = "included by\n";
      for (auto const& item : this->Mapping->IncluderFiles) {
        includers += cmStrCat("  ", this->MessagePath(item->FileName), '\n');
      }
    }
    this->LogCommandError(GenT::MOC,
                          cmStrCat("The moc process failed to compile\n  ",
                                   this->MessagePath(sourceFile), "\ninto\n  ",
                                   this->MessagePath(outputFile), '\n',
                                   includers, result.ErrorMessage),
                          cmd, result.StdOut);
    return;
  }

  // Moc command success. Print moc output.
  if (!result.StdOut.empty()) {
    this->Log().Info(GenT::MOC, result.StdOut);
  }

  // Extract dependencies from the dep file moc generated for us
  if (this->MocConst().CanOutputDependencies) {
    const std::string depfile = outputFile + ".d";
    if (this->Log().Verbose()) {
      this->Log().Info(
        GenT::MOC, "Reading dependencies from " + this->MessagePath(depfile));
    }
    if (!cmSystemTools::FileExists(depfile)) {
      this->Log().Warning(GenT::MOC,
                          "Dependency file " + this->MessagePath(depfile) +
                            " does not exist.");
      return;
    }
    this->CacheEntry->Moc.Depends = dependenciesFromDepFile(depfile.c_str());
  }
}

void cmQtAutoMocUicT::JobCompileUicT::Process()
{
  std::string const& sourceFile = this->Mapping->SourceFile->FileName;
  std::string const& outputFile = this->Mapping->OutputFile;

  // Compose uic command
  std::vector<std::string> cmd;
  cmd.push_back(this->UicConst().Executable);
  {
    std::vector<std::string> allOpts = this->UicConst().Options;
    auto optionIt = this->UicConst().UiFiles.find(sourceFile);
    if (optionIt != this->UicConst().UiFiles.end()) {
      UicMergeOptions(allOpts, optionIt->second.Options,
                      (this->BaseConst().QtVersion.Major == 5));
    }
    cm::append(cmd, allOpts);
  }
  cmd.emplace_back("-o");
  cmd.emplace_back(outputFile);
  cmd.emplace_back(sourceFile);

  cmWorkerPool::ProcessResultT result;
  if (this->RunProcess(GenT::UIC, result, cmd, this->Reason.get())) {
    // Uic command success
    // Print uic output
    if (!result.StdOut.empty()) {
      this->Log().Info(GenT::UIC, result.StdOut);
    }
  } else {
    // Uic command failed
    std::string includers;
    for (auto const& item : this->Mapping->IncluderFiles) {
      includers += cmStrCat("  ", this->MessagePath(item->FileName), '\n');
    }
    this->LogCommandError(GenT::UIC,
                          cmStrCat("The uic process failed to compile\n  ",
                                   this->MessagePath(sourceFile), "\ninto\n  ",
                                   this->MessagePath(outputFile),
                                   "\nincluded by\n", includers,
                                   result.ErrorMessage),
                          cmd, result.StdOut);
  }
}

void cmQtAutoMocUicT::JobMocsCompilationT::Process()
{
  // Compose mocs compilation file content
  std::string content =
    "// This file is autogenerated. Changes will be overwritten.\n";

  if (this->MocEval().CompFiles.empty()) {
    // Placeholder content
    content += "// No files found that require moc or the moc files are "
               "included\n"
               "enum some_compilers { need_more_than_nothing };\n";
  } else {
    // Valid content
    const bool mc = this->BaseConst().MultiConfig;
    cm::string_view const wrapFront = mc ? "#include <" : "#include \"";
    cm::string_view const wrapBack = mc ? ">\n" : "\"\n";
    content += cmWrap(wrapFront, this->MocEval().CompFiles, wrapBack, "");
  }

  std::string const& compAbs = this->MocConst().CompFileAbs;
  if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
    // Actually write mocs compilation file
    if (this->Log().Verbose()) {
      this->Log().Info(
        GenT::MOC, "Generating MOC compilation " + this->MessagePath(compAbs));
    }
    if (!FileWrite(compAbs, content)) {
      this->LogError(GenT::MOC,
                     cmStrCat("Writing MOC compilation ",
                              this->MessagePath(compAbs), " failed."));
    }
  } else if (this->MocEval().CompUpdated) {
    // Only touch mocs compilation file
    if (this->Log().Verbose()) {
      this->Log().Info(
        GenT::MOC, "Touching MOC compilation " + this->MessagePath(compAbs));
    }
    if (!cmSystemTools::Touch(compAbs, false)) {
      this->LogError(GenT::MOC,
                     cmStrCat("Touching MOC compilation ",
                              this->MessagePath(compAbs), " failed."));
    }
  }
}

/*
 * Escapes paths for Ninja depfiles.
 * This is a re-implementation of what moc does when writing depfiles.
 */
std::string escapeDependencyPath(cm::string_view path)
{
  std::string escapedPath;
  escapedPath.reserve(path.size());
  const size_t s = path.size();
  int backslashCount = 0;
  for (size_t i = 0; i < s; ++i) {
    if (path[i] == '\\') {
      ++backslashCount;
    } else {
      if (path[i] == '$') {
        escapedPath.push_back('$');
      } else if (path[i] == '#') {
        escapedPath.push_back('\\');
      } else if (path[i] == ' ') {
        // Double the amount of written backslashes,
        // and add one more to escape the space.
        while (backslashCount-- >= 0) {
          escapedPath.push_back('\\');
        }
      }
      backslashCount = 0;
    }
    escapedPath.push_back(path[i]);
  }
  return escapedPath;
}

/*
 * Return the initial dependencies of the merged depfile.
 * Those are dependencies from the project files, not from moc runs.
 */
std::vector<std::string>
cmQtAutoMocUicT::JobDepFilesMergeT::initialDependencies() const
{
  std::vector<std::string> dependencies;
  dependencies.reserve(this->BaseConst().ListFiles.size() +
                       this->BaseEval().Headers.size() +
                       this->BaseEval().Sources.size());
  cm::append(dependencies, this->BaseConst().ListFiles);
  auto append_file_path =
    [&dependencies](const SourceFileMapT::value_type& p) {
      dependencies.push_back(p.first);
    };
  std::for_each(this->BaseEval().Headers.begin(),
                this->BaseEval().Headers.end(), append_file_path);
  std::for_each(this->BaseEval().Sources.begin(),
                this->BaseEval().Sources.end(), append_file_path);
  return dependencies;
}

void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
{
  if (this->Log().Verbose()) {
    this->Log().Info(
      GenT::MOC,
      cmStrCat("Merging MOC dependencies into ",
               this->MessagePath(this->BaseConst().DepFile.c_str())));
  }
  auto processDepFile =
    [](const std::string& mocOutputFile) -> std::vector<std::string> {
    std::string f = mocOutputFile + ".d";
    if (!cmSystemTools::FileExists(f)) {
      return {};
    }
    return dependenciesFromDepFile(f.c_str());
  };

  std::vector<std::string> dependencies = this->initialDependencies();
  ParseCacheT& parseCache = this->BaseEval().ParseCache;
  auto processMappingEntry = [&](const MappingMapT::value_type& m) {
    auto cacheEntry = parseCache.GetOrInsert(m.first);
    if (cacheEntry.first->Moc.Depends.empty()) {
      cacheEntry.first->Moc.Depends = processDepFile(m.second->OutputFile);
    }
    dependencies.insert(dependencies.end(),
                        cacheEntry.first->Moc.Depends.begin(),
                        cacheEntry.first->Moc.Depends.end());
  };

  std::for_each(this->MocEval().HeaderMappings.begin(),
                this->MocEval().HeaderMappings.end(), processMappingEntry);
  std::for_each(this->MocEval().SourceMappings.begin(),
                this->MocEval().SourceMappings.end(), processMappingEntry);

  // Remove duplicates to make the depfile smaller
  std::sort(dependencies.begin(), dependencies.end());
  dependencies.erase(std::unique(dependencies.begin(), dependencies.end()),
                     dependencies.end());

  // Add form files
  for (const auto& uif : this->UicEval().UiFiles) {
    dependencies.push_back(uif.first);
  }

  // Write the file
  cmsys::ofstream ofs;
  ofs.open(this->BaseConst().DepFile.c_str(),
           (std::ios::out | std::ios::binary | std::ios::trunc));
  if (!ofs) {
    this->LogError(GenT::GEN,
                   cmStrCat("Cannot open ",
                            this->MessagePath(this->BaseConst().DepFile),
                            " for writing."));
    return;
  }
  ofs << this->BaseConst().DepFileRuleName << ": \\\n";
  for (const std::string& file : dependencies) {
    ofs << '\t' << escapeDependencyPath(file) << " \\\n";
    if (!ofs.good()) {
      this->LogError(GenT::GEN,
                     cmStrCat("Writing depfile",
                              this->MessagePath(this->BaseConst().DepFile),
                              " failed."));
      return;
    }
  }

  // Add the CMake executable to re-new cache data if necessary.
  // Also, this is the last entry, so don't add a backslash.
  ofs << '\t' << escapeDependencyPath(this->BaseConst().CMakeExecutable)
      << '\n';
}

void cmQtAutoMocUicT::JobFinishT::Process()
{
  this->Gen()->AbortSuccess();
}

cmQtAutoMocUicT::cmQtAutoMocUicT()
  : cmQtAutoGenerator(GenT::GEN)
{
}
cmQtAutoMocUicT::~cmQtAutoMocUicT() = default;

bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
{
  // -- Required settings
  if (!info.GetBool("MULTI_CONFIG", this->BaseConst_.MultiConfig, true) ||
      !info.GetUInt("QT_VERSION_MAJOR", this->BaseConst_.QtVersion.Major,
                    true) ||
      !info.GetUInt("QT_VERSION_MINOR", this->BaseConst_.QtVersion.Minor,
                    true) ||
      !info.GetUInt("PARALLEL", this->BaseConst_.ThreadCount, false) ||
      !info.GetString("BUILD_DIR", this->BaseConst_.AutogenBuildDir, true) ||
      !info.GetStringConfig("INCLUDE_DIR", this->BaseConst_.AutogenIncludeDir,
                            true) ||
      !info.GetString("CMAKE_EXECUTABLE", this->BaseConst_.CMakeExecutable,
                      true) ||
      !info.GetStringConfig("PARSE_CACHE_FILE",
                            this->BaseConst_.ParseCacheFile, true) ||
      !info.GetString("DEP_FILE", this->BaseConst_.DepFile, false) ||
      !info.GetString("DEP_FILE_RULE_NAME", this->BaseConst_.DepFileRuleName,
                      false) ||
      !info.GetStringConfig("SETTINGS_FILE", this->SettingsFile_, true) ||
      !info.GetArray("CMAKE_LIST_FILES", this->BaseConst_.ListFiles, true) ||
      !info.GetArray("HEADER_EXTENSIONS", this->BaseConst_.HeaderExtensions,
                     true) ||
      !info.GetString("QT_MOC_EXECUTABLE", this->MocConst_.Executable,
                      false) ||
      !info.GetString("QT_UIC_EXECUTABLE", this->UicConst_.Executable,
                      false)) {
    return false;
  }

  // -- Checks
  if (!this->BaseConst_.CMakeExecutableTime.Load(
        this->BaseConst_.CMakeExecutable)) {
    return info.LogError(
      cmStrCat("The CMake executable ",
               this->MessagePath(this->BaseConst_.CMakeExecutable),
               " does not exist."));
  }

  // -- Evaluate values
  this->BaseConst_.ThreadCount =
    std::min(this->BaseConst_.ThreadCount, ParallelMax);
  this->WorkerPool_.SetThreadCount(this->BaseConst_.ThreadCount);

  // -- Moc
  if (!this->MocConst_.Executable.empty()) {
    // -- Moc is enabled
    this->MocConst_.Enabled = true;

    // -- Temporary buffers
    struct
    {
      std::vector<std::string> MacroNames;
      std::vector<std::string> DependFilters;
    } tmp;

    // -- Required settings
    if (!info.GetBool("MOC_RELAXED_MODE", this->MocConst_.RelaxedMode,
                      false) ||
        !info.GetBool("MOC_PATH_PREFIX", this->MocConst_.PathPrefix, true) ||
        !info.GetArray("MOC_SKIP", this->MocConst_.SkipList, false) ||
        !info.GetArrayConfig("MOC_DEFINITIONS", this->MocConst_.Definitions,
                             false) ||
        !info.GetArrayConfig("MOC_INCLUDES", this->MocConst_.IncludePaths,
                             false) ||
        !info.GetArray("MOC_OPTIONS", this->MocConst_.OptionsExtra, false) ||
        !info.GetStringConfig("MOC_COMPILATION_FILE",
                              this->MocConst_.CompFileAbs, true) ||
        !info.GetArray("MOC_PREDEFS_CMD", this->MocConst_.PredefsCmd, false) ||
        !info.GetStringConfig("MOC_PREDEFS_FILE",
                              this->MocConst_.PredefsFileAbs,
                              !this->MocConst_.PredefsCmd.empty()) ||
        !info.GetArray("MOC_MACRO_NAMES", tmp.MacroNames, true) ||
        !info.GetArray("MOC_DEPEND_FILTERS", tmp.DependFilters, false)) {
      return false;
    }

    // -- Evaluate settings
    for (std::string const& item : tmp.MacroNames) {
      this->MocConst_.MacroFilters.emplace_back(
        item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
    }
    // Can moc output dependencies or do we need to setup dependency filters?
    if (this->BaseConst_.QtVersion >= IntegerVersion(5, 15)) {
      this->MocConst_.CanOutputDependencies = true;
    } else {
      Json::Value const& val = info.GetValue("MOC_DEPEND_FILTERS");
      if (!val.isArray()) {
        return info.LogError("MOC_DEPEND_FILTERS JSON value is not an array.");
      }
      Json::ArrayIndex const arraySize = val.size();
      for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
        // Test entry closure
        auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
          if (!test) {
            info.LogError(
              cmStrCat("MOC_DEPEND_FILTERS filter ", ii, ": ", msg));
          }
          return !test;
        };

        Json::Value const& pairVal = val[ii];

        if (testEntry(pairVal.isArray(), "JSON value is not an array.") ||
            testEntry(pairVal.size() == 2, "JSON array size invalid.")) {
          return false;
        }

        Json::Value const& keyVal = pairVal[0u];
        Json::Value const& expVal = pairVal[1u];
        if (testEntry(keyVal.isString(),
                      "JSON value for keyword is not a string.") ||
            testEntry(expVal.isString(),
                      "JSON value for regular expression is not a string.")) {
          return false;
        }

        std::string const key = keyVal.asString();
        std::string const exp = expVal.asString();
        if (testEntry(!key.empty(), "Keyword is empty.") ||
            testEntry(!exp.empty(), "Regular expression is empty.")) {
          return false;
        }

        this->MocConst_.DependFilters.emplace_back(key, exp);
        if (testEntry(
              this->MocConst_.DependFilters.back().Exp.is_valid(),
              cmStrCat("Regular expression compilation failed.\nKeyword: ",
                       Quoted(key), "\nExpression: ", Quoted(exp)))) {
          return false;
        }
      }
    }
    // Check if moc executable exists (by reading the file time)
    if (!this->MocConst_.ExecutableTime.Load(this->MocConst_.Executable)) {
      return info.LogError(cmStrCat(
        "The moc executable ", this->MessagePath(this->MocConst_.Executable),
        " does not exist."));
    }
  }

  // -- Uic
  if (!this->UicConst_.Executable.empty()) {
    // Uic is enabled
    this->UicConst_.Enabled = true;

    // -- Required settings
    if (!info.GetArray("UIC_SKIP", this->UicConst_.SkipList, false) ||
        !info.GetArray("UIC_SEARCH_PATHS", this->UicConst_.SearchPaths,
                       false) ||
        !info.GetArrayConfig("UIC_OPTIONS", this->UicConst_.Options, false)) {
      return false;
    }
    // .ui files
    {
      Json::Value const& val = info.GetValue("UIC_UI_FILES");
      if (!val.isArray()) {
        return info.LogError("UIC_UI_FILES JSON value is not an array.");
      }
      Json::ArrayIndex const arraySize = val.size();
      for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
        // Test entry closure
        auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
          if (!test) {
            info.LogError(cmStrCat("UIC_UI_FILES entry ", ii, ": ", msg));
          }
          return !test;
        };

        Json::Value const& entry = val[ii];
        if (testEntry(entry.isArray(), "JSON value is not an array.") ||
            testEntry(entry.size() == 2, "JSON array size invalid.")) {
          return false;
        }

        Json::Value const& entryName = entry[0u];
        Json::Value const& entryOptions = entry[1u];
        if (testEntry(entryName.isString(),
                      "JSON value for name is not a string.") ||
            testEntry(entryOptions.isArray(),
                      "JSON value for options is not an array.")) {
          return false;
        }

        auto& uiFile = this->UicConst_.UiFiles[entryName.asString()];
        InfoT::GetJsonArray(uiFile.Options, entryOptions);
      }
    }

    // -- Evaluate settings
    // Check if uic executable exists (by reading the file time)
    if (!this->UicConst_.ExecutableTime.Load(this->UicConst_.Executable)) {
      return info.LogError(cmStrCat(
        "The uic executable ", this->MessagePath(this->UicConst_.Executable),
        " does not exist."));
    }
  }

  // -- Headers
  {
    Json::Value const& val = info.GetValue("HEADERS");
    if (!val.isArray()) {
      return info.LogError("HEADERS JSON value is not an array.");
    }
    Json::ArrayIndex const arraySize = val.size();
    for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
      // Test entry closure
      auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
        if (!test) {
          info.LogError(cmStrCat("HEADERS entry ", ii, ": ", msg));
        }
        return !test;
      };

      Json::Value const& entry = val[ii];
      if (testEntry(entry.isArray(), "JSON value is not an array.") ||
          testEntry(entry.size() == 4, "JSON array size invalid.")) {
        return false;
      }

      Json::Value const& entryName = entry[0u];
      Json::Value const& entryFlags = entry[1u];
      Json::Value const& entryConfigs = entry[2u];
      Json::Value const& entryBuild = entry[3u];
      if (testEntry(entryName.isString(),
                    "JSON value for name is not a string.") ||
          testEntry(entryFlags.isString(),
                    "JSON value for flags is not a string.") ||
          testEntry(entryConfigs.isNull() || entryConfigs.isArray(),
                    "JSON value for configs is not null or array.") ||
          testEntry(entryBuild.isString(),
                    "JSON value for build path is not a string.")) {
        return false;
      }

      std::string name = entryName.asString();
      std::string flags = entryFlags.asString();
      std::string build = entryBuild.asString();
      if (testEntry(flags.size() == 2, "Invalid flags string size")) {
        return false;
      }

      if (entryConfigs.isArray()) {
        bool configFound = false;
        Json::ArrayIndex const configArraySize = entryConfigs.size();
        for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) {
          Json::Value const& config = entryConfigs[ci];
          if (testEntry(config.isString(),
                        "JSON value in config array is not a string.")) {
            return false;
          }
          configFound = configFound || config.asString() == this->InfoConfig();
        }
        if (!configFound) {
          continue;
        }
      }

      cmFileTime fileTime;
      if (!fileTime.Load(name)) {
        return info.LogError(cmStrCat(
          "The header file ", this->MessagePath(name), " does not exist."));
      }

      SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
      sourceHandle->FileTime = fileTime;
      sourceHandle->IsHeader = true;
      sourceHandle->Moc = (flags[0] == 'M');
      sourceHandle->Uic = (flags[1] == 'U');
      if (sourceHandle->Moc && this->MocConst().Enabled) {
        if (build.empty()) {
          return info.LogError(
            cmStrCat("Header file ", ii, " build path is empty"));
        }
        sourceHandle->BuildPath = std::move(build);
      }
      this->BaseEval().Headers.emplace(std::move(name),
                                       std::move(sourceHandle));
    }
  }

  // -- Sources
  {
    Json::Value const& val = info.GetValue("SOURCES");
    if (!val.isArray()) {
      return info.LogError("SOURCES JSON value is not an array.");
    }
    Json::ArrayIndex const arraySize = val.size();
    for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
      // Test entry closure
      auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
        if (!test) {
          info.LogError(cmStrCat("SOURCES entry ", ii, ": ", msg));
        }
        return !test;
      };

      Json::Value const& entry = val[ii];
      if (testEntry(entry.isArray(), "JSON value is not an array.") ||
          testEntry(entry.size() == 3, "JSON array size invalid.")) {
        return false;
      }

      Json::Value const& entryName = entry[0u];
      Json::Value const& entryFlags = entry[1u];
      Json::Value const& entryConfigs = entry[2u];
      if (testEntry(entryName.isString(),
                    "JSON value for name is not a string.") ||
          testEntry(entryFlags.isString(),
                    "JSON value for flags is not a string.") ||
          testEntry(entryConfigs.isNull() || entryConfigs.isArray(),
                    "JSON value for configs is not null or array.")) {
        return false;
      }

      std::string name = entryName.asString();
      std::string flags = entryFlags.asString();
      if (testEntry(flags.size() == 2, "Invalid flags string size")) {
        return false;
      }

      if (entryConfigs.isArray()) {
        bool configFound = false;
        Json::ArrayIndex const configArraySize = entryConfigs.size();
        for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) {
          Json::Value const& config = entryConfigs[ci];
          if (testEntry(config.isString(),
                        "JSON value in config array is not a string.")) {
            return false;
          }
          configFound = configFound || config.asString() == this->InfoConfig();
        }
        if (!configFound) {
          continue;
        }
      }

      cmFileTime fileTime;
      if (!fileTime.Load(name)) {
        return info.LogError(cmStrCat(
          "The source file ", this->MessagePath(name), " does not exist."));
      }

      SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
      sourceHandle->FileTime = fileTime;
      sourceHandle->IsHeader = false;
      sourceHandle->Moc = (flags[0] == 'M');
      sourceHandle->Uic = (flags[1] == 'U');
      this->BaseEval().Sources.emplace(std::move(name),
                                       std::move(sourceHandle));
    }
  }

  // -- Init derived information
  // Moc variables
  if (this->MocConst().Enabled) {
    // Compose moc includes list
    {
      // Compute framework paths
      std::set<std::string> frameworkPaths;
      for (std::string const& path : this->MocConst().IncludePaths) {
        // Extract framework path
        if (cmHasLiteralSuffix(path, ".framework/Headers")) {
          // Go up twice to get to the framework root
          std::vector<std::string> pathComponents;
          cmSystemTools::SplitPath(path, pathComponents);
          frameworkPaths.emplace(cmSystemTools::JoinPath(
            pathComponents.begin(), pathComponents.end() - 2));
        }
      }
      // Reserve options
      this->MocConst_.OptionsIncludes.reserve(
        this->MocConst().IncludePaths.size() + frameworkPaths.size() * 2);
      // Append includes
      for (std::string const& path : this->MocConst().IncludePaths) {
        this->MocConst_.OptionsIncludes.emplace_back("-I" + path);
      }
      // Append framework includes
      for (std::string const& path : frameworkPaths) {
        this->MocConst_.OptionsIncludes.emplace_back("-F");
        this->MocConst_.OptionsIncludes.push_back(path);
      }
    }

    // Compose moc definitions list
    {
      this->MocConst_.OptionsDefinitions.reserve(
        this->MocConst().Definitions.size());
      for (std::string const& def : this->MocConst().Definitions) {
        this->MocConst_.OptionsDefinitions.emplace_back("-D" + def);
      }
    }
  }

  return true;
}

template <class JOBTYPE>
void cmQtAutoMocUicT::CreateParseJobs(SourceFileMapT const& sourceMap)
{
  cmFileTime const parseCacheTime = this->BaseEval().ParseCacheTime;
  ParseCacheT& parseCache = this->BaseEval().ParseCache;
  for (const auto& src : sourceMap) {
    // Get or create the file parse data reference
    ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first);
    src.second->ParseData = std::move(cacheEntry.first);
    // Create a parse job if the cache file was missing or is older
    if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) {
      this->BaseEval().ParseCacheChanged = true;
      this->WorkerPool().EmplaceJob<JOBTYPE>(src.second);
    }
  }
}

/** Concurrently callable implementation of cmSystemTools::CollapseFullPath */
std::string cmQtAutoMocUicT::CollapseFullPathTS(std::string const& path) const
{
  std::lock_guard<std::mutex> guard(this->CMakeLibMutex_);
  return cmSystemTools::CollapseFullPath(path,
                                         this->ProjectDirs().CurrentSource);
}

void cmQtAutoMocUicT::InitJobs()
{
  // Add moc_predefs.h job
  if (this->MocConst().Enabled && !this->MocConst().PredefsCmd.empty()) {
    this->WorkerPool().EmplaceJob<JobMocPredefsT>();
  }

  // Add header parse jobs
  this->CreateParseJobs<JobParseHeaderT>(this->BaseEval().Headers);
  // Add source parse jobs
  this->CreateParseJobs<JobParseSourceT>(this->BaseEval().Sources);

  // Add parse cache evaluations jobs
  {
    // Add a fence job to ensure all parsing has finished
    this->WorkerPool().EmplaceJob<JobFenceT>();
    if (this->MocConst().Enabled) {
      this->WorkerPool().EmplaceJob<JobEvalCacheMocT>();
    }
    if (this->UicConst().Enabled) {
      this->WorkerPool().EmplaceJob<JobEvalCacheUicT>();
    }
    // Add evaluate job
    this->WorkerPool().EmplaceJob<JobEvalCacheFinishT>();
  }
}

bool cmQtAutoMocUicT::Process()
{
  this->SettingsFileRead();
  this->ParseCacheRead();
  if (!this->CreateDirectories()) {
    return false;
  }
  this->InitJobs();
  if (!this->WorkerPool_.Process(this)) {
    return false;
  }
  if (this->JobError_) {
    return false;
  }
  if (!this->ParseCacheWrite()) {
    return false;
  }
  if (!this->SettingsFileWrite()) {
    return false;
  }
  return true;
}

void cmQtAutoMocUicT::SettingsFileRead()
{
  // Compose current settings strings
  {
    cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
    auto cha = [&cryptoHash](cm::string_view value) {
      cryptoHash.Append(value);
      cryptoHash.Append(";");
    };

    if (this->MocConst_.Enabled) {
      cryptoHash.Initialize();
      cha(this->MocConst().Executable);
      for (auto const& item : this->MocConst().OptionsDefinitions) {
        cha(item);
      }
      for (auto const& item : this->MocConst().OptionsIncludes) {
        cha(item);
      }
      for (auto const& item : this->MocConst().OptionsExtra) {
        cha(item);
      }
      for (auto const& item : this->MocConst().PredefsCmd) {
        cha(item);
      }
      for (auto const& filter : this->MocConst().DependFilters) {
        cha(filter.Key);
      }
      for (auto const& filter : this->MocConst().MacroFilters) {
        cha(filter.Key);
      }
      this->SettingsStringMoc_ = cryptoHash.FinalizeHex();
    }

    if (this->UicConst().Enabled) {
      cryptoHash.Initialize();
      cha(this->UicConst().Executable);
      std::for_each(this->UicConst().Options.begin(),
                    this->UicConst().Options.end(), cha);
      for (const auto& item : this->UicConst().UiFiles) {
        cha(item.first);
        auto const& opts = item.second.Options;
        std::for_each(opts.begin(), opts.end(), cha);
      }
      this->SettingsStringUic_ = cryptoHash.FinalizeHex();
    }
  }

  // Read old settings and compare
  {
    std::string content;
    if (cmQtAutoGenerator::FileRead(content, this->SettingsFile_)) {
      if (this->MocConst().Enabled) {
        if (this->SettingsStringMoc_ != SettingsFind(content, "moc")) {
          this->MocConst_.SettingsChanged = true;
        }
      }
      if (this->UicConst().Enabled) {
        if (this->SettingsStringUic_ != SettingsFind(content, "uic")) {
          this->UicConst_.SettingsChanged = true;
        }
      }
      // In case any setting changed remove the old settings file.
      // This triggers a full rebuild on the next run if the current
      // build is aborted before writing the current settings in the end.
      if (this->MocConst().SettingsChanged ||
          this->UicConst().SettingsChanged) {
        cmSystemTools::RemoveFile(this->SettingsFile_);
      }
    } else {
      // Settings file read failed
      if (this->MocConst().Enabled) {
        this->MocConst_.SettingsChanged = true;
      }
      if (this->UicConst().Enabled) {
        this->UicConst_.SettingsChanged = true;
      }
    }
  }
}

bool cmQtAutoMocUicT::SettingsFileWrite()
{
  // Only write if any setting changed
  if (this->MocConst().SettingsChanged || this->UicConst().SettingsChanged) {
    if (this->Log().Verbose()) {
      this->Log().Info(GenT::GEN,
                       cmStrCat("Writing the settings file ",
                                this->MessagePath(this->SettingsFile_)));
    }
    // Compose settings file content
    std::string content;
    {
      auto SettingAppend = [&content](cm::string_view key,
                                      cm::string_view value) {
        if (!value.empty()) {
          content += cmStrCat(key, ':', value, '\n');
        }
      };
      SettingAppend("moc", this->SettingsStringMoc_);
      SettingAppend("uic", this->SettingsStringUic_);
    }
    // Write settings file
    std::string error;
    if (!cmQtAutoGenerator::FileWrite(this->SettingsFile_, content, &error)) {
      this->Log().Error(GenT::GEN,
                        cmStrCat("Writing the settings file ",
                                 this->MessagePath(this->SettingsFile_),
                                 " failed.\n", error));
      // Remove old settings file to trigger a full rebuild on the next run
      cmSystemTools::RemoveFile(this->SettingsFile_);
      return false;
    }
  }
  return true;
}

void cmQtAutoMocUicT::ParseCacheRead()
{
  cm::string_view reason;
  // Don't read the cache if it is invalid
  if (!this->BaseEval().ParseCacheTime.Load(
        this->BaseConst().ParseCacheFile)) {
    reason = "Refreshing parse cache because it doesn't exist.";
  } else if (this->MocConst().SettingsChanged ||
             this->UicConst().SettingsChanged) {
    reason = "Refreshing parse cache because the settings changed.";
  } else if (this->BaseEval().ParseCacheTime.Older(
               this->BaseConst().CMakeExecutableTime)) {
    reason =
      "Refreshing parse cache because it is older than the CMake executable.";
  }

  if (!reason.empty()) {
    // Don't read but refresh the complete parse cache
    if (this->Log().Verbose()) {
      this->Log().Info(GenT::GEN, reason);
    }
    this->BaseEval().ParseCacheChanged = true;
  } else {
    // Read parse cache
    this->BaseEval().ParseCache.ReadFromFile(this->BaseConst().ParseCacheFile);
  }
}

bool cmQtAutoMocUicT::ParseCacheWrite()
{
  if (this->BaseEval().ParseCacheChanged) {
    if (this->Log().Verbose()) {
      this->Log().Info(
        GenT::GEN,
        cmStrCat("Writing the parse cache file ",
                 this->MessagePath(this->BaseConst().ParseCacheFile)));
    }
    if (!this->BaseEval().ParseCache.WriteToFile(
          this->BaseConst().ParseCacheFile)) {
      this->Log().Error(
        GenT::GEN,
        cmStrCat("Writing the parse cache file ",
                 this->MessagePath(this->BaseConst().ParseCacheFile),
                 " failed."));
      return false;
    }
  }
  return true;
}

bool cmQtAutoMocUicT::CreateDirectories()
{
  // Create AUTOGEN include directory
  if (!cmSystemTools::MakeDirectory(this->BaseConst().AutogenIncludeDir)) {
    this->Log().Error(
      GenT::GEN,
      cmStrCat("Creating the AUTOGEN include directory ",
               this->MessagePath(this->BaseConst().AutogenIncludeDir),
               " failed."));
    return false;
  }
  return true;
}

std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
  const char* filePath)
{
  auto const content = cmReadGccDepfile(filePath);
  if (!content || content->empty()) {
    return {};
  }

  // Moc outputs a depfile with exactly one rule.
  // Discard the rule and return the dependencies.
  return content->front().paths;
}

void cmQtAutoMocUicT::Abort(bool error)
{
  if (error) {
    this->JobError_.store(true);
  }
  this->WorkerPool_.Abort();
}

std::string cmQtAutoMocUicT::AbsoluteBuildPath(
  cm::string_view relativePath) const
{
  return cmStrCat(this->BaseConst().AutogenBuildDir, '/', relativePath);
}

std::string cmQtAutoMocUicT::AbsoluteIncludePath(
  cm::string_view relativePath) const
{
  return cmStrCat(this->BaseConst().AutogenIncludeDir, '/', relativePath);
}

} // End of unnamed namespace

bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config)
{
  return cmQtAutoMocUicT().Run(infoFile, config);
}
