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

#include <cstddef>
#include <deque>
#include <initializer_list>
#include <limits>
#include <map>
#include <set>
#include <sstream> // for basic_ios, istringstream
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

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

#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>

#include "cmsys/SystemInformation.hxx"

#include "cmAlgorithms.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmEvaluatedTargetProperty.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionDAGChecker.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmList.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmQtAutoGen.h"
#include "cmQtAutoGenGlobalInitializer.h"
#include "cmSourceFile.h"
#include "cmSourceFileLocationKind.h"
#include "cmSourceGroup.h"
#include "cmStandardLevelResolver.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
#include "cmake.h"

namespace {

unsigned int GetParallelCPUCount()
{
  static unsigned int count = 0;
  // Detect only on the first call
  if (count == 0) {
    cmsys::SystemInformation info;
    info.RunCPUCheck();
    count =
      cm::clamp(info.GetNumberOfPhysicalCPU(), 1u, cmQtAutoGen::ParallelMax);
  }
  return count;
}

std::string FileProjectRelativePath(cmMakefile* makefile,
                                    std::string const& fileName)
{
  std::string res;
  {
    std::string pSource = cmSystemTools::RelativePath(
      makefile->GetCurrentSourceDirectory(), fileName);
    std::string pBinary = cmSystemTools::RelativePath(
      makefile->GetCurrentBinaryDirectory(), fileName);
    if (pSource.size() < pBinary.size()) {
      res = std::move(pSource);
    } else if (pBinary.size() < fileName.size()) {
      res = std::move(pBinary);
    } else {
      res = fileName;
    }
  }
  return res;
}

/**
 * Tests if targetDepend is a STATIC_LIBRARY and if any of its
 * recursive STATIC_LIBRARY dependencies depends on targetOrigin
 * (STATIC_LIBRARY cycle).
 */
bool StaticLibraryCycle(cmGeneratorTarget const* targetOrigin,
                        cmGeneratorTarget const* targetDepend,
                        std::string const& config)
{
  bool cycle = false;
  if ((targetOrigin->GetType() == cmStateEnums::STATIC_LIBRARY) &&
      (targetDepend->GetType() == cmStateEnums::STATIC_LIBRARY)) {
    std::set<cmGeneratorTarget const*> knownLibs;
    std::deque<cmGeneratorTarget const*> testLibs;

    // Insert initial static_library dependency
    knownLibs.insert(targetDepend);
    testLibs.push_back(targetDepend);

    while (!testLibs.empty()) {
      cmGeneratorTarget const* testTarget = testLibs.front();
      testLibs.pop_front();
      // Check if the test target is the origin target (cycle)
      if (testTarget == targetOrigin) {
        cycle = true;
        break;
      }
      // Collect all static_library dependencies from the test target
      cmLinkImplementationLibraries const* libs =
        testTarget->GetLinkImplementationLibraries(
          config, cmGeneratorTarget::LinkInterfaceFor::Link);
      if (libs) {
        for (cmLinkItem const& item : libs->Libraries) {
          cmGeneratorTarget const* depTarget = item.Target;
          if (depTarget &&
              (depTarget->GetType() == cmStateEnums::STATIC_LIBRARY) &&
              knownLibs.insert(depTarget).second) {
            testLibs.push_back(depTarget);
          }
        }
      }
    }
  }
  return cycle;
}

/** Sanitizes file search paths.  */
class SearchPathSanitizer
{
public:
  SearchPathSanitizer(cmMakefile* makefile)
    : SourcePath_(makefile->GetCurrentSourceDirectory())
  {
  }
  std::vector<std::string> operator()(
    std::vector<std::string> const& paths) const;

private:
  std::string SourcePath_;
};

std::vector<std::string> SearchPathSanitizer::operator()(
  std::vector<std::string> const& paths) const
{
  std::vector<std::string> res;
  res.reserve(paths.size());
  for (std::string const& srcPath : paths) {
    // Collapse relative paths
    std::string path =
      cmSystemTools::CollapseFullPath(srcPath, this->SourcePath_);
    // Remove suffix slashes
    while (cmHasSuffix(path, '/')) {
      path.pop_back();
    }
    // Accept only non empty paths
    if (!path.empty()) {
      res.emplace_back(std::move(path));
    }
  }
  return res;
}

/** @brief Writes a CMake info file.  */
class InfoWriter
{
public:
  // -- Single value
  void Set(std::string const& key, std::string const& value)
  {
    this->Value_[key] = value;
  }
  void SetConfig(std::string const& key,
                 cmQtAutoGenInitializer::ConfigString const& cfgStr);
  void SetBool(std::string const& key, bool value)
  {
    this->Value_[key] = value;
  }
  void SetUInt(std::string const& key, unsigned int value)
  {
    this->Value_[key] = value;
  }

  // -- Array utility
  template <typename CONT>
  static bool MakeArray(Json::Value& jval, CONT const& container);

  template <typename CONT>
  static void MakeStringArray(Json::Value& jval, CONT const& container);

  // -- Array value
  template <typename CONT>
  void SetArray(std::string const& key, CONT const& container);
  template <typename CONT>
  void SetConfigArray(
    std::string const& key,
    cmQtAutoGenInitializer::ConfigStrings<CONT> const& cfgStr);

  // -- Array of arrays
  template <typename CONT, typename FUNC>
  void SetArrayArray(std::string const& key, CONT const& container, FUNC func);

  // -- Save to json file
  bool Save(std::string const& filename);

private:
  Json::Value Value_;
};

void InfoWriter::SetConfig(std::string const& key,
                           cmQtAutoGenInitializer::ConfigString const& cfgStr)
{
  this->Set(key, cfgStr.Default);
  for (auto const& item : cfgStr.Config) {
    this->Set(cmStrCat(key, '_', item.first), item.second);
  }
}

template <typename CONT>
bool InfoWriter::MakeArray(Json::Value& jval, CONT const& container)
{
  jval = Json::arrayValue;
  std::size_t const listSize = cm::size(container);
  if (listSize == 0) {
    return false;
  }
  jval.resize(static_cast<unsigned int>(listSize));
  return true;
}

template <typename CONT>
void InfoWriter::MakeStringArray(Json::Value& jval, CONT const& container)
{
  if (MakeArray(jval, container)) {
    Json::ArrayIndex ii = 0;
    for (std::string const& item : container) {
      jval[ii++] = item;
    }
  }
}

template <typename CONT>
void InfoWriter::SetArray(std::string const& key, CONT const& container)
{
  MakeStringArray(this->Value_[key], container);
}

template <typename CONT, typename FUNC>
void InfoWriter::SetArrayArray(std::string const& key, CONT const& container,
                               FUNC func)
{
  Json::Value& jval = this->Value_[key];
  if (MakeArray(jval, container)) {
    Json::ArrayIndex ii = 0;
    for (auto const& citem : container) {
      Json::Value& aval = jval[ii++];
      aval = Json::arrayValue;
      func(aval, citem);
    }
  }
}

template <typename CONT>
void InfoWriter::SetConfigArray(
  std::string const& key,
  cmQtAutoGenInitializer::ConfigStrings<CONT> const& cfgStr)
{
  this->SetArray(key, cfgStr.Default);
  for (auto const& item : cfgStr.Config) {
    this->SetArray(cmStrCat(key, '_', item.first), item.second);
  }
}

bool InfoWriter::Save(std::string const& filename)
{
  cmGeneratedFileStream fileStream;
  fileStream.SetCopyIfDifferent(true);
  fileStream.Open(filename, false, true);
  if (!fileStream) {
    return false;
  }

  Json::StyledStreamWriter jsonWriter;
  try {
    jsonWriter.write(fileStream, this->Value_);
  } catch (...) {
    return false;
  }

  return fileStream.Close();
}

void AddAutogenExecutableToDependencies(
  cmQtAutoGenInitializer::GenVarsT const& genVars,
  std::vector<std::string>& dependencies)
{
  if (genVars.ExecutableTarget != nullptr) {
    dependencies.push_back(genVars.ExecutableTarget->Target->GetName());
  } else if (!genVars.Executable.empty()) {
    dependencies.push_back(genVars.Executable);
  }
}

} // End of unnamed namespace

cmQtAutoGenInitializer::cmQtAutoGenInitializer(
  cmQtAutoGenGlobalInitializer* globalInitializer,
  cmGeneratorTarget* genTarget, IntegerVersion const& qtVersion,
  bool mocEnabled, bool uicEnabled, bool rccEnabled, bool globalAutogenTarget,
  bool globalAutoRccTarget)
  : GlobalInitializer(globalInitializer)
  , GenTarget(genTarget)
  , GlobalGen(genTarget->GetGlobalGenerator())
  , LocalGen(genTarget->GetLocalGenerator())
  , Makefile(genTarget->Makefile)
  , PathCheckSum(genTarget->Makefile)
  , QtVersion(qtVersion)
{
  this->AutogenTarget.GlobalTarget = globalAutogenTarget;
  this->Moc.Enabled = mocEnabled;
  this->Uic.Enabled = uicEnabled;
  this->Rcc.Enabled = rccEnabled;
  this->Rcc.GlobalTarget = globalAutoRccTarget;
}

bool cmQtAutoGenInitializer::InitCustomTargets()
{
  // Configurations
  this->MultiConfig = this->GlobalGen->IsMultiConfig();
  this->ConfigDefault = this->Makefile->GetDefaultConfiguration();
  this->ConfigsList =
    this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);

  // Verbosity
  {
    std::string const def =
      this->Makefile->GetSafeDefinition("CMAKE_AUTOGEN_VERBOSE");
    if (!def.empty()) {
      unsigned long iVerb = 0;
      if (cmStrToULong(def, &iVerb)) {
        // Numeric verbosity
        this->Verbosity = static_cast<unsigned int>(iVerb);
      } else {
        // Non numeric verbosity
        if (cmIsOn(def)) {
          this->Verbosity = 1;
        }
      }
    }
  }

  // Targets FOLDER
  {
    cmValue folder =
      this->Makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER");
    if (!folder) {
      folder = this->Makefile->GetState()->GetGlobalProperty(
        "AUTOGEN_TARGETS_FOLDER");
    }
    // Inherit FOLDER property from target (#13688)
    if (!folder) {
      folder = this->GenTarget->GetProperty("FOLDER");
    }
    if (folder) {
      this->TargetsFolder = *folder;
    }
  }

  // Check status of policy CMP0071 regarding handling of GENERATED files
  switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0071)) {
    case cmPolicies::WARN:
      // Ignore GENERATED files but warn
      this->CMP0071Warn = true;
      CM_FALLTHROUGH;
    case cmPolicies::OLD:
      // Ignore GENERATED files
      break;
    case cmPolicies::REQUIRED_IF_USED:
    case cmPolicies::REQUIRED_ALWAYS:
    case cmPolicies::NEW:
      // Process GENERATED files
      this->CMP0071Accept = true;
      break;
  }

  // Check status of policy CMP0100 regarding handling of .hh headers
  switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0100)) {
    case cmPolicies::WARN:
      // Ignore but .hh files but warn
      this->CMP0100Warn = true;
      CM_FALLTHROUGH;
    case cmPolicies::OLD:
      // Ignore .hh files
      break;
    case cmPolicies::REQUIRED_IF_USED:
    case cmPolicies::REQUIRED_ALWAYS:
    case cmPolicies::NEW:
      // Process .hh file
      this->CMP0100Accept = true;
      break;
  }

  // Common directories
  std::string relativeBuildDir;
  {
    // Collapsed current binary directory
    std::string const cbd = cmSystemTools::CollapseFullPath(
      std::string(), this->Makefile->GetCurrentBinaryDirectory());

    // Info directory
    this->Dir.Info = cmStrCat(cbd, "/CMakeFiles/", this->GenTarget->GetName(),
                              "_autogen.dir");
    cmSystemTools::ConvertToUnixSlashes(this->Dir.Info);

    // Build directory
    this->Dir.Build = this->GenTarget->GetSafeProperty("AUTOGEN_BUILD_DIR");
    if (this->Dir.Build.empty()) {
      this->Dir.Build =
        cmStrCat(cbd, '/', this->GenTarget->GetName(), "_autogen");
    }
    cmSystemTools::ConvertToUnixSlashes(this->Dir.Build);
    this->Dir.RelativeBuild =
      cmSystemTools::RelativePath(cbd, this->Dir.Build);
    // Cleanup build directory
    this->AddCleanFile(this->Dir.Build);

    // Working directory
    this->Dir.Work = cbd;
    cmSystemTools::ConvertToUnixSlashes(this->Dir.Work);

    // Include directory
    this->ConfigFileNamesAndGenex(this->Dir.Include, this->Dir.IncludeGenExp,
                                  cmStrCat(this->Dir.Build, "/include"), "");
  }

  // Moc, Uic and _autogen target settings
  if (this->MocOrUicEnabled()) {
    // Init moc specific settings
    if (this->Moc.Enabled && !this->InitMoc()) {
      return false;
    }

    // Init uic specific settings
    if (this->Uic.Enabled && !this->InitUic()) {
      return false;
    }

    // Autogen target name
    this->AutogenTarget.Name =
      cmStrCat(this->GenTarget->GetName(), "_autogen");

    // Autogen target parallel processing
    {
      using ParallelType = decltype(this->AutogenTarget.Parallel);
      unsigned long propInt = 0;
      std::string const& prop =
        this->GenTarget->GetSafeProperty("AUTOGEN_PARALLEL");
      if (prop.empty() || (prop == "AUTO")) {
        // Autodetect number of CPUs
        this->AutogenTarget.Parallel = GetParallelCPUCount();
      } else if (cmStrToULong(prop, &propInt) && propInt > 0 &&
                 propInt <= std::numeric_limits<ParallelType>::max()) {
        this->AutogenTarget.Parallel = static_cast<ParallelType>(propInt);
      } else {
        // Warn the project author that AUTOGEN_PARALLEL is not valid.
        this->Makefile->IssueMessage(
          MessageType::AUTHOR_WARNING,
          cmStrCat("AUTOGEN_PARALLEL=\"", prop, "\" for target \"",
                   this->GenTarget->GetName(),
                   "\" is not valid. Using AUTOGEN_PARALLEL=1"));
        this->AutogenTarget.Parallel = 1;
      }
    }

    // Autogen target info and settings files
    {
      // Info file
      this->AutogenTarget.InfoFile =
        cmStrCat(this->Dir.Info, "/AutogenInfo.json");

      // Used settings file
      this->ConfigFileNames(this->AutogenTarget.SettingsFile,
                            cmStrCat(this->Dir.Info, "/AutogenUsed"), ".txt");
      this->ConfigFileClean(this->AutogenTarget.SettingsFile);

      // Parse cache file
      this->ConfigFileNames(this->AutogenTarget.ParseCacheFile,
                            cmStrCat(this->Dir.Info, "/ParseCache"), ".txt");
      this->ConfigFileClean(this->AutogenTarget.ParseCacheFile);
    }

    // Autogen target: Compute user defined dependencies
    {
      this->AutogenTarget.DependOrigin =
        this->GenTarget->GetPropertyAsBool("AUTOGEN_ORIGIN_DEPENDS");

      std::string const& deps =
        this->GenTarget->GetSafeProperty("AUTOGEN_TARGET_DEPENDS");
      if (!deps.empty()) {
        for (auto const& depName : cmList{ deps }) {
          // Allow target and file dependencies
          auto* depTarget = this->Makefile->FindTargetToUse(depName);
          if (depTarget) {
            this->AutogenTarget.DependTargets.insert(depTarget);
          } else {
            this->AutogenTarget.DependFiles.insert(depName);
          }
        }
      }
    }

    if (this->Moc.Enabled) {
      // Path prefix
      if (cmIsOn(this->GenTarget->GetProperty("AUTOMOC_PATH_PREFIX"))) {
        this->Moc.PathPrefix = true;
      }

      // CMAKE_AUTOMOC_RELAXED_MODE
      if (this->Makefile->IsOn("CMAKE_AUTOMOC_RELAXED_MODE")) {
        this->Moc.RelaxedMode = true;
        this->Makefile->IssueMessage(
          MessageType::AUTHOR_WARNING,
          cmStrCat("AUTOMOC: CMAKE_AUTOMOC_RELAXED_MODE is "
                   "deprecated an will be removed in the future.  Consider "
                   "disabling it and converting the target ",
                   this->GenTarget->GetName(), " to regular mode."));
      }

      // Options
      cmExpandList(this->GenTarget->GetSafeProperty("AUTOMOC_MOC_OPTIONS"),
                   this->Moc.Options);
      // Filters
      cmExpandList(this->GenTarget->GetSafeProperty("AUTOMOC_MACRO_NAMES"),
                   this->Moc.MacroNames);
      this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames),
                                 this->Moc.MacroNames.end());
      {
        cmList const filterList = { this->GenTarget->GetSafeProperty(
          "AUTOMOC_DEPEND_FILTERS") };
        if ((filterList.size() % 2) != 0) {
          cmSystemTools::Error(
            cmStrCat("AutoMoc: AUTOMOC_DEPEND_FILTERS predefs size ",
                     filterList.size(), " is not a multiple of 2."));
          return false;
        }
        this->Moc.DependFilters.reserve(1 + (filterList.size() / 2));
        this->Moc.DependFilters.emplace_back(
          "Q_PLUGIN_METADATA",
          "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
          "[^\\)]*FILE[ \t]*\"([^\"]+)\"");
        for (cmList::size_type ii = 0; ii != filterList.size(); ii += 2) {
          this->Moc.DependFilters.emplace_back(filterList[ii],
                                               filterList[ii + 1]);
        }
      }
    }
  }

  // Init rcc specific settings
  if (this->Rcc.Enabled && !this->InitRcc()) {
    return false;
  }

  // Add autogen include directory to the origin target INCLUDE_DIRECTORIES
  if (this->MocOrUicEnabled() || (this->Rcc.Enabled && this->MultiConfig)) {
    auto addBefore = false;
    auto const& value =
      this->GenTarget->GetProperty("AUTOGEN_USE_SYSTEM_INCLUDE");
    if (value.IsSet()) {
      if (cmIsOn(value)) {
        this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp,
                                                   "CXX");
      } else {
        addBefore = true;
      }
    } else {
      switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0151)) {
        case cmPolicies::WARN:
        case cmPolicies::OLD:
          addBefore = true;
          break;
        case cmPolicies::REQUIRED_IF_USED:
        case cmPolicies::REQUIRED_ALWAYS:
        case cmPolicies::NEW:
          this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp,
                                                     "CXX");
          break;
      }
    }
    this->GenTarget->AddIncludeDirectory(this->Dir.IncludeGenExp, addBefore);
  }

  // Scan files
  if (!this->InitScanFiles()) {
    return false;
  }

  // Create autogen target
  if (this->MocOrUicEnabled() && !this->InitAutogenTarget()) {
    return false;
  }

  // Create rcc targets
  if (this->Rcc.Enabled && !this->InitRccTargets()) {
    return false;
  }

  return true;
}

bool cmQtAutoGenInitializer::InitMoc()
{
  // Mocs compilation file
  if (this->GlobalGen->IsXcode()) {
    // XXX(xcode-per-cfg-src): Drop this Xcode-specific code path
    // when the Xcode generator supports per-config sources.
    this->Moc.CompilationFile.Default =
      cmStrCat(this->Dir.Build, "/mocs_compilation.cpp");
    this->Moc.CompilationFileGenex = this->Moc.CompilationFile.Default;
  } else {
    this->ConfigFileNamesAndGenex(
      this->Moc.CompilationFile, this->Moc.CompilationFileGenex,
      cmStrCat(this->Dir.Build, "/mocs_compilation"_s), ".cpp"_s);
  }

  // Moc predefs
  if (this->GenTarget->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") &&
      (this->QtVersion >= IntegerVersion(5, 8))) {
    // Command
    cmList::assign(
      this->Moc.PredefsCmd,
      this->Makefile->GetDefinition("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND"));
    // Header
    if (!this->Moc.PredefsCmd.empty()) {
      this->ConfigFileNames(this->Moc.PredefsFile,
                            cmStrCat(this->Dir.Build, "/moc_predefs"), ".h");
    }
  }

  // Moc includes
  {
    SearchPathSanitizer const sanitizer(this->Makefile);
    auto getDirs =
      [this, &sanitizer](std::string const& cfg) -> std::vector<std::string> {
      // Get the include dirs for this target, without stripping the implicit
      // include dirs off, see issue #13667.
      std::vector<std::string> dirs;
      bool const appendImplicit = (this->QtVersion.Major >= 5);
      this->LocalGen->GetIncludeDirectoriesImplicit(
        dirs, this->GenTarget, "CXX", cfg, false, appendImplicit);
      return sanitizer(dirs);
    };

    // Other configuration settings
    if (this->MultiConfig) {
      for (std::string const& cfg : this->ConfigsList) {
        std::vector<std::string> dirs = getDirs(cfg);
        if (dirs == this->Moc.Includes.Default) {
          continue;
        }
        this->Moc.Includes.Config[cfg] = std::move(dirs);
      }
    } else {
      // Default configuration include directories
      this->Moc.Includes.Default = getDirs(this->ConfigDefault);
    }
  }

  // Moc compile definitions
  {
    auto getDefs = [this](std::string const& cfg) -> std::set<std::string> {
      std::set<std::string> defines;
      this->LocalGen->GetTargetDefines(this->GenTarget, cfg, "CXX", defines);
      if (this->Moc.PredefsCmd.empty() &&
          this->Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") ==
            "Windows") {
        // Add WIN32 definition if we don't have a moc_predefs.h
        defines.insert("WIN32");
      }
      return defines;
    };

    // Other configuration defines
    if (this->MultiConfig) {
      for (std::string const& cfg : this->ConfigsList) {
        std::set<std::string> defines = getDefs(cfg);
        if (defines == this->Moc.Defines.Default) {
          continue;
        }
        this->Moc.Defines.Config[cfg] = std::move(defines);
      }
    } else {
      // Default configuration defines
      this->Moc.Defines.Default = getDefs(this->ConfigDefault);
    }
  }

  // Moc executable
  {
    if (!this->GetQtExecutable(this->Moc, "moc", false)) {
      return false;
    }
    // Let the _autogen target depend on the moc executable
    if (this->Moc.ExecutableTarget) {
      this->AutogenTarget.DependTargets.insert(
        this->Moc.ExecutableTarget->Target);
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::InitUic()
{
  // Uic search paths
  {
    std::string const& usp =
      this->GenTarget->GetSafeProperty("AUTOUIC_SEARCH_PATHS");
    if (!usp.empty()) {
      this->Uic.SearchPaths =
        SearchPathSanitizer(this->Makefile)(cmList{ usp });
    }
  }
  // Uic target options
  {
    auto getOpts = [this](std::string const& cfg) -> std::vector<std::string> {
      std::vector<std::string> opts;
      this->GenTarget->GetAutoUicOptions(opts, cfg);
      return opts;
    };

    // Default options
    this->Uic.Options.Default = getOpts(this->ConfigDefault);
    // Configuration specific options
    if (this->MultiConfig) {
      for (std::string const& cfg : this->ConfigsList) {
        std::vector<std::string> options = getOpts(cfg);
        if (options == this->Uic.Options.Default) {
          continue;
        }
        this->Uic.Options.Config[cfg] = std::move(options);
      }
    }
  }

  // Uic executable
  {
    if (!this->GetQtExecutable(this->Uic, "uic", true)) {
      return false;
    }
    // Let the _autogen target depend on the uic executable
    if (this->Uic.ExecutableTarget) {
      this->AutogenTarget.DependTargets.insert(
        this->Uic.ExecutableTarget->Target);
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::InitRcc()
{
  // Rcc executable
  {
    if (!this->GetQtExecutable(this->Rcc, "rcc", false)) {
      return false;
    }
    // Evaluate test output on demand
    CompilerFeatures& features = *this->Rcc.ExecutableFeatures;
    if (!features.Evaluated) {
      // Look for list options
      if (this->QtVersion.Major == 5 || this->QtVersion.Major == 6) {
        if (features.HelpOutput.find("--list") != std::string::npos) {
          features.ListOptions.emplace_back("--list");
        } else if (features.HelpOutput.find("-list") != std::string::npos) {
          features.ListOptions.emplace_back("-list");
        }
      }
      // Evaluation finished
      features.Evaluated = true;
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::InitScanFiles()
{
  cmake const* cm = this->Makefile->GetCMakeInstance();
  auto const& kw = this->GlobalInitializer->kw();

  auto makeMUFile = [this, &kw](cmSourceFile* sf, std::string const& fullPath,
                                std::vector<size_t> const& configs,
                                bool muIt) -> MUFileHandle {
    MUFileHandle muf = cm::make_unique<MUFile>();
    muf->FullPath = fullPath;
    muf->SF = sf;
    if (!configs.empty() && configs.size() != this->ConfigsList.size()) {
      muf->Configs = configs;
    }
    muf->Generated = sf->GetIsGenerated();
    bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
    muf->SkipMoc = this->Moc.Enabled &&
      (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOMOC));
    muf->SkipUic = this->Uic.Enabled &&
      (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC));
    if (muIt) {
      muf->MocIt = this->Moc.Enabled && !muf->SkipMoc;
      muf->UicIt = this->Uic.Enabled && !muf->SkipUic;
    }
    return muf;
  };

  auto addMUHeader = [this](MUFileHandle&& muf, cm::string_view extension) {
    cmSourceFile* sf = muf->SF;
    const bool muIt = (muf->MocIt || muf->UicIt);
    if (this->CMP0100Accept || (extension != "hh")) {
      // Accept
      if (muIt && muf->Generated) {
        this->AutogenTarget.FilesGenerated.emplace_back(muf.get());
      }
      this->AutogenTarget.Headers.emplace(sf, std::move(muf));
    } else if (muIt && this->CMP0100Warn) {
      // Store file for warning message
      this->AutogenTarget.CMP0100HeadersWarn.push_back(sf);
    }
  };

  auto addMUSource = [this](MUFileHandle&& muf) {
    if ((muf->MocIt || muf->UicIt) && muf->Generated) {
      this->AutogenTarget.FilesGenerated.emplace_back(muf.get());
    }
    this->AutogenTarget.Sources.emplace(muf->SF, std::move(muf));
  };

  // Scan through target files
  {
    // Scan through target files
    for (cmGeneratorTarget::AllConfigSource const& acs :
         this->GenTarget->GetAllConfigSources()) {
      std::string const& fullPath = acs.Source->GetFullPath();
      std::string const& extLower =
        cmSystemTools::LowerCase(acs.Source->GetExtension());

      // Register files that will be scanned by moc or uic
      if (this->MocOrUicEnabled()) {
        if (cm->IsAHeaderExtension(extLower)) {
          addMUHeader(makeMUFile(acs.Source, fullPath, acs.Configs, true),
                      extLower);
        } else if (cm->IsACLikeSourceExtension(extLower)) {
          addMUSource(makeMUFile(acs.Source, fullPath, acs.Configs, true));
        }
      }

      // Register rcc enabled files
      if (this->Rcc.Enabled) {
        if ((extLower == kw.qrc) &&
            !acs.Source->GetPropertyAsBool(kw.SKIP_AUTOGEN) &&
            !acs.Source->GetPropertyAsBool(kw.SKIP_AUTORCC)) {
          // Register qrc file
          Qrc qrc;
          qrc.QrcFile = fullPath;
          qrc.QrcName =
            cmSystemTools::GetFilenameWithoutLastExtension(qrc.QrcFile);
          qrc.Generated = acs.Source->GetIsGenerated();
          // RCC options
          {
            std::string const& opts =
              acs.Source->GetSafeProperty(kw.AUTORCC_OPTIONS);
            if (!opts.empty()) {
              cmExpandList(opts, qrc.Options);
            }
          }
          this->Rcc.Qrcs.push_back(std::move(qrc));
        }
      }
    }
  }
  // cmGeneratorTarget::GetAllConfigSources computes the target's
  // sources meta data cache. Clear it so that OBJECT library targets that
  // are AUTOGEN initialized after this target get their added
  // mocs_compilation.cpp source acknowledged by this target.
  this->GenTarget->ClearSourcesCache();

  // For source files find additional headers and private headers
  if (this->MocOrUicEnabled()) {
    // Header search suffixes and extensions
    static std::initializer_list<cm::string_view> const suffixes{ "", "_p" };
    auto const& exts = cm->GetHeaderExtensions();
    // Scan through sources
    for (auto const& pair : this->AutogenTarget.Sources) {
      MUFile const& muf = *pair.second;
      if (muf.MocIt || muf.UicIt) {
        // Search for the default header file and a private header
        std::string const& srcFullPath = muf.SF->ResolveFullPath();
        std::string basePath = cmStrCat(
          cmQtAutoGen::SubDirPrefix(srcFullPath),
          cmSystemTools::GetFilenameWithoutLastExtension(srcFullPath));
        for (auto const& suffix : suffixes) {
          std::string const suffixedPath = cmStrCat(basePath, suffix);
          for (auto const& ext : exts) {
            std::string fullPath = cmStrCat(suffixedPath, '.', ext);

            auto constexpr locationKind = cmSourceFileLocationKind::Known;
            cmSourceFile* sf =
              this->Makefile->GetSource(fullPath, locationKind);
            if (sf) {
              // Check if we know about this header already
              if (cm::contains(this->AutogenTarget.Headers, sf)) {
                continue;
              }
              // We only accept not-GENERATED files that do exist.
              if (!sf->GetIsGenerated() &&
                  !cmSystemTools::FileExists(fullPath)) {
                continue;
              }
            } else if (cmSystemTools::FileExists(fullPath)) {
              // Create a new source file for the existing file
              sf = this->Makefile->CreateSource(fullPath, false, locationKind);
            }

            if (sf) {
              auto eMuf = makeMUFile(sf, fullPath, muf.Configs, true);
              // Only process moc/uic when the parent is processed as well
              if (!muf.MocIt) {
                eMuf->MocIt = false;
              }
              if (!muf.UicIt) {
                eMuf->UicIt = false;
              }
              addMUHeader(std::move(eMuf), ext);
            }
          }
        }
      }
    }
  }

  // Scan through all source files in the makefile to extract moc and uic
  // parameters.  Historically we support non target source file parameters.
  // The reason is that their file names might be discovered from source files
  // at generation time.
  if (this->MocOrUicEnabled()) {
    for (const auto& sf : this->Makefile->GetSourceFiles()) {
      // sf->GetExtension() is only valid after sf->ResolveFullPath() ...
      // Since we're iterating over source files that might be not in the
      // target we need to check for path errors (not existing files).
      std::string pathError;
      std::string const& fullPath = sf->ResolveFullPath(&pathError);
      if (!pathError.empty() || fullPath.empty()) {
        continue;
      }
      std::string const& extLower =
        cmSystemTools::LowerCase(sf->GetExtension());

      if (cm->IsAHeaderExtension(extLower)) {
        if (!cm::contains(this->AutogenTarget.Headers, sf.get())) {
          auto muf = makeMUFile(sf.get(), fullPath, {}, false);
          if (muf->SkipMoc || muf->SkipUic) {
            addMUHeader(std::move(muf), extLower);
          }
        }
      } else if (cm->IsACLikeSourceExtension(extLower)) {
        if (!cm::contains(this->AutogenTarget.Sources, sf.get())) {
          auto muf = makeMUFile(sf.get(), fullPath, {}, false);
          if (muf->SkipMoc || muf->SkipUic) {
            addMUSource(std::move(muf));
          }
        }
      } else if (this->Uic.Enabled && (extLower == kw.ui)) {
        // .ui file
        bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
        bool const skipUic =
          (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC));
        if (!skipUic) {
          // Check if the .ui file has uic options
          std::string const uicOpts = sf->GetSafeProperty(kw.AUTOUIC_OPTIONS);
          if (uicOpts.empty()) {
            this->Uic.UiFilesNoOptions.emplace_back(fullPath);
          } else {
            this->Uic.UiFilesWithOptions.emplace_back(
              fullPath, std::move(cmList{ uicOpts }.data()));
          }

          auto uiHeaderRelativePath = cmSystemTools::RelativePath(
            this->LocalGen->GetCurrentSourceDirectory(),
            cmSystemTools::GetFilenamePath(fullPath));

          // Avoid creating a path containing adjacent slashes
          if (!uiHeaderRelativePath.empty() &&
              uiHeaderRelativePath.back() != '/') {
            uiHeaderRelativePath += '/';
          }

          auto uiHeaderFilePath = cmStrCat(
            '/', uiHeaderRelativePath, "ui_"_s,
            cmSystemTools::GetFilenameWithoutLastExtension(fullPath), ".h"_s);

          ConfigString uiHeader;
          std::string uiHeaderGenex;
          this->ConfigFileNamesAndGenex(
            uiHeader, uiHeaderGenex, cmStrCat(this->Dir.Build, "/include"_s),
            uiHeaderFilePath);

          this->Uic.UiHeaders.emplace_back(uiHeader, uiHeaderGenex);
        } else {
          // Register skipped .ui file
          this->Uic.SkipUi.insert(fullPath);
        }
      }
    }
  }

  // Process GENERATED sources and headers
  if (this->MocOrUicEnabled() && !this->AutogenTarget.FilesGenerated.empty()) {
    if (this->CMP0071Accept) {
      // Let the autogen target depend on the GENERATED files
      if (this->MultiConfig &&
          this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
        for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
          if (muf->Configs.empty()) {
            this->AutogenTarget.DependFiles.insert(muf->FullPath);
          } else {
            for (size_t ci : muf->Configs) {
              std::string const& config = this->ConfigsList[ci];
              std::string const& pathWithConfig =
                cmStrCat("$<$<CONFIG:", config, ">:", muf->FullPath, '>');
              this->AutogenTarget.DependFiles.insert(pathWithConfig);
            }
          }
        }
      } else {
        for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
          this->AutogenTarget.DependFiles.insert(muf->FullPath);
        }
      }
    } else if (this->CMP0071Warn) {
      cm::string_view property;
      if (this->Moc.Enabled && this->Uic.Enabled) {
        property = "SKIP_AUTOGEN";
      } else if (this->Moc.Enabled) {
        property = "SKIP_AUTOMOC";
      } else if (this->Uic.Enabled) {
        property = "SKIP_AUTOUIC";
      }
      std::string files;
      for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
        files += cmStrCat("  ", Quoted(muf->FullPath), '\n');
      }
      this->Makefile->IssueMessage(
        MessageType::AUTHOR_WARNING,
        cmStrCat(
          cmPolicies::GetPolicyWarning(cmPolicies::CMP0071), '\n',
          "For compatibility, CMake is excluding the GENERATED source "
          "file(s):\n",
          files, "from processing by ",
          cmQtAutoGen::Tools(this->Moc.Enabled, this->Uic.Enabled, false),
          ".  If any of the files should be processed, set CMP0071 to NEW.  "
          "If any of the files should not be processed, "
          "explicitly exclude them by setting the source file property ",
          property, ":\n  set_property(SOURCE file.h PROPERTY ", property,
          " ON)\n"));
    }
  }

  // Generate CMP0100 warning
  if (this->MocOrUicEnabled() &&
      !this->AutogenTarget.CMP0100HeadersWarn.empty()) {
    cm::string_view property;
    if (this->Moc.Enabled && this->Uic.Enabled) {
      property = "SKIP_AUTOGEN";
    } else if (this->Moc.Enabled) {
      property = "SKIP_AUTOMOC";
    } else if (this->Uic.Enabled) {
      property = "SKIP_AUTOUIC";
    }
    std::string files;
    for (cmSourceFile const* sf : this->AutogenTarget.CMP0100HeadersWarn) {
      files += cmStrCat("  ", Quoted(sf->GetFullPath()), '\n');
    }
    this->Makefile->IssueMessage(
      MessageType::AUTHOR_WARNING,
      cmStrCat(
        cmPolicies::GetPolicyWarning(cmPolicies::CMP0100), '\n',
        "For compatibility, CMake is excluding the header file(s):\n", files,
        "from processing by ",
        cmQtAutoGen::Tools(this->Moc.Enabled, this->Uic.Enabled, false),
        ".  If any of the files should be processed, set CMP0100 to NEW.  "
        "If any of the files should not be processed, "
        "explicitly exclude them by setting the source file property ",
        property, ":\n  set_property(SOURCE file.hh PROPERTY ", property,
        " ON)\n"));
  }

  // Process qrc files
  if (!this->Rcc.Qrcs.empty()) {
    const bool modernQt = (this->QtVersion.Major >= 5);
    // Target rcc options
    cmList const optionsTarget{ this->GenTarget->GetSafeProperty(
      kw.AUTORCC_OPTIONS) };

    // Check if file name is unique
    for (Qrc& qrc : this->Rcc.Qrcs) {
      qrc.Unique = true;
      for (Qrc const& qrc2 : this->Rcc.Qrcs) {
        if ((&qrc != &qrc2) && (qrc.QrcName == qrc2.QrcName)) {
          qrc.Unique = false;
          break;
        }
      }
    }
    // Path checksum and file names
    for (Qrc& qrc : this->Rcc.Qrcs) {
      // Path checksum
      qrc.QrcPathChecksum = this->PathCheckSum.getPart(qrc.QrcFile);
      // Output file name
      qrc.OutputFile = cmStrCat(this->Dir.Build, '/', qrc.QrcPathChecksum,
                                "/qrc_", qrc.QrcName, ".cpp");
      std::string const base = cmStrCat(this->Dir.Info, "/AutoRcc_",
                                        qrc.QrcName, '_', qrc.QrcPathChecksum);
      qrc.LockFile = cmStrCat(base, "_Lock.lock");
      qrc.InfoFile = cmStrCat(base, "_Info.json");
      this->ConfigFileNames(qrc.SettingsFile, cmStrCat(base, "_Used"), ".txt");
    }
    // rcc options
    for (Qrc& qrc : this->Rcc.Qrcs) {
      // Target options
      std::vector<std::string> opts = optionsTarget;
      // Merge computed "-name XYZ" option
      {
        std::string name = qrc.QrcName;
        // Replace '-' with '_'. The former is not valid for symbol names.
        std::replace(name.begin(), name.end(), '-', '_');
        if (!qrc.Unique) {
          name += cmStrCat('_', qrc.QrcPathChecksum);
        }
        std::vector<std::string> nameOpts;
        nameOpts.emplace_back("-name");
        nameOpts.emplace_back(std::move(name));
        RccMergeOptions(opts, nameOpts, modernQt);
      }
      // Merge file option
      RccMergeOptions(opts, qrc.Options, modernQt);
      qrc.Options = std::move(opts);
    }
    // rcc resources
    for (Qrc& qrc : this->Rcc.Qrcs) {
      if (!qrc.Generated) {
        std::string error;
        RccLister const lister(this->Rcc.Executable,
                               this->Rcc.ExecutableFeatures->ListOptions);
        if (!lister.list(qrc.QrcFile, qrc.Resources, error)) {
          cmSystemTools::Error(error);
          return false;
        }
      }
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::InitAutogenTarget()
{
  // Register info file as generated by CMake
  this->Makefile->AddCMakeOutputFile(this->AutogenTarget.InfoFile);

  // Determine whether to use a depfile for the AUTOGEN target.
  const bool useNinjaDepfile = this->QtVersion >= IntegerVersion(5, 15) &&
    this->GlobalGen->GetName().find("Ninja") != std::string::npos;

  // Files provided by the autogen target
  std::vector<std::string> autogenByproducts;
  std::vector<std::string> timestampByproducts;
  if (this->Moc.Enabled) {
    this->AddGeneratedSource(this->Moc.CompilationFile, this->Moc, true);
    if (useNinjaDepfile) {
      if (this->MultiConfig &&
          !this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
        // Make all mocs_compilation_<CONFIG>.cpp files byproducts of the
        // ${target}_autogen/timestamp custom command.
        // We cannot just use Moc.CompilationFileGenex here, because that
        // custom command runs cmake_autogen for each configuration.
        for (const auto& p : this->Moc.CompilationFile.Config) {
          timestampByproducts.push_back(p.second);
        }
      } else {
        timestampByproducts.push_back(this->Moc.CompilationFileGenex);
      }
    } else {
      autogenByproducts.push_back(this->Moc.CompilationFileGenex);
    }
  }

  if (this->Uic.Enabled) {
    for (const auto& file : this->Uic.UiHeaders) {
      this->AddGeneratedSource(file.first, this->Uic);
      autogenByproducts.push_back(file.second);
    }
  }

  // Compose target comment
  std::string autogenComment;
  {
    std::string tools;
    if (this->Moc.Enabled) {
      tools += "MOC";
    }
    if (this->Uic.Enabled) {
      if (!tools.empty()) {
        tools += " and ";
      }
      tools += "UIC";
    }
    autogenComment = cmStrCat("Automatic ", tools, " for target ",
                              this->GenTarget->GetName());
  }

  // Compose command lines
  // FIXME: Take advantage of our per-config mocs_compilation_$<CONFIG>.cpp
  // instead of fiddling with the include directories
  std::vector<std::string> configs;
  this->GlobalGen->GetQtAutoGenConfigs(configs);
  bool constexpr stdPipesUTF8 = true;
  cmCustomCommandLines commandLines;
  if (this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
    std::string autugenInfoFileconfig;
    if (this->MultiConfig) {
      autugenInfoFileconfig = "$<CONFIG>";
    } else {
      autugenInfoFileconfig = configs[0];
    }
    commandLines.push_back(cmMakeCommandLine(
      { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
        this->AutogenTarget.InfoFile, autugenInfoFileconfig }));

  } else {
    for (auto const& config : configs) {
      commandLines.push_back(cmMakeCommandLine(
        { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
          this->AutogenTarget.InfoFile, config }));
    }
  }

  // Use PRE_BUILD on demand
  bool usePRE_BUILD = false;
  if (this->GlobalGen->GetName().find("Visual Studio") != std::string::npos) {
    // Under VS use a PRE_BUILD event instead of a separate target to
    // reduce the number of targets loaded into the IDE.
    // This also works around a VS 11 bug that may skip updating the target:
    //  https://connect.microsoft.com/VisualStudio/feedback/details/769495
    usePRE_BUILD = true;
  }
  // Disable PRE_BUILD in some cases
  if (usePRE_BUILD) {
    // Cannot use PRE_BUILD with file depends
    if (!this->AutogenTarget.DependFiles.empty()) {
      usePRE_BUILD = false;
    }
    // Cannot use PRE_BUILD when a global autogen target is in place
    if (this->AutogenTarget.GlobalTarget) {
      usePRE_BUILD = false;
    }
  }
  // Create the autogen target/command
  if (usePRE_BUILD) {
    // Add additional autogen target dependencies to origin target
    for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) {
      this->GenTarget->Target->AddUtility(depTarget->GetName(), false,
                                          this->Makefile);
    }

    if (!this->Uic.UiFilesNoOptions.empty() ||
        !this->Uic.UiFilesWithOptions.empty()) {
      // Add a generated timestamp file
      ConfigString timestampFile;
      std::string timestampFileGenex;
      ConfigFileNamesAndGenex(timestampFile, timestampFileGenex,
                              cmStrCat(this->Dir.Build, "/autouic"_s),
                              ".stamp"_s);
      this->AddGeneratedSource(timestampFile, this->Uic);

      // Add a step in the pre-build command to touch the timestamp file
      commandLines.push_back(
        cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", "touch",
                            timestampFileGenex }));

      // UIC needs to be re-run if any of the known UI files change or the
      // executable itself has been updated
      auto uicDependencies = this->Uic.UiFilesNoOptions;
      for (auto const& uiFile : this->Uic.UiFilesWithOptions) {
        uicDependencies.push_back(uiFile.first);
      }
      AddAutogenExecutableToDependencies(this->Uic, uicDependencies);

      // Add a rule file to cause the target to build if a dependency has
      // changed, which will trigger the pre-build command to run autogen
      auto cc = cm::make_unique<cmCustomCommand>();
      cc->SetOutputs(timestampFileGenex);
      cc->SetDepends(uicDependencies);
      cc->SetComment("");
      cc->SetWorkingDirectory(this->Dir.Work.c_str());
      cc->SetEscapeOldStyle(false);
      cc->SetStdPipesUTF8(stdPipesUTF8);
      this->LocalGen->AddCustomCommandToOutput(std::move(cc));
    }

    // Add the pre-build command directly to bypass the OBJECT_LIBRARY
    // rejection in cmMakefile::AddCustomCommandToTarget because we know
    // PRE_BUILD will work for an OBJECT_LIBRARY in this specific case.
    //
    // PRE_BUILD does not support file dependencies!
    cmCustomCommand cc;
    cc.SetByproducts(autogenByproducts);
    cc.SetCommandLines(commandLines);
    cc.SetComment(autogenComment.c_str());
    cc.SetBacktrace(this->Makefile->GetBacktrace());
    cc.SetWorkingDirectory(this->Dir.Work.c_str());
    cc.SetStdPipesUTF8(stdPipesUTF8);
    cc.SetEscapeOldStyle(false);
    cc.SetEscapeAllowMakeVars(true);
    this->GenTarget->Target->AddPreBuildCommand(std::move(cc));
  } else {

    // Add link library target dependencies to the autogen target
    // dependencies
    if (this->AutogenTarget.DependOrigin) {
      // add_dependencies/addUtility do not support generator expressions.
      // We depend only on the libraries found in all configs therefore.
      std::map<cmGeneratorTarget const*, std::size_t> commonTargets;
      for (std::string const& config : this->ConfigsList) {
        cmLinkImplementationLibraries const* libs =
          this->GenTarget->GetLinkImplementationLibraries(
            config, cmGeneratorTarget::LinkInterfaceFor::Link);
        if (libs) {
          for (cmLinkItem const& item : libs->Libraries) {
            cmGeneratorTarget const* libTarget = item.Target;
            if (libTarget &&
                !StaticLibraryCycle(this->GenTarget, libTarget, config)) {
              // Increment target config count
              commonTargets[libTarget]++;
            }
          }
        }
      }
      for (auto const& item : commonTargets) {
        if (item.second == this->ConfigsList.size()) {
          this->AutogenTarget.DependTargets.insert(item.first->Target);
        }
      }
    }

    std::vector<std::string> dependencies(
      this->AutogenTarget.DependFiles.begin(),
      this->AutogenTarget.DependFiles.end());

    if (useNinjaDepfile) {
      // Create a custom command that generates a timestamp file and
      // has a depfile assigned. The depfile is created by JobDepFilesMergeT.
      //
      // Also create an additional '_autogen_timestamp_deps' that the custom
      // command will depend on. It will have no sources or commands to
      // execute, but it will have dependencies that would originally be
      // assigned to the pre-Qt 5.15 'autogen' target. These dependencies will
      // serve as a list of order-only dependencies for the custom command,
      // without forcing the custom command to re-execute.
      //
      // The dependency tree would then look like
      // '_autogen_timestamp_deps (order-only)' <- '/timestamp' file <-
      // '_autogen' target.
      const auto timestampTargetName =
        cmStrCat(this->GenTarget->GetName(), "_autogen_timestamp_deps");

      // Add additional autogen target dependencies to
      // '_autogen_timestamp_deps'.
      for (const cmTarget* t : this->AutogenTarget.DependTargets) {
        std::string depname = t->GetName();
        if (t->IsImported()) {
          auto const ttype = t->GetType();
          if (ttype == cmStateEnums::TargetType::STATIC_LIBRARY ||
              ttype == cmStateEnums::TargetType::SHARED_LIBRARY ||
              ttype == cmStateEnums::TargetType::UNKNOWN_LIBRARY) {
            depname = cmStrCat("$<TARGET_LINKER_FILE:", t->GetName(), ">");
          }
        }
        dependencies.emplace_back(std::move(depname));
      }

      auto cc = cm::make_unique<cmCustomCommand>();
      cc->SetWorkingDirectory(this->Dir.Work.c_str());
      cc->SetDepends(dependencies);
      cc->SetEscapeOldStyle(false);
      cmTarget* timestampTarget = this->LocalGen->AddUtilityCommand(
        timestampTargetName, true, std::move(cc));
      this->LocalGen->AddGeneratorTarget(
        cm::make_unique<cmGeneratorTarget>(timestampTarget, this->LocalGen));

      // Set FOLDER property on the timestamp target, so it appears in the
      // appropriate folder in an IDE or in the file api.
      if (!this->TargetsFolder.empty()) {
        timestampTarget->SetProperty("FOLDER", this->TargetsFolder);
      }

      // Make '/timestamp' file depend on '_autogen_timestamp_deps' and on the
      // moc and uic executables (whichever are enabled).
      dependencies.clear();
      dependencies.push_back(timestampTargetName);

      AddAutogenExecutableToDependencies(this->Moc, dependencies);
      AddAutogenExecutableToDependencies(this->Uic, dependencies);

      // Create the custom command that outputs the timestamp file.
      const char timestampFileName[] = "timestamp";
      const std::string outputFile =
        cmStrCat(this->Dir.Build, "/", timestampFileName);
      this->AutogenTarget.DepFile = cmStrCat(this->Dir.Build, "/deps");
      this->AutogenTarget.DepFileRuleName =
        cmStrCat(this->Dir.RelativeBuild, "/", timestampFileName);
      commandLines.push_back(cmMakeCommandLine(
        { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile }));

      this->AddGeneratedSource(outputFile, this->Moc);
      cc = cm::make_unique<cmCustomCommand>();
      cc->SetOutputs(outputFile);
      cc->SetByproducts(timestampByproducts);
      cc->SetDepends(dependencies);
      cc->SetCommandLines(commandLines);
      cc->SetComment(autogenComment.c_str());
      cc->SetWorkingDirectory(this->Dir.Work.c_str());
      cc->SetEscapeOldStyle(false);
      cc->SetDepfile(this->AutogenTarget.DepFile);
      cc->SetStdPipesUTF8(stdPipesUTF8);
      this->LocalGen->AddCustomCommandToOutput(std::move(cc));

      // Alter variables for the autogen target which now merely wraps the
      // custom command
      dependencies.clear();
      dependencies.push_back(outputFile);
      commandLines.clear();
      autogenComment.clear();
    }

    // Create autogen target
    auto cc = cm::make_unique<cmCustomCommand>();
    cc->SetWorkingDirectory(this->Dir.Work.c_str());
    cc->SetByproducts(autogenByproducts);
    cc->SetDepends(dependencies);
    cc->SetCommandLines(commandLines);
    cc->SetEscapeOldStyle(false);
    cc->SetComment(autogenComment.c_str());
    cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand(
      this->AutogenTarget.Name, true, std::move(cc));
    // Create autogen generator target
    this->LocalGen->AddGeneratorTarget(
      cm::make_unique<cmGeneratorTarget>(autogenTarget, this->LocalGen));

    // Forward origin utilities to autogen target
    if (this->AutogenTarget.DependOrigin) {
      for (BT<std::pair<std::string, bool>> const& depName :
           this->GenTarget->GetUtilities()) {
        autogenTarget->AddUtility(depName.Value.first, false, this->Makefile);
      }
    }
    if (!useNinjaDepfile) {
      // Add additional autogen target dependencies to autogen target
      for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) {
        autogenTarget->AddUtility(depTarget->GetName(), false, this->Makefile);
      }
    }

    // Set FOLDER property in autogen target
    if (!this->TargetsFolder.empty()) {
      autogenTarget->SetProperty("FOLDER", this->TargetsFolder);
    }

    // Add autogen target to the origin target dependencies
    this->GenTarget->Target->AddUtility(this->AutogenTarget.Name, false,
                                        this->Makefile);

    // Add autogen target to the global autogen target dependencies
    if (this->AutogenTarget.GlobalTarget) {
      this->GlobalInitializer->AddToGlobalAutoGen(this->LocalGen,
                                                  this->AutogenTarget.Name);
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::InitRccTargets()
{
  for (Qrc const& qrc : this->Rcc.Qrcs) {
    // Register info file as generated by CMake
    this->Makefile->AddCMakeOutputFile(qrc.InfoFile);
    // Register file at target
    {
      cmSourceFile* sf = this->AddGeneratedSource(qrc.OutputFile, this->Rcc);
      sf->SetProperty("SKIP_UNITY_BUILD_INCLUSION", "On");
    }

    std::vector<std::string> ccOutput;
    ccOutput.push_back(qrc.OutputFile);

    std::vector<std::string> ccDepends;
    // Add the .qrc and info file to the custom command dependencies
    ccDepends.push_back(qrc.QrcFile);
    ccDepends.push_back(qrc.InfoFile);

    cmCustomCommandLines commandLines;
    if (this->MultiConfig) {
      // Build for all configurations
      for (std::string const& config : this->ConfigsList) {
        commandLines.push_back(
          cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E",
                              "cmake_autorcc", qrc.InfoFile, config }));
      }
    } else {
      commandLines.push_back(
        cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E",
                            "cmake_autorcc", qrc.InfoFile, "$<CONFIG>" }));
    }
    std::string ccComment =
      cmStrCat("Automatic RCC for ",
               FileProjectRelativePath(this->Makefile, qrc.QrcFile));

    auto cc = cm::make_unique<cmCustomCommand>();
    cc->SetWorkingDirectory(this->Dir.Work.c_str());
    cc->SetCommandLines(commandLines);
    cc->SetComment(ccComment.c_str());
    cc->SetStdPipesUTF8(true);

    if (qrc.Generated || this->Rcc.GlobalTarget) {
      // Create custom rcc target
      std::string ccName;
      {
        ccName = cmStrCat(this->GenTarget->GetName(), "_arcc_", qrc.QrcName);
        if (!qrc.Unique) {
          ccName += cmStrCat('_', qrc.QrcPathChecksum);
        }

        cc->SetByproducts(ccOutput);
        cc->SetDepends(ccDepends);
        cc->SetEscapeOldStyle(false);
        cmTarget* autoRccTarget =
          this->LocalGen->AddUtilityCommand(ccName, true, std::move(cc));

        // Create autogen generator target
        this->LocalGen->AddGeneratorTarget(
          cm::make_unique<cmGeneratorTarget>(autoRccTarget, this->LocalGen));

        // Set FOLDER property in autogen target
        if (!this->TargetsFolder.empty()) {
          autoRccTarget->SetProperty("FOLDER", this->TargetsFolder);
        }
        if (!this->Rcc.ExecutableTargetName.empty()) {
          autoRccTarget->AddUtility(this->Rcc.ExecutableTargetName, false,
                                    this->Makefile);
        }
      }
      // Add autogen target to the origin target dependencies
      this->GenTarget->Target->AddUtility(ccName, false, this->Makefile);

      // Add autogen target to the global autogen target dependencies
      if (this->Rcc.GlobalTarget) {
        this->GlobalInitializer->AddToGlobalAutoRcc(this->LocalGen, ccName);
      }
    } else {
      // Create custom rcc command
      {
        std::vector<std::string> ccByproducts;

        // Add the resource files to the dependencies
        for (std::string const& fileName : qrc.Resources) {
          // Add resource file to the custom command dependencies
          ccDepends.push_back(fileName);
        }
        if (!this->Rcc.ExecutableTargetName.empty()) {
          ccDepends.push_back(this->Rcc.ExecutableTargetName);
        }
        cc->SetOutputs(ccOutput);
        cc->SetByproducts(ccByproducts);
        cc->SetDepends(ccDepends);
        this->LocalGen->AddCustomCommandToOutput(std::move(cc));
      }
      // Reconfigure when .qrc file changes
      this->Makefile->AddCMakeDependFile(qrc.QrcFile);
    }
  }

  return true;
}

bool cmQtAutoGenInitializer::SetupCustomTargets()
{
  // Create info directory on demand
  if (!cmSystemTools::MakeDirectory(this->Dir.Info)) {
    cmSystemTools::Error(cmStrCat("AutoGen: Could not create directory: ",
                                  Quoted(this->Dir.Info)));
    return false;
  }

  // Generate autogen target info file
  if (this->MocOrUicEnabled()) {
    // Write autogen target info files
    if (!this->SetupWriteAutogenInfo()) {
      return false;
    }
  }

  // Write AUTORCC info files
  return !this->Rcc.Enabled || this->SetupWriteRccInfo();
}

bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
{
  // Utility lambdas
  auto MfDef = [this](std::string const& key) {
    return this->Makefile->GetSafeDefinition(key);
  };

  // Filtered headers and sources
  std::set<std::string> moc_skip;
  std::set<std::string> uic_skip;
  std::vector<MUFile const*> headers;
  std::vector<MUFile const*> sources;

  // Filter headers
  {
    headers.reserve(this->AutogenTarget.Headers.size());
    for (auto const& pair : this->AutogenTarget.Headers) {
      MUFile const* const muf = pair.second.get();
      if (muf->SkipMoc) {
        moc_skip.insert(muf->FullPath);
      }
      if (muf->SkipUic) {
        uic_skip.insert(muf->FullPath);
      }
      if (muf->Generated && !this->CMP0071Accept) {
        continue;
      }
      if (muf->MocIt || muf->UicIt) {
        headers.emplace_back(muf);
      }
    }
    std::sort(headers.begin(), headers.end(),
              [](MUFile const* a, MUFile const* b) {
                return (a->FullPath < b->FullPath);
              });
  }

  // Filter sources
  {
    sources.reserve(this->AutogenTarget.Sources.size());
    for (auto const& pair : this->AutogenTarget.Sources) {
      MUFile const* const muf = pair.second.get();
      if (muf->Generated && !this->CMP0071Accept) {
        continue;
      }
      if (muf->SkipMoc) {
        moc_skip.insert(muf->FullPath);
      }
      if (muf->SkipUic) {
        uic_skip.insert(muf->FullPath);
      }
      if (muf->MocIt || muf->UicIt) {
        sources.emplace_back(muf);
      }
    }
    std::sort(sources.begin(), sources.end(),
              [](MUFile const* a, MUFile const* b) {
                return (a->FullPath < b->FullPath);
              });
  }

  // Info writer
  InfoWriter info;

  // General
  info.SetBool("MULTI_CONFIG", this->MultiConfig);
  info.SetUInt("PARALLEL", this->AutogenTarget.Parallel);
  info.SetUInt("VERBOSITY", this->Verbosity);

  // Directories
  info.Set("CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR"));
  info.Set("CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR"));
  info.Set("CMAKE_CURRENT_SOURCE_DIR", MfDef("CMAKE_CURRENT_SOURCE_DIR"));
  info.Set("CMAKE_CURRENT_BINARY_DIR", MfDef("CMAKE_CURRENT_BINARY_DIR"));
  info.Set("BUILD_DIR", this->Dir.Build);
  info.SetConfig("INCLUDE_DIR", this->Dir.Include);

  info.SetUInt("QT_VERSION_MAJOR", this->QtVersion.Major);
  info.SetUInt("QT_VERSION_MINOR", this->QtVersion.Minor);
  info.Set("QT_MOC_EXECUTABLE", this->Moc.Executable);
  info.Set("QT_UIC_EXECUTABLE", this->Uic.Executable);

  info.Set("CMAKE_EXECUTABLE", cmSystemTools::GetCMakeCommand());
  info.SetConfig("SETTINGS_FILE", this->AutogenTarget.SettingsFile);
  info.SetConfig("PARSE_CACHE_FILE", this->AutogenTarget.ParseCacheFile);
  info.Set("DEP_FILE", this->AutogenTarget.DepFile);
  info.Set("DEP_FILE_RULE_NAME", this->AutogenTarget.DepFileRuleName);
  info.SetArray("CMAKE_LIST_FILES", this->Makefile->GetListFiles());
  info.SetArray("HEADER_EXTENSIONS",
                this->Makefile->GetCMakeInstance()->GetHeaderExtensions());
  auto cfgArray = [this](std::vector<size_t> const& configs) -> Json::Value {
    Json::Value value;
    if (!configs.empty()) {
      value = Json::arrayValue;
      for (size_t ci : configs) {
        value.append(this->ConfigsList[ci]);
      }
    }
    return value;
  };
  info.SetArrayArray("HEADERS", headers,
                     [this, &cfgArray](Json::Value& jval, MUFile const* muf) {
                       jval.resize(4u);
                       jval[0u] = muf->FullPath;
                       jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm',
                                           muf->UicIt ? 'U' : 'u');
                       jval[2u] = this->GetMocBuildPath(*muf);
                       jval[3u] = cfgArray(muf->Configs);
                     });
  info.SetArrayArray(
    "SOURCES", sources, [&cfgArray](Json::Value& jval, MUFile const* muf) {
      jval.resize(3u);
      jval[0u] = muf->FullPath;
      jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u');
      jval[2u] = cfgArray(muf->Configs);
    });

  // Write moc settings
  if (this->Moc.Enabled) {
    info.SetArray("MOC_SKIP", moc_skip);
    info.SetConfigArray("MOC_DEFINITIONS", this->Moc.Defines);
    info.SetConfigArray("MOC_INCLUDES", this->Moc.Includes);
    info.SetArray("MOC_OPTIONS", this->Moc.Options);
    info.SetBool("MOC_RELAXED_MODE", this->Moc.RelaxedMode);
    info.SetBool("MOC_PATH_PREFIX", this->Moc.PathPrefix);

    cmGeneratorExpressionDAGChecker dagChecker(
      this->GenTarget, "AUTOMOC_MACRO_NAMES", nullptr, nullptr);
    EvaluatedTargetPropertyEntries InterfaceAutoMocMacroNamesEntries;

    if (this->MultiConfig) {
      for (auto const& cfg : this->ConfigsList) {
        if (!cfg.empty()) {
          AddInterfaceEntries(this->GenTarget, cfg,
                              "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX",
                              &dagChecker, InterfaceAutoMocMacroNamesEntries,
                              IncludeRuntimeInterface::Yes);
        }
      }
    } else {
      AddInterfaceEntries(this->GenTarget, this->ConfigDefault,
                          "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX", &dagChecker,
                          InterfaceAutoMocMacroNamesEntries,
                          IncludeRuntimeInterface::Yes);
    }

    for (auto const& entry : InterfaceAutoMocMacroNamesEntries.Entries) {
      this->Moc.MacroNames.insert(this->Moc.MacroNames.end(),
                                  entry.Values.begin(), entry.Values.end());
    }
    this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames),
                               this->Moc.MacroNames.end());

    info.SetArray("MOC_MACRO_NAMES", this->Moc.MacroNames);
    info.SetArrayArray(
      "MOC_DEPEND_FILTERS", this->Moc.DependFilters,
      [](Json::Value& jval, std::pair<std::string, std::string> const& pair) {
        jval.resize(2u);
        jval[0u] = pair.first;
        jval[1u] = pair.second;
      });
    info.SetConfig("MOC_COMPILATION_FILE", this->Moc.CompilationFile);
    info.SetConfig("MOC_PREDEFS_FILE", this->Moc.PredefsFile);

    cmStandardLevelResolver const resolver{ this->Makefile };
    auto const CompileOptionFlag =
      resolver.GetCompileOptionDef(this->GenTarget, "CXX", "");

    auto const CompileOptionValue =
      this->GenTarget->Makefile->GetSafeDefinition(CompileOptionFlag);

    if (!CompileOptionValue.empty()) {
      if (this->Moc.PredefsCmd.size() >= 3) {
        this->Moc.PredefsCmd.insert(this->Moc.PredefsCmd.begin() + 1,
                                    CompileOptionValue);
      }
    }
    info.SetArray("MOC_PREDEFS_CMD", this->Moc.PredefsCmd);
  }

  // Write uic settings
  if (this->Uic.Enabled) {
    // Add skipped .ui files
    uic_skip.insert(this->Uic.SkipUi.begin(), this->Uic.SkipUi.end());

    info.SetArray("UIC_SKIP", uic_skip);
    info.SetArrayArray("UIC_UI_FILES", this->Uic.UiFilesWithOptions,
                       [](Json::Value& jval, UicT::UiFileT const& uiFile) {
                         jval.resize(2u);
                         jval[0u] = uiFile.first;
                         InfoWriter::MakeStringArray(jval[1u], uiFile.second);
                       });
    info.SetConfigArray("UIC_OPTIONS", this->Uic.Options);
    info.SetArray("UIC_SEARCH_PATHS", this->Uic.SearchPaths);
  }

  info.Save(this->AutogenTarget.InfoFile);

  return true;
}

bool cmQtAutoGenInitializer::SetupWriteRccInfo()
{
  for (Qrc const& qrc : this->Rcc.Qrcs) {
    // Utility lambdas
    auto MfDef = [this](std::string const& key) {
      return this->Makefile->GetSafeDefinition(key);
    };

    InfoWriter info;

    // General
    info.SetBool("MULTI_CONFIG", this->MultiConfig);
    info.SetUInt("VERBOSITY", this->Verbosity);

    // Files
    info.Set("LOCK_FILE", qrc.LockFile);
    info.SetConfig("SETTINGS_FILE", qrc.SettingsFile);

    // Directories
    info.Set("CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR"));
    info.Set("CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR"));
    info.Set("CMAKE_CURRENT_SOURCE_DIR", MfDef("CMAKE_CURRENT_SOURCE_DIR"));
    info.Set("CMAKE_CURRENT_BINARY_DIR", MfDef("CMAKE_CURRENT_BINARY_DIR"));
    info.Set("BUILD_DIR", this->Dir.Build);
    info.SetConfig("INCLUDE_DIR", this->Dir.Include);

    // rcc executable
    info.Set("RCC_EXECUTABLE", this->Rcc.Executable);
    info.SetArray("RCC_LIST_OPTIONS",
                  this->Rcc.ExecutableFeatures->ListOptions);

    // qrc file
    info.Set("SOURCE", qrc.QrcFile);
    info.Set("OUTPUT_CHECKSUM", qrc.QrcPathChecksum);
    info.Set("OUTPUT_NAME", cmSystemTools::GetFilenameName(qrc.OutputFile));
    info.SetArray("OPTIONS", qrc.Options);
    info.SetArray("INPUTS", qrc.Resources);

    info.Save(qrc.InfoFile);
  }

  return true;
}

cmSourceFile* cmQtAutoGenInitializer::RegisterGeneratedSource(
  std::string const& filename)
{
  cmSourceFile* gFile = this->Makefile->GetOrCreateSource(filename, true);
  gFile->MarkAsGenerated();
  gFile->SetProperty("SKIP_AUTOGEN", "1");
  gFile->SetProperty("SKIP_LINTING", "ON");
  return gFile;
}

cmSourceFile* cmQtAutoGenInitializer::AddGeneratedSource(
  std::string const& filename, GenVarsT const& genVars, bool prepend)
{
  // Register source at makefile
  cmSourceFile* gFile = this->RegisterGeneratedSource(filename);
  // Add source file to target
  this->GenTarget->AddSource(filename, prepend);

  // Add source file to source group
  this->AddToSourceGroup(filename, genVars.GenNameUpper);

  return gFile;
}

void cmQtAutoGenInitializer::AddGeneratedSource(ConfigString const& filename,
                                                GenVarsT const& genVars,
                                                bool prepend)
{
  // XXX(xcode-per-cfg-src): Drop the Xcode-specific part of the condition
  // when the Xcode generator supports per-config sources.
  if (!this->MultiConfig || this->GlobalGen->IsXcode()) {
    cmSourceFile* sf =
      this->AddGeneratedSource(filename.Default, genVars, prepend);
    handleSkipPch(sf);
    return;
  }
  for (auto const& cfg : this->ConfigsList) {
    std::string const& filenameCfg = filename.Config.at(cfg);
    // Register source at makefile
    cmSourceFile* sf = this->RegisterGeneratedSource(filenameCfg);
    handleSkipPch(sf);
    // Add source file to target for this configuration.
    this->GenTarget->AddSource(
      cmStrCat("$<$<CONFIG:"_s, cfg, ">:"_s, filenameCfg, ">"_s), prepend);
    // Add source file to source group
    this->AddToSourceGroup(filenameCfg, genVars.GenNameUpper);
  }
}

void cmQtAutoGenInitializer::AddToSourceGroup(std::string const& fileName,
                                              cm::string_view genNameUpper)
{
  cmSourceGroup* sourceGroup = nullptr;
  // Acquire source group
  {
    std::string property;
    std::string groupName;
    {
      // Prefer generator specific source group name
      std::initializer_list<std::string> const props{
        cmStrCat(genNameUpper, "_SOURCE_GROUP"), "AUTOGEN_SOURCE_GROUP"
      };
      for (std::string const& prop : props) {
        cmValue propName = this->Makefile->GetState()->GetGlobalProperty(prop);
        if (cmNonempty(propName)) {
          groupName = *propName;
          property = prop;
          break;
        }
      }
    }
    // Generate a source group on demand
    if (!groupName.empty()) {
      sourceGroup = this->Makefile->GetOrCreateSourceGroup(groupName);
      if (!sourceGroup) {
        cmSystemTools::Error(
          cmStrCat(genNameUpper, " error in ", property,
                   ": Could not find or create the source group ",
                   cmQtAutoGen::Quoted(groupName)));
      }
    }
  }
  if (sourceGroup) {
    sourceGroup->AddGroupFile(fileName);
  }
}

void cmQtAutoGenInitializer::AddCleanFile(std::string const& fileName)
{
  this->GenTarget->Target->AppendProperty("ADDITIONAL_CLEAN_FILES", fileName);
}

void cmQtAutoGenInitializer::ConfigFileNames(ConfigString& configString,
                                             cm::string_view prefix,
                                             cm::string_view suffix)
{
  configString.Default = cmStrCat(prefix, suffix);
  if (this->MultiConfig) {
    for (auto const& cfg : this->ConfigsList) {
      configString.Config[cfg] = cmStrCat(prefix, '_', cfg, suffix);
    }
  }
}

void cmQtAutoGenInitializer::ConfigFileNamesAndGenex(
  ConfigString& configString, std::string& genex, cm::string_view const prefix,
  cm::string_view const suffix)
{
  this->ConfigFileNames(configString, prefix, suffix);
  if (this->MultiConfig) {
    genex = cmStrCat(prefix, "_$<CONFIG>"_s, suffix);
  } else {
    genex = configString.Default;
  }
}

void cmQtAutoGenInitializer::ConfigFileClean(ConfigString& configString)
{
  this->AddCleanFile(configString.Default);
  if (this->MultiConfig) {
    for (auto const& pair : configString.Config) {
      this->AddCleanFile(pair.second);
    }
  }
}

static cmQtAutoGen::IntegerVersion parseMocVersion(std::string str)
{
  cmQtAutoGen::IntegerVersion result;

  static const std::string prelude = "moc ";
  size_t const pos = str.find(prelude);
  if (pos == std::string::npos) {
    return result;
  }

  str.erase(0, prelude.size() + pos);
  std::istringstream iss(str);
  std::string major;
  std::string minor;
  if (!std::getline(iss, major, '.') || !std::getline(iss, minor, '.')) {
    return result;
  }

  result.Major = static_cast<unsigned int>(std::stoi(major));
  result.Minor = static_cast<unsigned int>(std::stoi(minor));
  return result;
}

static cmQtAutoGen::IntegerVersion GetMocVersion(
  const std::string& mocExecutablePath)
{
  std::string capturedStdOut;
  int exitCode;
  if (!cmSystemTools::RunSingleCommand({ mocExecutablePath, "--version" },
                                       &capturedStdOut, nullptr, &exitCode,
                                       nullptr, cmSystemTools::OUTPUT_NONE)) {
    return {};
  }

  if (exitCode != 0) {
    return {};
  }

  return parseMocVersion(capturedStdOut);
}

static std::string FindMocExecutableFromMocTarget(cmMakefile const* makefile,
                                                  unsigned int qtMajorVersion)
{
  std::string result;
  const std::string mocTargetName =
    "Qt" + std::to_string(qtMajorVersion) + "::moc";
  cmTarget const* mocTarget = makefile->FindTargetToUse(mocTargetName);
  if (mocTarget) {
    result = mocTarget->GetSafeProperty("IMPORTED_LOCATION");
  }
  return result;
}

std::pair<cmQtAutoGen::IntegerVersion, unsigned int>
cmQtAutoGenInitializer::GetQtVersion(cmGeneratorTarget const* target,
                                     std::string mocExecutable)
{
  // Converts a char ptr to an unsigned int value
  auto toUInt = [](const char* const input) -> unsigned int {
    unsigned long tmp = 0;
    if (input && cmStrToULong(input, &tmp)) {
      return static_cast<unsigned int>(tmp);
    }
    return 0u;
  };
  auto toUInt2 = [](cmValue input) -> unsigned int {
    unsigned long tmp = 0;
    if (input && cmStrToULong(*input, &tmp)) {
      return static_cast<unsigned int>(tmp);
    }
    return 0u;
  };

  // Initialize return value to a default
  std::pair<IntegerVersion, unsigned int> res(
    IntegerVersion(),
    toUInt(target->GetLinkInterfaceDependentStringProperty("QT_MAJOR_VERSION",
                                                           "")));

  // Acquire known Qt versions
  std::vector<cmQtAutoGen::IntegerVersion> knownQtVersions;
  {
    // Qt version variable prefixes
    static std::initializer_list<
      std::pair<cm::string_view, cm::string_view>> const keys{
      { "Qt6Core_VERSION_MAJOR", "Qt6Core_VERSION_MINOR" },
      { "Qt5Core_VERSION_MAJOR", "Qt5Core_VERSION_MINOR" },
      { "QT_VERSION_MAJOR", "QT_VERSION_MINOR" },
    };

    knownQtVersions.reserve(keys.size() * 2);

    // Adds a version to the result (nullptr safe)
    auto addVersion = [&knownQtVersions, &toUInt2](cmValue major,
                                                   cmValue minor) {
      cmQtAutoGen::IntegerVersion ver(toUInt2(major), toUInt2(minor));
      if (ver.Major != 0) {
        knownQtVersions.emplace_back(ver);
      }
    };

    // Read versions from variables
    for (auto const& keyPair : keys) {
      addVersion(target->Makefile->GetDefinition(std::string(keyPair.first)),
                 target->Makefile->GetDefinition(std::string(keyPair.second)));
    }

    // Read versions from directory properties
    for (auto const& keyPair : keys) {
      addVersion(target->Makefile->GetProperty(std::string(keyPair.first)),
                 target->Makefile->GetProperty(std::string(keyPair.second)));
    }
  }

  // Evaluate known Qt versions
  if (!knownQtVersions.empty()) {
    if (res.second == 0) {
      // No specific version was requested by the target:
      // Use highest known Qt version.
      res.first = knownQtVersions.at(0);
    } else {
      // Pick a version from the known versions:
      for (auto const& it : knownQtVersions) {
        if (it.Major == res.second) {
          res.first = it;
          break;
        }
      }
    }
  }

  if (res.first.Major == 0) {
    // We could not get the version number from variables or directory
    // properties. This might happen if the find_package call for Qt is wrapped
    // in a function. Try to find the moc executable path from the available
    // targets and call "moc --version" to get the Qt version.
    if (mocExecutable.empty()) {
      mocExecutable =
        FindMocExecutableFromMocTarget(target->Makefile, res.second);
    }
    if (!mocExecutable.empty()) {
      res.first = GetMocVersion(mocExecutable);
    }
  }

  return res;
}

std::string cmQtAutoGenInitializer::GetMocBuildPath(MUFile const& muf)
{
  std::string res;
  if (!muf.MocIt) {
    return res;
  }

  std::string basePath =
    cmStrCat(this->PathCheckSum.getPart(muf.FullPath), "/moc_",
             FileNameWithoutLastExtension(muf.FullPath));

  res = cmStrCat(basePath, ".cpp");
  if (this->Moc.EmittedBuildPaths.emplace(res).second) {
    return res;
  }

  // File name already emitted.
  // Try appending the header suffix to the base path.
  basePath = cmStrCat(basePath, '_', muf.SF->GetExtension());
  res = cmStrCat(basePath, ".cpp");
  if (this->Moc.EmittedBuildPaths.emplace(res).second) {
    return res;
  }

  // File name with header extension already emitted.
  // Try adding a number to the base path.
  constexpr std::size_t number_begin = 2;
  constexpr std::size_t number_end = 256;
  for (std::size_t ii = number_begin; ii != number_end; ++ii) {
    res = cmStrCat(basePath, '_', ii, ".cpp");
    if (this->Moc.EmittedBuildPaths.emplace(res).second) {
      return res;
    }
  }

  // Output file name conflict (unlikely, but still...)
  cmSystemTools::Error(
    cmStrCat("moc output file name conflict for ", muf.FullPath));

  return res;
}

bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars,
                                             const std::string& executable,
                                             bool ignoreMissingTarget) const
{
  auto print_err = [this, &genVars](std::string const& err) {
    cmSystemTools::Error(cmStrCat(genVars.GenNameUpper, " for target ",
                                  this->GenTarget->GetName(), ": ", err));
  };

  // Custom executable
  {
    std::string const prop = cmStrCat(genVars.GenNameUpper, "_EXECUTABLE");
    std::string const& val = this->GenTarget->Target->GetSafeProperty(prop);
    if (!val.empty()) {
      // Evaluate generator expression
      {
        cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
        cmGeneratorExpression ge(*this->Makefile->GetCMakeInstance(), lfbt);
        std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(val);
        genVars.Executable = cge->Evaluate(this->LocalGen, "");
      }
      if (genVars.Executable.empty() && !ignoreMissingTarget) {
        print_err(prop + " evaluates to an empty value");
        return false;
      }

      // Create empty compiler features.
      genVars.ExecutableFeatures =
        std::make_shared<cmQtAutoGen::CompilerFeatures>();
      return true;
    }
  }

  // Find executable target
  {
    // Find executable target name
    cm::string_view prefix;
    if (this->QtVersion.Major == 4) {
      prefix = "Qt4::";
    } else if (this->QtVersion.Major == 5) {
      prefix = "Qt5::";
    } else if (this->QtVersion.Major == 6) {
      prefix = "Qt6::";
    }
    std::string const targetName = cmStrCat(prefix, executable);

    // Find target
    cmGeneratorTarget* genTarget =
      this->LocalGen->FindGeneratorTargetToUse(targetName);
    if (genTarget) {
      genVars.ExecutableTargetName = targetName;
      genVars.ExecutableTarget = genTarget;
      if (genTarget->IsImported()) {
        genVars.Executable = genTarget->ImportedGetLocation("");
      } else {
        genVars.Executable = genTarget->GetLocation("");
      }
    } else {
      if (ignoreMissingTarget) {
        // Create empty compiler features.
        genVars.ExecutableFeatures =
          std::make_shared<cmQtAutoGen::CompilerFeatures>();
        return true;
      }
      print_err(cmStrCat("Could not find ", executable, " executable target ",
                         targetName));
      return false;
    }
  }

  // Get executable features
  {
    std::string err;
    genVars.ExecutableFeatures = this->GlobalInitializer->GetCompilerFeatures(
      executable, genVars.Executable, err);
    if (!genVars.ExecutableFeatures) {
      print_err(err);
      return false;
    }
  }

  return true;
}

void cmQtAutoGenInitializer::handleSkipPch(cmSourceFile* sf)
{
  bool skipPch = true;
  for (auto const& pair : this->AutogenTarget.Sources) {
    if (!pair.first->GetIsGenerated() &&
        !pair.first->GetProperty("SKIP_PRECOMPILE_HEADERS")) {
      skipPch = false;
    }
  }

  if (skipPch) {
    sf->SetProperty("SKIP_PRECOMPILE_HEADERS", "ON");
  }
}
