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

#include "cm_memory.hxx"

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

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

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

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,
                                    std::string const& message) const
{
  Gen()->AbortError();
  Gen()->Log().Error(genType, message);
}

void cmQtAutoMocUic::JobT::LogFileError(GenT genType,
                                        std::string const& filename,
                                        std::string const& message) const
{
  Gen()->AbortError();
  Gen()->Log().ErrorFile(genType, filename, message);
}

void cmQtAutoMocUic::JobT::LogCommandError(
  GenT genType, std::string const& 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()) {
    std::string msg;
    if ((infoMessage != nullptr) && !infoMessage->empty()) {
      msg = *infoMessage;
      if (msg.back() != '\n') {
        msg += '\n';
      }
    }
    msg += QuotedCommand(command);
    msg += '\n';
    Log().Info(genType, msg);
  }
  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& predefsFileRel = MocConst().PredefsFileRel;
  std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
  {
    cmWorkerPool::ProcessResultT result;
    {
      // Compose command
      std::vector<std::string> cmd = MocConst().PredefsCmd;
      // Add includes
      cmAppend(cmd, MocConst().Includes);
      // Add definitions
      for (std::string const& def : MocConst().Definitions) {
        cmd.emplace_back("-D" + def);
      }
      // Execute command
      if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
        std::string msg =
          cmStrCat("The content generation command for ",
                   Quoted(predefsFileRel), " failed.\n", result.ErrorMessage);
        LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
        return;
      }
    }

    // (Re)write predefs file only on demand
    if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
      if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
        std::string msg =
          cmStrCat("Writing ", Quoted(predefsFileRel), " failed.");
        LogFileError(GenT::MOC, predefsFileAbs, msg);
        return;
      }
    } else {
      // Touch to update the time stamp
      if (Log().Verbose()) {
        Log().Info(GenT::MOC, "Touching " + Quoted(predefsFileRel));
      }
      if (!cmSystemTools::Touch(predefsFileAbs, false)) {
        std::string msg =
          cmStrCat("Touching ", Quoted(predefsFileAbs), " failed.");
        LogFileError(GenT::MOC, predefsFileAbs, msg);
        return;
      }
    }
  }

  // Read file time afterwards
  if (!MocEval().PredefsTime.Load(predefsFileAbs)) {
    LogFileError(GenT::MOC, predefsFileAbs, "File time reading 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 ", Quoted(MocConst().PredefsFileRel),
                         ", because it doesn't exist.");
    }
    return true;
  }

  // Test if the settings changed
  if (MocConst().SettingsChanged) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", Quoted(MocConst().PredefsFileRel),
                         ", 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 ", Quoted(MocConst().PredefsFileRel),
                             " because it is older than ", Quoted(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, "Parsing " + Quoted(fileName));
  }
  // Read file content
  {
    std::string error;
    if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) {
      LogFileError(GenT::GEN, fileName, "Could not read the file: " + error);
      return false;
    }
  }
  // Warn if empty
  if (Content.empty()) {
    Log().WarningFile(GenT::GEN, fileName, "The file 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();
  }
}

void cmQtAutoMocUic::JobEvaluateT::Process()
{
  // Evaluate for moc
  if (MocConst().Enabled) {
    // Evaluate headers
    for (auto const& pair : BaseEval().Headers) {
      if (!MocEvalHeader(pair.second)) {
        return;
      }
    }
    // Evaluate sources
    for (auto const& pair : BaseEval().Sources) {
      if (!MocEvalSource(pair.second)) {
        return;
      }
    }
  }
  // Evaluate for uic
  if (UicConst().Enabled) {
    if (!UicEval(BaseEval().Headers) || !UicEval(BaseEval().Sources)) {
      return;
    }
  }

  // Add discovered header parse jobs
  Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered);
  // Add generate job after
  Gen()->WorkerPool().EmplaceJob<JobGenerateT>();
}

bool cmQtAutoMocUic::JobEvaluateT::MocEvalHeader(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
    MocRegisterMapping(handle, true);
  }

  return true;
}

bool cmQtAutoMocUic::JobEvaluateT::MocEvalSource(
  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 sourceDir = 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)) {
    {
      std::string emsg =
        cmStrCat("The file contains a ", Quoted(parseData.Macro),
                 " macro, but does not include ", Quoted(sourceBase + ".moc"),
                 "!\nConsider to\n  - add #include \"", sourceBase,
                 ".moc\"\n  - enable SKIP_AUTOMOC for this file");
      LogFileError(GenT::MOC, sourceFile.FileName, emsg);
    }
    return false;
  }

  // Evaluate "moc_" includes
  for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
    std::string const headerBase = incKey.Dir + incKey.Base;
    SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
    if (!header) {
      {
        std::string msg =
          cmStrCat("The file includes the moc file ", Quoted(incKey.Key),
                   ",\nbut the header could not be found "
                   "in the following locations\n",
                   MocMessageTestHeaders(headerBase));
        LogFileError(GenT::MOC, sourceFile.FileName, msg);
      }
      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
        std::string msg = cmStrCat(
          "The file contains a ", Quoted(parseData.Macro),
          " macro, but does not include ", Quoted(sourceBase + ".moc"),
          ".\nInstead it includes ", Quoted(incKey.Key),
          ".\nRunning moc on the source\n  ", Quoted(sourceFile.FileName),
          "!\nBetter include ", Quoted(sourceBase + ".moc"),
          " for compatibility with regular mode.\n",
          "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n");
        Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
      }
      // Create mapping
      if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
        return false;
      }
      continue;
    }

    // Check if header is skipped
    if (MocConst().skipped(header->FileName)) {
      continue;
    }
    // Create mapping
    if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
      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 (!MocRegisterIncluded(incKey.Key, source, source, false)) {
          return false;
        }
        continue;
      }
      // Try to find a header instead but issue a warning.
      // This is for KDE4 compatibility.
      std::string const headerBase = incKey.Dir + incKey.Base;
      SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
      if (!header) {
        std::string msg =
          cmStrCat("The file includes the moc file ", Quoted(incKey.Key),
                   ",\nwhich seems to be the moc file from a different source "
                   "file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a matching header"
                   "could not be found in the following locations\n",
                   MocMessageTestHeaders(headerBase));
        LogFileError(GenT::MOC, sourceFile.FileName, msg);
        return false;
      }
      // Check if header is skipped
      if (MocConst().skipped(header->FileName)) {
        continue;
      }
      // Issue a warning
      if (ownMoc && parseData.Macro.empty()) {
        std::string msg = cmStrCat(
          "The file includes the moc file ", Quoted(incKey.Key),
          ", but does not contain a\n", MocConst().MacrosString(),
          " macro.\nRunning moc on the header\n  ", Quoted(header->FileName),
          "!\nBetter include ", Quoted("moc_" + incKey.Base + ".cpp"),
          " for a compatibility with regular mode.\n",
          "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n");
        Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
      } else {
        std::string msg = cmStrCat(
          "The file includes the moc file ", Quoted(incKey.Key),
          " instead of ", Quoted("moc_" + incKey.Base + ".cpp"),
          ".\nRunning moc on the header\n  ", Quoted(header->FileName),
          "!\nBetter include ", Quoted("moc_" + incKey.Base + ".cpp"),
          " for compatibility with regular mode.\n",
          "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n");
        Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
      }
      // Create mapping
      if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
        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
        std::string msg = cmStrCat(
          "The file includes the moc file ", Quoted(incKey.Key),
          ",\nwhich seems to be the moc file from a different "
          "source file.\nThis is not supported.  Include ",
          Quoted(sourceBase + ".moc"), " to run moc on this source file.");
        LogFileError(GenT::MOC, sourceFile.FileName, msg);
        return false;
      }
      // Accept but issue a warning if moc isn't required
      if (parseData.Macro.empty()) {
        std::string msg = cmStrCat(
          "The file includes the moc file ", Quoted(incKey.Key),
          ", but does not contain a ", MocConst().MacrosString(), " macro.");
        Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
      }
      // Create mapping
      if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
        return false;
      }
    }
  }

  return true;
}

cmQtAutoMocUic::SourceFileHandleT
cmQtAutoMocUic::JobEvaluateT::MocFindIncludedHeader(
  std::string const& includerDir, std::string const& includeBase) const
{
  // Search in vicinity of the source
  {
    SourceFileHandleT res = MocFindHeader(includerDir + includeBase);
    if (res) {
      return res;
    }
  }
  // Search in include directories
  for (std::string const& path : MocConst().IncludePaths) {
    std::string testPath = cmStrCat(path, '/', includeBase);
    SourceFileHandleT res = MocFindHeader(testPath);
    if (res) {
      return res;
    }
  }
  // Return without success
  return SourceFileHandleT();
}

cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindHeader(
  std::string const& basePath) const
{
  std::string testPath;
  testPath.reserve(basePath.size() + 8);
  for (std::string const& ext : BaseConst().HeaderExtensions) {
    testPath.clear();
    testPath += basePath;
    testPath += '.';
    testPath += ext;
    cmFileTime fileTime;
    if (fileTime.Load(testPath)) {
      // Compute real path of the file
      testPath = cmSystemTools::GetRealPath(testPath);
      // Return a known file if it exists already
      {
        auto it = BaseEval().Headers.find(testPath);
        if (it != BaseEval().Headers.end()) {
          return it->second;
        }
      }
      // Created and return discovered file entry
      SourceFileHandleT& res = MocEval().HeadersDiscovered[testPath];
      if (!res) {
        res = std::make_shared<SourceFileT>(testPath);
        res->FileTime = fileTime;
        res->Moc = true;
      }
      return res;
    }
  }
  // Return without success
  return SourceFileHandleT();
}

std::string cmQtAutoMocUic::JobEvaluateT::MocMessageTestHeaders(
  std::string const& fileBase) const
{
  std::ostringstream res;
  {
    std::string exts =
      cmStrCat(".{", cmJoin(BaseConst().HeaderExtensions, ","),
               '}'); // Compose result string
    res << "  " << fileBase << exts << '\n';
    for (std::string const& path : MocConst().IncludePaths) {
      res << "  " << path << '/' << fileBase << exts << '\n';
    }
  }
  return res.str();
}

bool cmQtAutoMocUic::JobEvaluateT::MocRegisterIncluded(
  std::string const& includeString, SourceFileHandleT includerFileHandle,
  SourceFileHandleT sourceFileHandle, bool sourceIsHeader) 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 msg = cmStrCat("The source files\n  ",
                                 Quoted(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        msg += "  ";
        msg += Quoted(item->FileName);
        msg += '\n';
      }
      msg += "contain the same include string ";
      msg += Quoted(includeString);
      msg += ", but\nthe moc file would be generated from different "
             "source files\n  ";
      msg += Quoted(sourceFileHandle->FileName);
      msg += " and\n  ";
      msg += Quoted(handle->SourceFile->FileName);
      msg += ".\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";
      LogError(GenT::MOC, msg);
      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
  MocRegisterMapping(handle, sourceIsHeader);
  return true;
}

void cmQtAutoMocUic::JobEvaluateT::MocRegisterMapping(
  MappingHandleT mappingHandle, bool sourceIsHeader) const
{
  auto& regMap =
    sourceIsHeader ? 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);
    }
  }
}

bool cmQtAutoMocUic::JobEvaluateT::UicEval(SourceFileMapT const& fileMap)
{
  for (auto const& pair : fileMap) {
    if (!UicEvalFile(pair.second)) {
      return false;
    }
  }
  return true;
}

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

  std::string const sourceDir = SubDirPrefix(sourceFile.FileName);
  for (IncludeKeyT const& incKey : Include) {
    // Find .ui file name
    SourceFileHandleT uiFileHandle =
      UicFindIncludedUi(sourceFile.FileName, sourceDir, incKey);
    if (!uiFileHandle || UicConst().skipped(uiFileHandle->FileName)) {
      continue;
    }
    // Register mapping
    if (!UicRegisterMapping(incKey.Key, std::move(uiFileHandle),
                            std::move(sourceFileHandle))) {
      return false;
    }
  }

  return true;
}

bool cmQtAutoMocUic::JobEvaluateT::UicRegisterMapping(
  std::string const& includeString, SourceFileHandleT uiFileHandle,
  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 msg = cmStrCat("The source files\n  ",
                                 Quoted(includerFileHandle->FileName), '\n');
      for (auto const& item : handle->IncluderFiles) {
        msg += "  ";
        msg += Quoted(item->FileName);
        msg += '\n';
      }
      msg += "contain the same include string ";
      msg += Quoted(includeString);
      msg += ", but\nthe uic file would be generated from different "
             "user interface files\n  ";
      msg += Quoted(uiFileHandle->FileName);
      msg += " and\n  ";
      msg += Quoted(handle->SourceFile->FileName);
      msg += ".\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";
      LogError(GenT::UIC, msg);
      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 = std::move(uiFileHandle);
    handle->OutputFile += Gen()->AbsoluteIncludePath(includeString);
    // Register mapping
    Includes.emplace(includeString, std::move(handle));
  }
  return true;
}

cmQtAutoMocUic::SourceFileHandleT
cmQtAutoMocUic::JobEvaluateT::UicFindIncludedUi(
  std::string const& sourceFile, std::string const& sourceDir,
  IncludeKeyT const& incKey) const
{
  std::string searchFileName = cmStrCat(incKey.Base, ".ui");
  // Collect search paths list
  std::vector<std::string> testFiles;
  {
    auto& searchPaths = UicConst().SearchPaths;
    testFiles.reserve((searchPaths.size() + 1) * 2);

    // Vicinity of the source
    testFiles.emplace_back(sourceDir + searchFileName);
    if (!incKey.Dir.empty()) {
      testFiles.emplace_back(cmStrCat(sourceDir, incKey.Dir, searchFileName));
    }
    // AUTOUIC search paths
    if (!searchPaths.empty()) {
      for (std::string const& sPath : searchPaths) {
        testFiles.emplace_back(cmStrCat(sPath, '/', searchFileName));
      }
      if (!incKey.Dir.empty()) {
        for (std::string const& sPath : searchPaths) {
          testFiles.emplace_back(
            cmStrCat(sPath, '/', incKey.Dir, searchFileName));
        }
      }
    }
  }

  // Search for the .ui file!
  for (std::string const& testFile : testFiles) {
    cmFileTime fileTime;
    if (fileTime.Load(testFile)) {
      // .ui file found in files system!
      std::string realPath = cmSystemTools::GetRealPath(testFile);
      // Get or create .ui file handle
      SourceFileHandleT& handle = Gen()->UicEval().UiFiles[realPath];
      if (!handle) {
        // The file wasn't registered, yet
        handle = std::make_shared<SourceFileT>(realPath);
        handle->FileTime = fileTime;
      }
      return handle;
    }
  }

  // Log error
  {
    std::string msg =
      cmStrCat("The file includes the uic file ", Quoted(incKey.Key),
               ",\nbut the user interface file ", Quoted(searchFileName),
               "\ncould not be found in the following locations\n");
    for (std::string const& testFile : testFiles) {
      msg += "  ";
      msg += Quoted(testFile);
      msg += '\n';
    }
    LogFileError(GenT::UIC, sourceFile, msg);
  }

  return SourceFileHandleT();
}

void cmQtAutoMocUic::JobGenerateT::Process()
{
  // Add moc compile jobs
  if (MocConst().Enabled) {
    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 (!MocGenerate(pair.second, compFile)) {
        return;
      }
    }
    for (auto const& pair : MocEval().SourceMappings) {
      if (!MocGenerate(pair.second, false)) {
        return;
      }
    }

    // Add mocs compilations job on demand
    Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
  }

  // Add uic compile jobs
  if (UicConst().Enabled) {
    for (auto const& pair : Gen()->UicEval().Includes) {
      if (!UicGenerate(pair.second)) {
        return;
      }
    }
  }

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

bool cmQtAutoMocUic::JobGenerateT::MocGenerate(MappingHandleT const& mapping,
                                               bool compFile) const
{
  std::unique_ptr<std::string> reason;
  if (Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (MocUpdate(*mapping, reason.get())) {
    // Create the parent directory
    if (!MakeParentDirectory(mapping->OutputFile)) {
      LogFileError(GenT::MOC, mapping->OutputFile,
                   "Could not create parent directory.");
      return false;
    }
    // Add moc job
    Gen()->WorkerPool().EmplaceJob<JobMocT>(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::JobGenerateT::MocUpdate(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 ", Quoted(outputFile),
                 ", because it doesn't exist, from ", Quoted(sourceFile));
    }
    return true;
  }

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

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

  // Test if the moc executable is newer
  if (outputFileTime.Older(MocConst().ExecutableTime)) {
    if (reason != nullptr) {
      *reason = cmStrCat("Generating ", Quoted(outputFile),
                         ", because it's older than the moc executable, from ",
                         Quoted(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 = MocFindDependency(sourceDir, dep);
      if (depMatch.first.empty()) {
        Log().WarningFile(GenT::MOC, sourceFile,
                          "Could not find dependency file " + Quoted(dep));
        continue;
      }
      // Test if dependency file is older
      if (outputFileTime.Older(depMatch.second)) {
        if (reason != nullptr) {
          *reason =
            cmStrCat("Generating ", Quoted(outputFile),
                     ", because it's older than its dependency file ",
                     Quoted(depMatch.first), ", from ", Quoted(sourceFile));
        }
        return true;
      }
    }
  }

  return false;
}

std::pair<std::string, cmFileTime>
cmQtAutoMocUic::JobGenerateT::MocFindDependency(
  std::string const& sourceDir, std::string const& includeString) const
{
  typedef std::pair<std::string, cmFileTime> ResPair;
  // 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{ includePath, {} };
    res.first += '/';
    res.first += includeString;
    if (res.second.Load(res.first)) {
      return res;
    }
  }
  // Return empty
  return ResPair();
}

bool cmQtAutoMocUic::JobGenerateT::UicGenerate(
  MappingHandleT const& mapping) const
{
  std::unique_ptr<std::string> reason;
  if (Log().Verbose()) {
    reason = cm::make_unique<std::string>();
  }
  if (UicUpdate(*mapping, reason.get())) {
    // Create the parent directory
    if (!MakeParentDirectory(mapping->OutputFile)) {
      LogFileError(GenT::UIC, mapping->OutputFile,
                   "Could not create parent directory.");
      return false;
    }
    // Add uic job
    Gen()->WorkerPool().EmplaceJob<JobUicT>(mapping, std::move(reason));
  }
  return true;
}

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

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

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

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

  return false;
}

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

  // Compose moc command
  std::vector<std::string> cmd;
  cmd.push_back(MocConst().Executable);
  // Add options
  cmAppend(cmd, MocConst().AllOptions);
  // Add predefs include
  if (!MocConst().PredefsFileAbs.empty()) {
    cmd.emplace_back("--include");
    cmd.push_back(MocConst().PredefsFileAbs);
  }
  cmd.emplace_back("-o");
  cmd.push_back(outputFile);
  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 msg =
      cmStrCat("The moc process failed to compile\n  ", Quoted(sourceFile),
               "\ninto\n  ", Quoted(outputFile));
    if (Mapping->IncluderFiles.empty()) {
      msg += ".\n";
    } else {
      msg += "\nincluded by\n";
      for (auto const& item : Mapping->IncluderFiles) {
        msg += "  ";
        msg += Quoted(item->FileName);
        msg += '\n';
      }
    }
    msg += result.ErrorMessage;
    LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
  }
}

void cmQtAutoMocUic::JobUicT::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().TargetOptions;
    auto optionIt = UicConst().Options.find(sourceFile);
    if (optionIt != UicConst().Options.end()) {
      UicMergeOptions(allOpts, optionIt->second,
                      (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 msg =
      cmStrCat("The uic process failed to compile\n  ", Quoted(sourceFile),
               "\ninto\n  ", Quoted(outputFile), "\nincluded by\n");
    for (auto const& item : Mapping->IncluderFiles) {
      msg += "  ";
      msg += Quoted(item->FileName);
      msg += '\n';
    }
    msg += result.ErrorMessage;
    LogCommandError(GenT::UIC, msg, 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";
    content += "enum some_compilers { need_more_than_nothing };\n";
  } else {
    // Valid content
    char const clampB = BaseConst().MultiConfig ? '<' : '"';
    char const clampE = BaseConst().MultiConfig ? '>' : '"';
    for (std::string const& mocfile : MocEval().CompFiles) {
      content += "#include ";
      content += clampB;
      content += mocfile;
      content += clampE;
      content += '\n';
    }
  }

  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 " + compAbs);
    }
    if (!FileWrite(compAbs, content)) {
      LogFileError(GenT::MOC, compAbs,
                   "mocs compilation file writing failed.");
    }
  } else if (MocEval().CompUpdated) {
    // Only touch mocs compilation file
    if (Log().Verbose()) {
      Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
    }
    if (!cmSystemTools::Touch(compAbs, false)) {
      LogFileError(GenT::MOC, compAbs,
                   "mocs compilation file touching failed.");
    }
  }
}

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

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

bool cmQtAutoMocUic::Init(cmMakefile* makefile)
{
  // Utility lambdas
  auto InfoGet = [makefile](const char* key) {
    return makefile->GetSafeDefinition(key);
  };
  auto InfoGetBool = [makefile](const char* key) {
    return makefile->IsOn(key);
  };
  auto InfoGetList = [makefile](const char* key) -> std::vector<std::string> {
    std::vector<std::string> list =
      cmExpandedList(makefile->GetSafeDefinition(key));
    return list;
  };
  auto InfoGetLists =
    [makefile](const char* key) -> std::vector<std::vector<std::string>> {
    std::vector<std::vector<std::string>> lists;
    {
      std::string const value = makefile->GetSafeDefinition(key);
      std::string::size_type pos = 0;
      while (pos < value.size()) {
        std::string::size_type next = value.find(ListSep, pos);
        std::string::size_type length =
          (next != std::string::npos) ? next - pos : value.size() - pos;
        // Remove enclosing braces
        if (length >= 2) {
          std::string::const_iterator itBeg = value.begin() + (pos + 1);
          std::string::const_iterator itEnd = itBeg + (length - 2);
          lists.emplace_back(cmExpandedList(std::string(itBeg, itEnd)));
        }
        pos += length;
        pos += ListSep.size();
      }
    }
    return lists;
  };
  auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
    const char* valueConf = nullptr;
    {
      std::string keyConf = cmStrCat(key, '_', InfoConfig());
      valueConf = makefile->GetDefinition(keyConf);
    }
    if (valueConf == nullptr) {
      return makefile->GetSafeDefinition(key);
    }
    return std::string(valueConf);
  };
  auto InfoGetConfigList =
    [&InfoGetConfig](const char* key) -> std::vector<std::string> {
    std::vector<std::string> list = cmExpandedList(InfoGetConfig(key));
    return list;
  };
  auto LogInfoError = [this](std::string const& msg) -> bool {
    std::ostringstream err;
    err << "In " << Quoted(this->InfoFile()) << ":\n" << msg;
    this->Log().Error(GenT::GEN, err.str());
    return false;
  };
  auto MatchSizes = [&LogInfoError](const char* keyA, const char* keyB,
                                    std::size_t sizeA,
                                    std::size_t sizeB) -> bool {
    if (sizeA == sizeB) {
      return true;
    }
    std::ostringstream err;
    err << "Lists sizes mismatch " << keyA << '(' << sizeA << ") " << keyB
        << '(' << sizeB << ')';
    return LogInfoError(err.str());
  };

  // -- Read info file
  if (!makefile->ReadListFile(InfoFile())) {
    return LogInfoError("File processing failed");
  }

  // -- Meta
  Logger_.RaiseVerbosity(InfoGet("AM_VERBOSITY"));
  BaseConst_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG");
  {
    unsigned long num = 1;
    if (cmStrToULong(InfoGet("AM_PARALLEL"), &num)) {
      num = std::max<unsigned long>(num, 1);
      num = std::min<unsigned long>(num, ParallelMax);
    }
    WorkerPool_.SetThreadCount(static_cast<unsigned int>(num));
  }
  BaseConst_.HeaderExtensions =
    makefile->GetCMakeInstance()->GetHeaderExtensions();

  // - Files and directories
  BaseConst_.IncludeProjectDirsBefore =
    InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
  BaseConst_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
  BaseConst_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
  BaseConst_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
  BaseConst_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
  BaseConst_.AutogenBuildDir = InfoGet("AM_BUILD_DIR");
  if (BaseConst_.AutogenBuildDir.empty()) {
    return LogInfoError("Autogen build directory missing.");
  }
  BaseConst_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR");
  if (BaseConst_.AutogenIncludeDir.empty()) {
    return LogInfoError("Autogen include directory missing.");
  }
  BaseConst_.CMakeExecutable = InfoGetConfig("AM_CMAKE_EXECUTABLE");
  if (BaseConst_.CMakeExecutable.empty()) {
    return LogInfoError("CMake executable file name missing.");
  }
  if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) {
    std::string error =
      cmStrCat("The CMake executable ", Quoted(BaseConst_.CMakeExecutable),
               " does not exist.");
    return LogInfoError(error);
  }
  BaseConst_.ParseCacheFile = InfoGetConfig("AM_PARSE_CACHE_FILE");
  if (BaseConst_.ParseCacheFile.empty()) {
    return LogInfoError("Parse cache file name missing.");
  }

  // - Settings file
  SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE");
  if (SettingsFile_.empty()) {
    return LogInfoError("Settings file name missing.");
  }

  // - Qt environment
  {
    unsigned long qtv = BaseConst_.QtVersionMajor;
    if (cmStrToULong(InfoGet("AM_QT_VERSION_MAJOR"), &qtv)) {
      BaseConst_.QtVersionMajor = static_cast<unsigned int>(qtv);
    }
  }

  // - Moc
  MocConst_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE");
  if (!MocConst().Executable.empty()) {
    MocConst_.Enabled = true;
    // Load the executable file time
    if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
      std::string error =
        cmStrCat("The moc executable ", Quoted(MocConst_.Executable),
                 " does not exist.");
      return LogInfoError(error);
    }
    for (std::string& sfl : InfoGetList("AM_MOC_SKIP")) {
      MocConst_.SkipList.insert(std::move(sfl));
    }
    MocConst_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
    MocConst_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
    MocConst_.Options = InfoGetList("AM_MOC_OPTIONS");
    MocConst_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
    for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) {
      MocConst_.MacroFilters.emplace_back(
        item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
    }
    {
      auto addFilter = [this, &LogInfoError](std::string const& key,
                                             std::string const& exp) -> bool {
        auto filterErr = [&LogInfoError, &key, &exp](const char* err) -> bool {
          std::ostringstream ferr;
          ferr << "AUTOMOC_DEPEND_FILTERS: " << err << '\n';
          ferr << "  Key: " << Quoted(key) << '\n';
          ferr << "  Exp: " << Quoted(exp) << '\n';
          return LogInfoError(ferr.str());
        };
        if (key.empty()) {
          return filterErr("Key is empty");
        }
        if (exp.empty()) {
          return filterErr("Regular expression is empty");
        }
        this->MocConst_.DependFilters.emplace_back(key, exp);
        if (!this->MocConst_.DependFilters.back().Exp.is_valid()) {
          return filterErr("Regular expression compiling failed");
        }
        return true;
      };

      // Insert default filter for Q_PLUGIN_METADATA
      if (BaseConst().QtVersionMajor != 4) {
        if (!addFilter("Q_PLUGIN_METADATA",
                       "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
                       "[^\\)]*FILE[ \t]*\"([^\"]+)\"")) {
          return false;
        }
      }
      // Insert user defined dependency filters
      std::vector<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS");
      if ((flts.size() % 2) != 0) {
        return LogInfoError(
          "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
      }
      for (auto itC = flts.begin(), itE = flts.end(); itC != itE; itC += 2) {
        if (!addFilter(*itC, *(itC + 1))) {
          return false;
        }
      }
    }
    MocConst_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
  }

  // - Uic
  UicConst_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE");
  if (!UicConst().Executable.empty()) {
    UicConst_.Enabled = true;
    // Load the executable file time
    if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) {
      std::string error =
        cmStrCat("The uic executable ", Quoted(UicConst_.Executable),
                 " does not exist.");
      return LogInfoError(error);
    }
    for (std::string& sfl : InfoGetList("AM_UIC_SKIP")) {
      UicConst_.SkipList.insert(std::move(sfl));
    }
    UicConst_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
    UicConst_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
    {
      const char* keyFiles = "AM_UIC_OPTIONS_FILES";
      const char* keyOpts = "AM_UIC_OPTIONS_OPTIONS";
      auto sources = InfoGetList(keyFiles);
      auto options = InfoGetLists(keyOpts);
      if (!MatchSizes(keyFiles, keyOpts, sources.size(), options.size())) {
        return false;
      }
      auto fitEnd = sources.cend();
      auto fit = sources.begin();
      auto oit = options.begin();
      while (fit != fitEnd) {
        UicConst_.Options[*fit] = std::move(*oit);
        ++fit;
        ++oit;
      }
    }
  }

  // - Headers and sources
  {
    auto makeSource =
      [&LogInfoError](std::string const& fileName,
                      std::string const& fileFlags) -> SourceFileHandleT {
      if (fileFlags.size() != 2) {
        LogInfoError("Invalid file flags string size");
        return SourceFileHandleT();
      }
      cmFileTime fileTime;
      if (!fileTime.Load(fileName)) {
        LogInfoError("The source file " + cmQtAutoGen::Quoted(fileName) +
                     " does not exist.");
        return SourceFileHandleT();
      }
      SourceFileHandleT sfh = std::make_shared<SourceFileT>(fileName);
      sfh->FileTime = fileTime;
      sfh->Moc = (fileFlags[0] == 'M');
      sfh->Uic = (fileFlags[1] == 'U');
      return sfh;
    };

    // Headers
    {
      // Get file lists
      const char* keyFiles = "AM_HEADERS";
      const char* keyFlags = "AM_HEADERS_FLAGS";
      std::vector<std::string> files = InfoGetList(keyFiles);
      std::vector<std::string> flags = InfoGetList(keyFlags);
      std::vector<std::string> builds;
      if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
        return false;
      }
      if (MocConst().Enabled) {
        const char* keyPaths = "AM_HEADERS_BUILD_PATHS";
        builds = InfoGetList(keyPaths);
        if (!MatchSizes(keyFiles, keyPaths, files.size(), builds.size())) {
          return false;
        }
      }
      // Process file lists
      for (std::size_t ii = 0; ii != files.size(); ++ii) {
        std::string& fileName(files[ii]);
        SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
        if (!sfh) {
          return false;
        }
        if (MocConst().Enabled) {
          sfh->BuildPath = std::move(builds[ii]);
          if (sfh->BuildPath.empty()) {
            Log().ErrorFile(GenT::GEN, this->InfoFile(),
                            "Header file build path is empty");
            return false;
          }
        }
        BaseEval().Headers.emplace(std::move(fileName), std::move(sfh));
      }
    }

    // Sources
    {
      const char* keyFiles = "AM_SOURCES";
      const char* keyFlags = "AM_SOURCES_FLAGS";
      std::vector<std::string> files = InfoGetList(keyFiles);
      std::vector<std::string> flags = InfoGetList(keyFlags);
      if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
        return false;
      }
      // Process file lists
      for (std::size_t ii = 0; ii != files.size(); ++ii) {
        std::string& fileName(files[ii]);
        SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
        if (!sfh) {
          return false;
        }
        BaseEval().Sources.emplace(std::move(fileName), std::move(sfh));
      }
    }
  }

  // Init derived information
  // ------------------------

  // Moc variables
  if (MocConst().Enabled) {
    // Mocs compilation file
    MocConst_.CompFileAbs = AbsoluteBuildPath("mocs_compilation.cpp");

    // Moc predefs file
    if (!MocConst_.PredefsCmd.empty()) {
      MocConst_.PredefsFileRel = "moc_predefs";
      if (BaseConst_.MultiConfig) {
        MocConst_.PredefsFileRel += '_';
        MocConst_.PredefsFileRel += InfoConfig();
      }
      MocConst_.PredefsFileRel += ".h";
      MocConst_.PredefsFileAbs = AbsoluteBuildPath(MocConst().PredefsFileRel);
    }

    // Sort include directories on demand
    if (BaseConst().IncludeProjectDirsBefore) {
      // Move strings to temporary list
      std::list<std::string> includes(MocConst().IncludePaths.begin(),
                                      MocConst().IncludePaths.end());
      MocConst_.IncludePaths.clear();
      MocConst_.IncludePaths.reserve(includes.size());
      // Append project directories only
      {
        std::array<std::string const*, 2> const movePaths = {
          { &BaseConst().ProjectBinaryDir, &BaseConst().ProjectSourceDir }
        };
        for (std::string const* ppath : movePaths) {
          std::list<std::string>::iterator it = includes.begin();
          while (it != includes.end()) {
            std::string const& path = *it;
            if (cmHasPrefix(path, *ppath)) {
              MocConst_.IncludePaths.push_back(path);
              it = includes.erase(it);
            } else {
              ++it;
            }
          }
        }
      }
      // Append remaining directories
      MocConst_.IncludePaths.insert(MocConst_.IncludePaths.end(),
                                    includes.begin(), includes.end());
    }
    // Compose moc includes list
    {
      std::set<std::string> frameworkPaths;
      for (std::string const& path : MocConst().IncludePaths) {
        MocConst_.Includes.push_back("-I" + path);
        // 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));
        }
      }
      // Append framework includes
      for (std::string const& path : frameworkPaths) {
        MocConst_.Includes.emplace_back("-F");
        MocConst_.Includes.push_back(path);
      }
    }
    // Setup single list with all options
    {
      // Add includes
      MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
                                  MocConst().Includes.begin(),
                                  MocConst().Includes.end());
      // Add definitions
      for (std::string const& def : MocConst().Definitions) {
        MocConst_.AllOptions.push_back("-D" + def);
      }
      // Add options
      MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
                                  MocConst().Options.begin(),
                                  MocConst().Options.end());
    }
  }

  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);
    }
  }
}

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 evaluate job
  WorkerPool().EmplaceJob<JobEvaluateT>();
}

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);
    std::string const sep(";");
    auto cha = [&cryptoHash, &sep](std::string const& value) {
      cryptoHash.Append(value);
      cryptoHash.Append(sep);
    };

    if (MocConst_.Enabled) {
      cryptoHash.Initialize();
      cha(MocConst().Executable);
      for (auto const& value : MocConst().AllOptions) {
        cha(value);
      }
      cha(BaseConst().IncludeProjectDirsBefore ? "TRUE" : "FALSE");
      for (auto const& value : MocConst().PredefsCmd) {
        cha(value);
      }
      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);
      for (auto const& value : UicConst().TargetOptions) {
        cha(value);
      }
      for (const auto& item : UicConst().Options) {
        cha(item.first);
        for (auto const& svalue : item.second) {
          cha(svalue);
        }
      }
      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, "Writing settings file " + Quoted(SettingsFile_));
    }
    // Compose settings file content
    std::string content;
    {
      auto SettingAppend = [&content](const char* key,
                                      std::string const& value) {
        if (!value.empty()) {
          content += key;
          content += ':';
          content += value;
          content += '\n';
        }
      };
      SettingAppend("moc", SettingsStringMoc_);
      SettingAppend("uic", SettingsStringUic_);
    }
    // Write settings file
    std::string error;
    if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
      Log().ErrorFile(GenT::GEN, SettingsFile_,
                      "Settings file writing failed. " + error);
      // Remove old settings file to trigger a full rebuild on the next run
      cmSystemTools::RemoveFile(SettingsFile_);
      return false;
    }
  }
  return true;
}

void cmQtAutoMocUic::ParseCacheRead()
{
  const char* reason = nullptr;
  // 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 != nullptr) {
    // 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,
                 "Writing parse cache file " +
                   Quoted(BaseConst().ParseCacheFile));
    }
    if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) {
      Log().ErrorFile(GenT::GEN, BaseConst().ParseCacheFile,
                      "Parse cache file writing failed.");
      return false;
    }
  }
  return true;
}

bool cmQtAutoMocUic::CreateDirectories()
{
  // Create AUTOGEN include directory
  if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) {
    Log().ErrorFile(GenT::GEN, BaseConst().AutogenIncludeDir,
                    "Could not create directory.");
    return false;
  }
  return true;
}

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

std::string cmQtAutoMocUic::AbsoluteBuildPath(
  std::string const& relativePath) const
{
  std::string res(BaseConst().AutogenBuildDir);
  res += '/';
  res += relativePath;
  return res;
}

std::string cmQtAutoMocUic::AbsoluteIncludePath(
  std::string const& relativePath) const
{
  std::string res(BaseConst().AutogenIncludeDir);
  res += '/';
  res += relativePath;
  return res;
}
