/* 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 <set>
#include <utility>

#include <cm/memory>

#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmGeneratedFileStream.h"
#include "cmQtAutoGen.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cm_jsoncpp_value.h"
#include "cmsys/FStream.hxx"

#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_"

} // End of unnamed namespace

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

void cmQtAutoMocUic::ParseCacheT::FileT::Clear()
{
  Moc.Macro.clear();
  Moc.Include.Underscore.clear();
  Moc.Include.Dot.clear();
  Moc.Depends.clear();

  Uic.Include.clear();
  Uic.Depends.clear();
}

cmQtAutoMocUic::ParseCacheT::FileHandleT cmQtAutoMocUic::ParseCacheT::Get(
  std::string const& fileName) const
{
  auto it = Map_.find(fileName);
  if (it != Map_.end()) {
    return it->second;
  }
  return FileHandleT();
}

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

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

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

void cmQtAutoMocUic::ParseCacheT::Clear()
{
  Map_.clear();
}

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

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

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

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

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

std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const
{
  std::string res;
  const auto itB = MacroFilters.cbegin();
  const auto itE = 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;
}

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

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

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

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

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

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

void cmQtAutoMocUic::JobMocPredefsT::Process()
{
  // (Re)generate moc_predefs.h on demand
  std::unique_ptr<std::string> reason;
  if (Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (!Update(reason.get())) {
    return;
  }
  std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
  {
    cmWorkerPool::ProcessResultT result;
    {
      // Compose command
      std::vector<std::string> cmd = MocConst().PredefsCmd;
      // Add definitions
      cmAppend(cmd, MocConst().OptionsDefinitions);
      // Add includes
      cmAppend(cmd, MocConst().OptionsIncludes);
      // Execute command
      if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
        LogCommandError(GenT::MOC,
                        cmStrCat("The content generation command for ",
                                 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)) {
        LogError(
          GenT::MOC,
          cmStrCat("Writing ", MessagePath(predefsFileAbs), " failed."));
        return;
      }
    } else {
      // Touch to update the time stamp
      if (Log().Verbose()) {
        Log().Info(GenT::MOC, "Touching " + MessagePath(predefsFileAbs));
      }
      if (!cmSystemTools::Touch(predefsFileAbs, false)) {
        LogError(
          GenT::MOC,
          cmStrCat("Touching ", MessagePath(predefsFileAbs), " failed."));
        return;
      }
    }
  }

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

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

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

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

  return false;
}

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

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

void cmQtAutoMocUic::JobParseT::MocDependecies()
{
  if (MocConst().DependFilters.empty()) {
    return;
  }

  // Find dependency strings
  std::set<std::string> parseDepends;
  for (KeyExpT const& filter : MocConst().DependFilters) {
    // Run a simple find string check
    if (Content.find(filter.Key) == std::string::npos) {
      continue;
    }
    // Run the expensive regular expression check loop
    const char* contentChars = 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 = 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 cmQtAutoMocUic::JobParseT::MocIncludes()
{
  if (Content.find("moc") == std::string::npos) {
    return;
  }

  std::set<std::string> underscore;
  std::set<std::string> dot;
  {
    const char* contentChars = Content.c_str();
    cmsys::RegularExpression const& regExp = 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 = FileHandle->ParseData->Moc.Include;
  CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
  CreateKeys(Include.Dot, dot, 0);
}

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

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

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

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

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

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

bool cmQtAutoMocUic::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 (BaseConst().MultiConfig) {
      handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
    } else {
      handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
    }

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

  return true;
}

bool cmQtAutoMocUic::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 = 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)) {
    LogError(GenT::MOC,
             cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
                      Quoted(parseData.Macro), " macro, but does not include ",
                      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 (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
        LogError(GenT::MOC,
                 cmStrCat(MessagePath(sourceFile.FileName),
                          "\nincludes the moc file ", MessagePath(incKey.Key),
                          ",\nbut a header ", MessageHeader(headerBase),
                          "\ncould not be found "
                          "in the following directories\n",
                          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
      Log().Warning(
        GenT::MOC,
        cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
                 Quoted(parseData.Macro), " macro, but does not include ",
                 MessagePath(sourceBase + ".moc"), ".\nInstead it includes ",
                 MessagePath(incKey.Key), ".\nRunning moc on the source\n  ",
                 MessagePath(sourceFile.FileName), "!\nBetter include ",
                 MessagePath(sourceBase + ".moc"),
                 " for compatibility with regular mode.\n",
                 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));

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

    // Check if header is skipped
    if (MocConst().skipped(headerHandle->FileName)) {
      continue;
    }
    // Create mapping
    if (!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 (!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 (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
          LogError(
            GenT::MOC,
            cmStrCat(
              MessagePath(sourceFile.FileName), "\nincludes the moc file ",
              MessagePath(incKey.Key),
              ",\nwhich seems to be the moc file from a different source "
              "file.\nCMAKE_AUTOMOC_RELAXED_MODE:\nAlso a matching header ",
              MessageHeader(headerBase),
              "\ncould not be found in the following directories\n",
              MessageSearchLocations()));
          return false;
        }
      }
      // Check if header is skipped
      if (MocConst().skipped(headerHandle->FileName)) {
        continue;
      }
      // Issue a warning
      if (ownMoc && parseData.Macro.empty()) {
        Log().Warning(
          GenT::MOC,
          cmStrCat(MessagePath(sourceFile.FileName),
                   "\nincludes the moc file ", MessagePath(incKey.Key),
                   ", but does not contain a\n", MocConst().MacrosString(),
                   " macro.\nRunning moc on the header\n  ",
                   MessagePath(headerHandle->FileName), "!\nBetter include ",
                   MessagePath("moc_" + incKey.Base + ".cpp"),
                   " for a compatibility with regular mode.\n",
                   "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
      } else {
        Log().Warning(
          GenT::MOC,
          cmStrCat(MessagePath(sourceFile.FileName),
                   "\nincludes the moc file ", MessagePath(incKey.Key),
                   " instead of ", MessagePath("moc_" + incKey.Base + ".cpp"),
                   ".\nRunning moc on the header\n  ",
                   MessagePath(headerHandle->FileName), "!\nBetter include ",
                   MessagePath("moc_" + incKey.Base + ".cpp"),
                   " for compatibility with regular mode.\n",
                   "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
      }
      // Create mapping
      if (!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
        LogError(GenT::MOC,
                 cmStrCat(MessagePath(sourceFile.FileName),
                          "\nincludes the moc file ", MessagePath(incKey.Key),
                          ",\nwhich seems to be the moc file from a different "
                          "source file.\nThis is not supported.  Include ",
                          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()) {
        Log().Warning(GenT::MOC,
                      cmStrCat(MessagePath(sourceFile.FileName),
                               "\nincludes the moc file ",
                               MessagePath(incKey.Key),
                               ", but does not contain a ",
                               MocConst().MacrosString(), " macro."));
      }
      // Create mapping
      if (!RegisterIncluded(incKey.Key, source, source)) {
        return false;
      }
    }
  }

  return true;
}

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

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

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

      // Created and return discovered file entry
      {
        SourceFileHandleT& handle = 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(baseCollapsed));
    }
    return found;
  };

  // Search in vicinity of the source
  if (findHeader(cmStrCat(includerDir, includeBase))) {
    return true;
  }
  // Search in include directories
  for (std::string const& path : MocConst().IncludePaths) {
    if (findHeader(cmStrCat(path, '/', includeBase))) {
      return true;
    }
  }
  // Return without success
  return false;
}

bool cmQtAutoMocUic::JobEvalCacheMocT::RegisterIncluded(
  std::string const& includeString, SourceFileHandleT includerFileHandle,
  SourceFileHandleT sourceFileHandle) const
{
  // Check if this file is already included
  MappingHandleT& handle = 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("  ", MessagePath(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        files += cmStrCat("  ", MessagePath(item->FileName), '\n');
      }
      LogError(
        GenT::MOC,
        cmStrCat("The source files\n", files,
                 "contain the same include string ",
                 MessagePath(includeString),
                 ", but\nthe moc file would be generated from different "
                 "source files\n  ",
                 MessagePath(sourceFileHandle->FileName), " and\n  ",
                 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 = Gen()->AbsoluteIncludePath(includeString);

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

void cmQtAutoMocUic::JobEvalCacheMocT::RegisterMapping(
  MappingHandleT mappingHandle) const
{
  auto& regMap = mappingHandle->SourceFile->IsHeader
    ? MocEval().HeaderMappings
    : 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 cmQtAutoMocUic::JobEvalCacheMocT::MessageHeader(
  cm::string_view headerBase) const
{
  return MessagePath(cmStrCat(
    headerBase, ".{", cmJoin(this->BaseConst().HeaderExtensions, ","), '}'));
}

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

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

bool cmQtAutoMocUic::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);
  for (IncludeKeyT const& incKey : Include) {
    // Find .ui file
    UiName = cmStrCat(incKey.Base, ".ui");
    if (!FindIncludedUi(sourceDirPrefix, incKey.Dir)) {
      LogError(GenT::UIC,
               cmStrCat(MessagePath(sourceFile.FileName),
                        "\nincludes the uic file ", MessagePath(incKey.Key),
                        ",\nbut the user interface file ", MessagePath(UiName),
                        "\ncould not be found in the following directories\n",
                        MessageSearchLocations()));
      return false;
    }
    // Check if the file is skipped
    if (UicConst().skipped(UiFileHandle->FileName)) {
      continue;
    }
    // Register mapping
    if (!RegisterMapping(incKey.Key, sourceFileHandle)) {
      return false;
    }
  }

  return true;
}

bool cmQtAutoMocUic::JobEvalCacheUicT::FindIncludedUi(
  cm::string_view sourceDirPrefix, cm::string_view includePrefix)
{
  // Clear locations buffer
  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 (findUi(cmStrCat(sourceDirPrefix, UiName))) {
    return true;
  }
  if (!includePrefix.empty()) {
    if (findUi(cmStrCat(sourceDirPrefix, includePrefix, UiName))) {
      return true;
    }
  }
  // Additional AUTOUIC search paths
  auto const& searchPaths = UicConst().SearchPaths;
  if (!searchPaths.empty()) {
    for (std::string const& sPath : searchPaths) {
      if (findUi(cmStrCat(sPath, '/', UiName))) {
        return true;
      }
    }
    if (!includePrefix.empty()) {
      for (std::string const& sPath : searchPaths) {
        if (findUi(cmStrCat(sPath, '/', includePrefix, UiName))) {
          return true;
        }
      }
    }
  }

  return false;
}

bool cmQtAutoMocUic::JobEvalCacheUicT::RegisterMapping(
  std::string const& includeString, SourceFileHandleT includerFileHandle)
{
  auto& Includes = Gen()->UicEval().Includes;
  auto it = Includes.find(includeString);
  if (it != Includes.end()) {
    MappingHandleT const& handle = it->second;
    if (handle->SourceFile != UiFileHandle) {
      // The output file already gets generated - from a different .ui file!
      std::string files =
        cmStrCat("  ", MessagePath(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        files += cmStrCat("  ", MessagePath(item->FileName), '\n');
      }
      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  ",
          MessagePath(UiFileHandle->FileName), " and\n  ",
          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 = UiFileHandle;
    handle->OutputFile = Gen()->AbsoluteIncludePath(includeString);
    // Register mapping
    Includes.emplace(includeString, std::move(handle));
  }
  return true;
}

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

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

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

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

bool cmQtAutoMocUic::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
                                                bool compFile) const
{
  std::unique_ptr<std::string> reason;
  if (Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (Probe(*mapping, reason.get())) {
    // Register the parent directory for creation
    MocEval().OutputDirs.emplace(cmQtAutoGen::ParentDir(mapping->OutputFile));
    // Add moc job
    Gen()->WorkerPool().EmplaceJob<JobCompileMocT>(mapping, std::move(reason));
    // Check if a moc job for a mocs_compilation.cpp entry was generated
    if (compFile) {
      MocEval().CompUpdated = true;
    }
  }
  return true;
}

bool cmQtAutoMocUic::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 ", MessagePath(outputFile),
                 ", because it doesn't exist, from ", MessagePath(sourceFile));
    }
    return true;
  }

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

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

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

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

  // Test if a dependency file is newer
  {
    // Check dependency timestamps
    std::string const sourceDir = SubDirPrefix(sourceFile);
    for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) {
      // Find dependency file
      auto const depMatch = FindDependency(sourceDir, dep);
      if (depMatch.first.empty()) {
        Log().Warning(GenT::MOC,
                      cmStrCat(MessagePath(sourceFile), " depends on ",
                               MessagePath(dep),
                               " but the file does not exist."));
        continue;
      }
      // Test if dependency file is older
      if (outputFileTime.Older(depMatch.second)) {
        if (reason != nullptr) {
          *reason = cmStrCat("Generating ", MessagePath(outputFile),
                             ", because it's older than its dependency file ",
                             MessagePath(depMatch.first), ", from ",
                             MessagePath(sourceFile));
        }
        return true;
      }
    }
  }

  return false;
}

std::pair<std::string, cmFileTime>
cmQtAutoMocUic::JobProbeDepsMocT::FindDependency(
  std::string const& sourceDir, std::string const& includeString) const
{
  using ResPair = std::pair<std::string, cmFileTime>;
  // 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 : MocConst().IncludePaths) {
    ResPair res{ cmStrCat(includePath, '/', includeString), {} };
    if (res.second.Load(res.first)) {
      return res;
    }
  }
  // Return empty
  return ResPair();
}

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

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

bool cmQtAutoMocUic::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 ", MessagePath(outputFile),
                 ", because it doesn't exist, from ", MessagePath(sourceFile));
    }
    return true;
  }

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

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

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

  return false;
}

void cmQtAutoMocUic::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 ", MessagePath(dirName), " failed."));
          return;
        }
      }
    };
    if (MocConst().Enabled && UicConst().Enabled) {
      StringSet outputDirs = MocEval().OutputDirs;
      outputDirs.insert(UicEval().OutputDirs.begin(),
                        UicEval().OutputDirs.end());
      createDirs(GenT::GEN, outputDirs);
    } else if (MocConst().Enabled) {
      createDirs(GenT::MOC, MocEval().OutputDirs);
    } else if (UicConst().Enabled) {
      createDirs(GenT::UIC, UicEval().OutputDirs);
    }
  }

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

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

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

  // Compose moc command
  std::vector<std::string> cmd;
  {
    // Reserve large enough
    cmd.reserve(MocConst().OptionsDefinitions.size() +
                MocConst().OptionsIncludes.size() +
                MocConst().OptionsExtra.size() + 16);
    cmd.push_back(MocConst().Executable);
    // Add definitions
    cmAppend(cmd, MocConst().OptionsDefinitions);
    // Add includes
    cmAppend(cmd, MocConst().OptionsIncludes);
    // Add predefs include
    if (!MocConst().PredefsFileAbs.empty()) {
      cmd.emplace_back("--include");
      cmd.push_back(MocConst().PredefsFileAbs);
    }
    // Add path prefix on demand
    if (MocConst().PathPrefix && Mapping->SourceFile->IsHeader) {
      for (std::string const& dir : 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
    cmAppend(cmd, MocConst().OptionsExtra);
    // 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 (RunProcess(GenT::MOC, result, cmd, Reason.get())) {
    // Moc command success. Print moc output.
    if (!result.StdOut.empty()) {
      Log().Info(GenT::MOC, result.StdOut);
    }
  } else {
    // Moc command failed
    std::string includers;
    if (!Mapping->IncluderFiles.empty()) {
      includers = "included by\n";
      for (auto const& item : Mapping->IncluderFiles) {
        includers += cmStrCat("  ", MessagePath(item->FileName), '\n');
      }
    }
    LogCommandError(GenT::MOC,
                    cmStrCat("The moc process failed to compile\n  ",
                             MessagePath(sourceFile), "\ninto\n  ",
                             MessagePath(outputFile), '\n', includers,
                             result.ErrorMessage),
                    cmd, result.StdOut);
  }
}

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

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

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

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

  if (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 = BaseConst().MultiConfig;
    cm::string_view const wrapFront = mc ? "#include <" : "#include \"";
    cm::string_view const wrapBack = mc ? ">\n" : "\"\n";
    content += cmWrap(wrapFront, MocEval().CompFiles, wrapBack, "");
  }

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

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

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

bool cmQtAutoMocUic::InitFromInfo()
{
  // -- Required settings
  if (!InfoBool("MULTI_CONFIG", BaseConst_.MultiConfig, true) ||
      !InfoUInt("QT_VERSION_MAJOR", BaseConst_.QtVersionMajor, true) ||
      !InfoUInt("PARALLEL", BaseConst_.ThreadCount, false) ||
      !InfoString("BUILD_DIR", BaseConst_.AutogenBuildDir, true) ||
      !InfoStringConfig("INCLUDE_DIR", BaseConst_.AutogenIncludeDir, true) ||
      !InfoString("CMAKE_EXECUTABLE", BaseConst_.CMakeExecutable, true) ||
      !InfoStringConfig("PARSE_CACHE_FILE", BaseConst_.ParseCacheFile, true) ||
      !InfoStringConfig("SETTINGS_FILE", SettingsFile_, true) ||
      !InfoArray("HEADER_EXTENSIONS", BaseConst_.HeaderExtensions, true) ||
      !InfoString("QT_MOC_EXECUTABLE", MocConst_.Executable, false) ||
      !InfoString("QT_UIC_EXECUTABLE", UicConst_.Executable, false)) {
    return false;
  }

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

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

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

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

    // -- Required settings
    if (!InfoBool("MOC_RELAXED_MODE", MocConst_.RelaxedMode, false) ||
        !InfoBool("MOC_PATH_PREFIX", MocConst_.PathPrefix, true) ||
        !InfoArray("MOC_SKIP", MocConst_.SkipList, false) ||
        !InfoArrayConfig("MOC_DEFINITIONS", MocConst_.Definitions, false) ||
        !InfoArrayConfig("MOC_INCLUDES", MocConst_.IncludePaths, false) ||
        !InfoArray("MOC_OPTIONS", MocConst_.OptionsExtra, false) ||
        !InfoStringConfig("MOC_COMPILATION_FILE", MocConst_.CompFileAbs,
                          true) ||
        !InfoArray("MOC_PREDEFS_CMD", MocConst_.PredefsCmd, false) ||
        !InfoStringConfig("MOC_PREDEFS_FILE", MocConst_.PredefsFileAbs,
                          !MocConst_.PredefsCmd.empty()) ||
        !InfoArray("MOC_MACRO_NAMES", tmp.MacroNames, true) ||
        !InfoArray("MOC_DEPEND_FILTERS", tmp.DependFilters, false)) {
      return false;
    }

    // -- Evaluate settings
    for (std::string const& item : tmp.MacroNames) {
      MocConst_.MacroFilters.emplace_back(
        item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
    }
    // Dependency filters
    {
      Json::Value const& val = Info()["MOC_DEPEND_FILTERS"];
      if (!val.isArray()) {
        return LogInfoError("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 = [this, ii](bool test,
                                    cm::string_view message) -> bool {
          if (!test) {
            this->LogInfoError(
              cmStrCat("MOC_DEPEND_FILTERS filter ", ii, ": ", message));
          }
          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 (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
      return LogInfoError(cmStrCat("The moc executable ",
                                   MessagePath(MocConst_.Executable),
                                   " does not exist."));
    }
  }

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

    // -- Required settings
    if (!InfoArray("UIC_SKIP", UicConst_.SkipList, false) ||
        !InfoArray("UIC_SEARCH_PATHS", UicConst_.SearchPaths, false) ||
        !InfoArrayConfig("UIC_OPTIONS", UicConst_.Options, false)) {
      return false;
    }
    // .ui files
    {
      Json::Value const& val = Info()["UIC_UI_FILES"];
      if (!val.isArray()) {
        return LogInfoError("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 = [this, ii](bool test,
                                    cm::string_view message) -> bool {
          if (!test) {
            this->LogInfoError(
              cmStrCat("UIC_UI_FILES entry ", ii, ": ", message));
          }
          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 = UicConst_.UiFiles[entryName.asString()];
        JsonGetArray(uiFile.Options, entryOptions);
      }
    }

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

  // -- Headers
  {
    Json::Value const& val = Info()["HEADERS"];
    if (!val.isArray()) {
      return LogInfoError("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 = [this, ii](bool test, cm::string_view message) -> bool {
        if (!test) {
          this->LogInfoError(cmStrCat("HEADERS entry ", ii, ": ", message));
        }
        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& entryBuild = 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(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;
      }

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

      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 && MocConst().Enabled) {
        if (build.empty()) {
          return LogInfoError(
            cmStrCat("Header file ", ii, " build path is empty"));
        }
        sourceHandle->BuildPath = std::move(build);
      }
      BaseEval().Headers.emplace(std::move(name), std::move(sourceHandle));
    }
  }

  // -- Sources
  {
    Json::Value const& val = Info()["SOURCES"];
    if (!val.isArray()) {
      return LogInfoError("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 = [this, ii](bool test, cm::string_view message) -> bool {
        if (!test) {
          this->LogInfoError(cmStrCat("SOURCES entry ", ii, ": ", message));
        }
        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& entryFlags = entry[1u];
      if (testEntry(entryName.isString(),
                    "JSON value for name is not a string.") ||
          testEntry(entryFlags.isString(),
                    "JSON value for flags is not a string.")) {
        return false;
      }

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

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

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

  // -- Init derived information
  // Moc variables
  if (MocConst().Enabled) {
    // Compose moc includes list
    {
      // Compute framework paths
      std::set<std::string> frameworkPaths;
      for (std::string const& path : 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
      MocConst_.OptionsIncludes.reserve(MocConst().IncludePaths.size() +
                                        frameworkPaths.size() * 2);
      // Append includes
      for (std::string const& path : MocConst().IncludePaths) {
        MocConst_.OptionsIncludes.emplace_back("-I" + path);
      }
      // Append framework includes
      for (std::string const& path : frameworkPaths) {
        MocConst_.OptionsIncludes.emplace_back("-F");
        MocConst_.OptionsIncludes.push_back(path);
      }
    }

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

  return true;
}

template <class JOBTYPE>
void cmQtAutoMocUic::CreateParseJobs(SourceFileMapT const& sourceMap)
{
  cmFileTime const parseCacheTime = BaseEval().ParseCacheTime;
  ParseCacheT& parseCache = BaseEval().ParseCache;
  for (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)) {
      BaseEval().ParseCacheChanged = true;
      WorkerPool().EmplaceJob<JOBTYPE>(src.second);
    }
  }
}

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

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

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

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

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

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

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

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

  // Read old settings and compare
  {
    std::string content;
    if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) {
      if (MocConst().Enabled) {
        if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
          MocConst_.SettingsChanged = true;
        }
      }
      if (UicConst().Enabled) {
        if (SettingsStringUic_ != SettingsFind(content, "uic")) {
          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 (MocConst().SettingsChanged || UicConst().SettingsChanged) {
        cmSystemTools::RemoveFile(SettingsFile_);
      }
    } else {
      // Settings file read failed
      if (MocConst().Enabled) {
        MocConst_.SettingsChanged = true;
      }
      if (UicConst().Enabled) {
        UicConst_.SettingsChanged = true;
      }
    }
  }
}

bool cmQtAutoMocUic::SettingsFileWrite()
{
  // Only write if any setting changed
  if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
    if (Log().Verbose()) {
      Log().Info(
        GenT::GEN,
        cmStrCat("Writing the settings file ", MessagePath(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", SettingsStringMoc_);
      SettingAppend("uic", SettingsStringUic_);
    }
    // Write settings file
    std::string error;
    if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
      Log().Error(GenT::GEN,
                  cmStrCat("Writing the settings file ",
                           MessagePath(SettingsFile_), " failed.\n", error));
      // Remove old settings file to trigger a full rebuild on the next run
      cmSystemTools::RemoveFile(SettingsFile_);
      return false;
    }
  }
  return true;
}

void cmQtAutoMocUic::ParseCacheRead()
{
  cm::string_view reason;
  // Don't read the cache if it is invalid
  if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) {
    reason = "Refreshing parse cache because it doesn't exist.";
  } else if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
    reason = "Refreshing parse cache because the settings changed.";
  } else if (BaseEval().ParseCacheTime.Older(
               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 (Log().Verbose()) {
      Log().Info(GenT::GEN, reason);
    }
    BaseEval().ParseCacheChanged = true;
  } else {
    // Read parse cache
    BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile);
  }
}

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

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

void cmQtAutoMocUic::Abort(bool error)
{
  if (error) {
    JobError_.store(true);
  }
  WorkerPool_.Abort();
}

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

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