From a008578deebfa71b38786281450e3d9cf84f5847 Mon Sep 17 00:00:00 2001
From: Sebastian Holtermann <sebholt@xwmw.org>
Date: Wed, 3 Jan 2018 16:59:40 +0100
Subject: Autogen: Process files concurrently in AUTOMOC and AUTOUIC

This introduces concurrent thread processing in the `_autogen`
target wich processes AUTOMOC and AUTOUIC.
Source file parsing is distributed among the threads by
using a job queue from which the threads pull new parse jobs.
Each thread might start an independent ``moc`` or ``uic`` process.
Altogether this roughly speeds up the AUTOMOC and AUTOUIC build
process by the number of physical CPUs on the host system.

The exact number of threads to start in  the `_autogen` target
is controlled by the new AUTOGEN_PARALLEL target property which
is initialized by the new CMAKE_AUTOGEN_PARALLEL variable.
If AUTOGEN_PARALLEL is empty or unset (which is the default)
the thread count is set to the number of physical CPUs on
the host system.

The AUTOMOC/AUTOUIC generator and the AUTORCC generator are
refactored to use a libuv loop internally.

Closes #17422.
---
 Modules/AutoRccInfo.cmake.in            |    4 -
 Modules/AutogenInfo.cmake.in            |    1 +
 Source/cmQtAutoGen.cxx                  |  314 ++-
 Source/cmQtAutoGen.h                    |   55 +-
 Source/cmQtAutoGenerator.cxx            |  669 +++++--
 Source/cmQtAutoGenerator.h              |  308 ++-
 Source/cmQtAutoGeneratorInitializer.cxx |  177 +-
 Source/cmQtAutoGeneratorInitializer.h   |   11 +-
 Source/cmQtAutoGeneratorMocUic.cxx      | 3153 +++++++++++++++++--------------
 Source/cmQtAutoGeneratorMocUic.h        |  510 +++--
 Source/cmQtAutoGeneratorRcc.cxx         |  719 ++++---
 Source/cmQtAutoGeneratorRcc.h           |   97 +-
 Source/cmTarget.cxx                     |    1 +
 13 files changed, 3682 insertions(+), 2337 deletions(-)

diff --git a/Modules/AutoRccInfo.cmake.in b/Modules/AutoRccInfo.cmake.in
index 5457a6f..cbab4a7 100644
--- a/Modules/AutoRccInfo.cmake.in
+++ b/Modules/AutoRccInfo.cmake.in
@@ -1,10 +1,6 @@
 # Meta
 set(ARCC_MULTI_CONFIG @_multi_config@)
 # Directories and files
-set(ARCC_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/")
-set(ARCC_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/")
-set(ARCC_CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@/")
-set(ARCC_CMAKE_CURRENT_BINARY_DIR "@CMAKE_CURRENT_BINARY_DIR@/")
 set(ARCC_BUILD_DIR @_build_dir@)
 # Qt environment
 set(ARCC_RCC_EXECUTABLE @_qt_rcc_executable@)
diff --git a/Modules/AutogenInfo.cmake.in b/Modules/AutogenInfo.cmake.in
index 9a4a06d..7320c0a 100644
--- a/Modules/AutogenInfo.cmake.in
+++ b/Modules/AutogenInfo.cmake.in
@@ -1,5 +1,6 @@
 # Meta
 set(AM_MULTI_CONFIG @_multi_config@)
+set(AM_PARALLEL @_parallel@)
 # Directories and files
 set(AM_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/")
 set(AM_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/")
diff --git a/Source/cmQtAutoGen.cxx b/Source/cmQtAutoGen.cxx
index 255a532..18ecbe7 100644
--- a/Source/cmQtAutoGen.cxx
+++ b/Source/cmQtAutoGen.cxx
@@ -2,16 +2,13 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmQtAutoGen.h"
 #include "cmAlgorithms.h"
-#include "cmProcessOutput.h"
 #include "cmSystemTools.h"
 
-#include "cmsys/FStream.hxx"
 #include "cmsys/RegularExpression.hxx"
 
 #include <algorithm>
 #include <iterator>
 #include <sstream>
-#include <stddef.h>
 
 // - Static variables
 
@@ -21,8 +18,8 @@ std::string const genNameUic = "AutoUic";
 std::string const genNameRcc = "AutoRcc";
 
 std::string const mcNameSingle = "SINGLE";
-std::string const mcNameWrap = "WRAP";
-std::string const mcNameFull = "FULL";
+std::string const mcNameWrapper = "WRAPPER";
+std::string const mcNameMulti = "MULTI";
 
 // - Static functions
 
@@ -80,203 +77,53 @@ void MergeOptions(std::vector<std::string>& baseOpts,
   baseOpts.insert(baseOpts.end(), extraOpts.begin(), extraOpts.end());
 }
 
-/// @brief Reads the resource files list from from a .qrc file - Qt4 version
-/// @return True if the .qrc file was successfully parsed
-static bool RccListInputsQt4(std::string const& fileName,
-                             std::vector<std::string>& files,
-                             std::string* errorMessage)
-{
-  bool allGood = true;
-  // Read qrc file content into string
-  std::string qrcContents;
-  {
-    cmsys::ifstream ifs(fileName.c_str());
-    if (ifs) {
-      std::ostringstream osst;
-      osst << ifs.rdbuf();
-      qrcContents = osst.str();
-    } else {
-      if (errorMessage != nullptr) {
-        std::string& err = *errorMessage;
-        err = "rcc file not readable:\n  ";
-        err += cmQtAutoGen::Quoted(fileName);
-        err += "\n";
-      }
-      allGood = false;
-    }
-  }
-  if (allGood) {
-    // qrc file directory
-    std::string qrcDir(cmSystemTools::GetFilenamePath(fileName));
-    if (!qrcDir.empty()) {
-      qrcDir += '/';
-    }
-
-    cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
-    cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
-
-    size_t offset = 0;
-    while (fileMatchRegex.find(qrcContents.c_str() + offset)) {
-      std::string qrcEntry = fileMatchRegex.match(1);
-      offset += qrcEntry.size();
-      {
-        fileReplaceRegex.find(qrcEntry);
-        std::string tag = fileReplaceRegex.match(1);
-        qrcEntry = qrcEntry.substr(tag.size());
-      }
-      if (!cmSystemTools::FileIsFullPath(qrcEntry.c_str())) {
-        qrcEntry = qrcDir + qrcEntry;
-      }
-      files.push_back(qrcEntry);
-    }
-  }
-  return allGood;
-}
-
-/// @brief Reads the resource files list from from a .qrc file - Qt5 version
-/// @return True if the .qrc file was successfully parsed
-static bool RccListInputsQt5(std::string const& rccCommand,
-                             std::vector<std::string> const& rccListOptions,
-                             std::string const& fileName,
-                             std::vector<std::string>& files,
-                             std::string* errorMessage)
-{
-  if (rccCommand.empty()) {
-    cmSystemTools::Error("rcc executable not available");
-    return false;
-  }
-
-  std::string const fileDir = cmSystemTools::GetFilenamePath(fileName);
-  std::string const fileNameName = cmSystemTools::GetFilenameName(fileName);
-
-  // Run rcc list command
-  bool result = false;
-  int retVal = 0;
-  std::string rccStdOut;
-  std::string rccStdErr;
-  {
-    std::vector<std::string> command;
-    command.push_back(rccCommand);
-    command.insert(command.end(), rccListOptions.begin(),
-                   rccListOptions.end());
-    command.push_back(fileNameName);
-    result = cmSystemTools::RunSingleCommand(
-      command, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
-      cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto);
-  }
-  if (!result || retVal) {
-    if (errorMessage != nullptr) {
-      std::string& err = *errorMessage;
-      err = "rcc list process failed for:\n  ";
-      err += cmQtAutoGen::Quoted(fileName);
-      err += "\n";
-      err += rccStdOut;
-      err += "\n";
-      err += rccStdErr;
-      err += "\n";
-    }
-    return false;
-  }
-
-  // Lambda to strip CR characters
-  auto StripCR = [](std::string& line) {
-    std::string::size_type cr = line.find('\r');
-    if (cr != std::string::npos) {
-      line = line.substr(0, cr);
-    }
-  };
-
-  // Parse rcc std output
-  {
-    std::istringstream ostr(rccStdOut);
-    std::string oline;
-    while (std::getline(ostr, oline)) {
-      StripCR(oline);
-      if (!oline.empty()) {
-        files.push_back(oline);
-      }
-    }
-  }
-  // Parse rcc error output
-  {
-    std::istringstream estr(rccStdErr);
-    std::string eline;
-    while (std::getline(estr, eline)) {
-      StripCR(eline);
-      if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
-        static std::string searchString = "Cannot find file '";
-
-        std::string::size_type pos = eline.find(searchString);
-        if (pos == std::string::npos) {
-          if (errorMessage != nullptr) {
-            std::string& err = *errorMessage;
-            err = "rcc lists unparsable output:\n";
-            err += cmQtAutoGen::Quoted(eline);
-            err += "\n";
-          }
-          return false;
-        }
-        pos += searchString.length();
-        std::string::size_type sz = eline.size() - pos - 1;
-        files.push_back(eline.substr(pos, sz));
-      }
-    }
-  }
-
-  // Convert relative paths to absolute paths
-  for (std::string& resFile : files) {
-    resFile = cmSystemTools::CollapseCombinedPath(fileDir, resFile);
-  }
-
-  return true;
-}
-
 // - Class definitions
 
-std::string const cmQtAutoGen::listSep = "<<<S>>>";
+std::string const cmQtAutoGen::ListSep = "<<<S>>>";
+unsigned int const cmQtAutoGen::ParallelMax = 64;
 
-std::string const& cmQtAutoGen::GeneratorName(Generator type)
+std::string const& cmQtAutoGen::GeneratorName(GeneratorT type)
 {
   switch (type) {
-    case Generator::GEN:
+    case GeneratorT::GEN:
       return genNameGen;
-    case Generator::MOC:
+    case GeneratorT::MOC:
       return genNameMoc;
-    case Generator::UIC:
+    case GeneratorT::UIC:
       return genNameUic;
-    case Generator::RCC:
+    case GeneratorT::RCC:
       return genNameRcc;
   }
   return genNameGen;
 }
 
-std::string cmQtAutoGen::GeneratorNameUpper(Generator genType)
+std::string cmQtAutoGen::GeneratorNameUpper(GeneratorT genType)
 {
   return cmSystemTools::UpperCase(cmQtAutoGen::GeneratorName(genType));
 }
 
-std::string const& cmQtAutoGen::MultiConfigName(MultiConfig config)
+std::string const& cmQtAutoGen::MultiConfigName(MultiConfigT config)
 {
   switch (config) {
-    case MultiConfig::SINGLE:
+    case MultiConfigT::SINGLE:
       return mcNameSingle;
-    case MultiConfig::WRAP:
-      return mcNameWrap;
-    case MultiConfig::FULL:
-      return mcNameFull;
+    case MultiConfigT::WRAPPER:
+      return mcNameWrapper;
+    case MultiConfigT::MULTI:
+      return mcNameMulti;
   }
-  return mcNameWrap;
+  return mcNameWrapper;
 }
 
-cmQtAutoGen::MultiConfig cmQtAutoGen::MultiConfigType(std::string const& name)
+cmQtAutoGen::MultiConfigT cmQtAutoGen::MultiConfigType(std::string const& name)
 {
   if (name == mcNameSingle) {
-    return MultiConfig::SINGLE;
+    return MultiConfigT::SINGLE;
   }
-  if (name == mcNameFull) {
-    return MultiConfig::FULL;
+  if (name == mcNameMulti) {
+    return MultiConfigT::MULTI;
   }
-  return MultiConfig::WRAP;
+  return MultiConfigT::WRAPPER;
 }
 
 std::string cmQtAutoGen::Quoted(std::string const& text)
@@ -294,6 +141,33 @@ std::string cmQtAutoGen::Quoted(std::string const& text)
   return res;
 }
 
+std::string cmQtAutoGen::QuotedCommand(std::vector<std::string> const& command)
+{
+  std::string res;
+  for (std::string const& item : command) {
+    if (!res.empty()) {
+      res.push_back(' ');
+    }
+    std::string const cesc = cmQtAutoGen::Quoted(item);
+    if (item.empty() || (cesc.size() > (item.size() + 2)) ||
+        (cesc.find(' ') != std::string::npos)) {
+      res += cesc;
+    } else {
+      res += item;
+    }
+  }
+  return res;
+}
+
+std::string cmQtAutoGen::SubDirPrefix(std::string const& filename)
+{
+  std::string res(cmSystemTools::GetFilenamePath(filename));
+  if (!res.empty()) {
+    res += '/';
+  }
+  return res;
+}
+
 std::string cmQtAutoGen::AppendFilenameSuffix(std::string const& filename,
                                               std::string const& suffix)
 {
@@ -333,27 +207,79 @@ void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts,
   MergeOptions(baseOpts, newOpts, valueOpts, isQt5);
 }
 
-bool cmQtAutoGen::RccListInputs(std::string const& rccCommand,
-                                std::vector<std::string> const& rccListOptions,
-                                std::string const& fileName,
-                                std::vector<std::string>& files,
-                                std::string* errorMessage)
+void cmQtAutoGen::RccListParseContent(std::string const& content,
+                                      std::vector<std::string>& files)
 {
-  bool allGood = false;
-  if (cmSystemTools::FileExists(fileName.c_str())) {
-    if (rccListOptions.empty()) {
-      allGood = RccListInputsQt4(fileName, files, errorMessage);
-    } else {
-      allGood = RccListInputsQt5(rccCommand, rccListOptions, fileName, files,
-                                 errorMessage);
+  cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
+  cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
+
+  const char* contentChars = content.c_str();
+  while (fileMatchRegex.find(contentChars)) {
+    std::string const qrcEntry = fileMatchRegex.match(1);
+    contentChars += qrcEntry.size();
+    {
+      fileReplaceRegex.find(qrcEntry);
+      std::string const tag = fileReplaceRegex.match(1);
+      files.push_back(qrcEntry.substr(tag.size()));
     }
-  } else {
-    if (errorMessage != nullptr) {
-      std::string& err = *errorMessage;
-      err = "rcc resource file does not exist:\n  ";
-      err += cmQtAutoGen::Quoted(fileName);
-      err += "\n";
+  }
+}
+
+bool cmQtAutoGen::RccListParseOutput(std::string const& rccStdOut,
+                                     std::string const& rccStdErr,
+                                     std::vector<std::string>& files,
+                                     std::string& error)
+{
+  // Lambda to strip CR characters
+  auto StripCR = [](std::string& line) {
+    std::string::size_type cr = line.find('\r');
+    if (cr != std::string::npos) {
+      line = line.substr(0, cr);
     }
+  };
+
+  // Parse rcc std output
+  {
+    std::istringstream ostr(rccStdOut);
+    std::string oline;
+    while (std::getline(ostr, oline)) {
+      StripCR(oline);
+      if (!oline.empty()) {
+        files.push_back(oline);
+      }
+    }
+  }
+  // Parse rcc error output
+  {
+    std::istringstream estr(rccStdErr);
+    std::string eline;
+    while (std::getline(estr, eline)) {
+      StripCR(eline);
+      if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
+        static std::string const searchString = "Cannot find file '";
+
+        std::string::size_type pos = eline.find(searchString);
+        if (pos == std::string::npos) {
+          error = "rcc lists unparsable output:\n";
+          error += cmQtAutoGen::Quoted(eline);
+          error += "\n";
+          return false;
+        }
+        pos += searchString.length();
+        std::string::size_type sz = eline.size() - pos - 1;
+        files.push_back(eline.substr(pos, sz));
+      }
+    }
+  }
+
+  return true;
+}
+
+void cmQtAutoGen::RccListConvertFullPath(std::string const& qrcFileDir,
+                                         std::vector<std::string>& files)
+{
+  for (std::string& entry : files) {
+    std::string tmp = cmSystemTools::CollapseCombinedPath(qrcFileDir, entry);
+    entry = std::move(tmp);
   }
-  return allGood;
 }
diff --git a/Source/cmQtAutoGen.h b/Source/cmQtAutoGen.h
index e769e93..30ce0f6 100644
--- a/Source/cmQtAutoGen.h
+++ b/Source/cmQtAutoGen.h
@@ -9,14 +9,18 @@
 #include <vector>
 
 /** \class cmQtAutoGen
- * \brief Class used as namespace for QtAutogen related types  and functions
+ * \brief Common base class for QtAutoGen classes
  */
 class cmQtAutoGen
 {
 public:
-  static std::string const listSep;
+  /// @brief Nested lists separator
+  static std::string const ListSep;
+  /// @brief Maximum number of parallel threads/processes in a generator
+  static unsigned int const ParallelMax;
 
-  enum Generator
+  /// @brief AutoGen generator type
+  enum class GeneratorT
   {
     GEN, // General
     MOC,
@@ -24,27 +28,33 @@ public:
     RCC
   };
 
-  enum MultiConfig
+  /// @brief Multiconfiguration type
+  enum class MultiConfigT
   {
-    SINGLE, // Single configuration
-    WRAP,   // Multi configuration using wrapper files
-    FULL    // Full multi configuration using per config sources
+    SINGLE,  // Single configuration
+    WRAPPER, // Multi configuration using wrapper files
+    MULTI    // Multi configuration using per config sources
   };
 
 public:
   /// @brief Returns the generator name
-  static std::string const& GeneratorName(Generator genType);
+  static std::string const& GeneratorName(GeneratorT genType);
   /// @brief Returns the generator name in upper case
-  static std::string GeneratorNameUpper(Generator genType);
+  static std::string GeneratorNameUpper(GeneratorT genType);
 
   /// @brief Returns the multi configuration name string
-  static std::string const& MultiConfigName(MultiConfig config);
+  static std::string const& MultiConfigName(MultiConfigT config);
   /// @brief Returns the multi configuration type
-  static MultiConfig MultiConfigType(std::string const& name);
+  static MultiConfigT MultiConfigType(std::string const& name);
 
   /// @brief Returns a the string escaped and enclosed in quotes
   static std::string Quoted(std::string const& text);
 
+  static std::string QuotedCommand(std::vector<std::string> const& command);
+
+  /// @brief Returns the parent directory of the file with a "/" suffix
+  static std::string SubDirPrefix(std::string const& filename);
+
   /// @brief Appends the suffix to the filename before the last dot
   static std::string AppendFilenameSuffix(std::string const& filename,
                                           std::string const& suffix);
@@ -59,14 +69,21 @@ public:
                               std::vector<std::string> const& newOpts,
                               bool isQt5);
 
-  /// @brief Reads the resource files list from from a .qrc file
-  /// @arg fileName Must be the absolute path of the .qrc file
-  /// @return True if the rcc file was successfully read
-  static bool RccListInputs(std::string const& rccCommand,
-                            std::vector<std::string> const& rccListOptions,
-                            std::string const& fileName,
-                            std::vector<std::string>& files,
-                            std::string* errorMessage = nullptr);
+  /// @brief Parses the content of a qrc file
+  ///
+  /// Use when rcc does not support the "--list" option
+  static void RccListParseContent(std::string const& content,
+                                  std::vector<std::string>& files);
+
+  /// @brief Parses the output of the "rcc --list ..." command
+  static bool RccListParseOutput(std::string const& rccStdOut,
+                                 std::string const& rccStdErr,
+                                 std::vector<std::string>& files,
+                                 std::string& error);
+
+  /// @brief Converts relative qrc entry paths to full paths
+  static void RccListConvertFullPath(std::string const& qrcFileDir,
+                                     std::vector<std::string>& files);
 };
 
 #endif
diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx
index ee0ddbc..5b2b6d0 100644
--- a/Source/cmQtAutoGenerator.cxx
+++ b/Source/cmQtAutoGenerator.cxx
@@ -4,7 +4,6 @@
 #include "cmQtAutoGenerator.h"
 
 #include "cmsys/FStream.hxx"
-#include "cmsys/Terminal.h"
 
 #include "cmAlgorithms.h"
 #include "cmGlobalGenerator.h"
@@ -14,108 +13,55 @@
 #include "cmSystemTools.h"
 #include "cmake.h"
 
-// -- Static functions
-
-static std::string HeadLine(std::string const& title)
-{
-  std::string head = title;
-  head += '\n';
-  head.append(head.size() - 1, '-');
-  head += '\n';
-  return head;
-}
-
-static std::string QuotedCommand(std::vector<std::string> const& command)
-{
-  std::string res;
-  for (std::string const& item : command) {
-    if (!res.empty()) {
-      res.push_back(' ');
-    }
-    std::string const cesc = cmQtAutoGen::Quoted(item);
-    if (item.empty() || (cesc.size() > (item.size() + 2)) ||
-        (cesc.find(' ') != std::string::npos)) {
-      res += cesc;
-    } else {
-      res += item;
-    }
-  }
-  return res;
-}
+#include <algorithm>
 
 // -- Class methods
 
-cmQtAutoGenerator::cmQtAutoGenerator()
-  : Verbose(cmSystemTools::HasEnv("VERBOSE"))
-  , ColorOutput(true)
+void cmQtAutoGenerator::Logger::SetVerbose(bool value)
 {
-  {
-    std::string colorEnv;
-    cmSystemTools::GetEnv("COLOR", colorEnv);
-    if (!colorEnv.empty()) {
-      this->ColorOutput = cmSystemTools::IsOn(colorEnv.c_str());
-    }
-  }
+  Verbose_ = value;
 }
 
-bool cmQtAutoGenerator::Run(std::string const& infoFile,
-                            std::string const& config)
+void cmQtAutoGenerator::Logger::SetColorOutput(bool value)
 {
-  // Info settings
-  this->InfoFile = infoFile;
-  cmSystemTools::ConvertToUnixSlashes(this->InfoFile);
-  this->InfoDir = cmSystemTools::GetFilenamePath(infoFile);
-  this->InfoConfig = config;
-
-  cmake cm(cmake::RoleScript);
-  cm.SetHomeOutputDirectory(this->InfoDir);
-  cm.SetHomeDirectory(this->InfoDir);
-  cm.GetCurrentSnapshot().SetDefaultDefinitions();
-  cmGlobalGenerator gg(&cm);
-
-  cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
-  snapshot.GetDirectory().SetCurrentBinary(this->InfoDir);
-  snapshot.GetDirectory().SetCurrentSource(this->InfoDir);
-
-  auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
-  // The OLD/WARN behavior for policy CMP0053 caused a speed regression.
-  // https://gitlab.kitware.com/cmake/cmake/issues/17570
-  makefile->SetPolicyVersion("3.9");
-  gg.SetCurrentMakefile(makefile.get());
-
-  return this->Process(makefile.get());
+  ColorOutput_ = value;
 }
 
-void cmQtAutoGenerator::LogBold(std::string const& message) const
+std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title)
 {
-  cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundBlue |
-                                     cmsysTerminal_Color_ForegroundBold,
-                                   message.c_str(), true, this->ColorOutput);
+  std::string head = title;
+  head += '\n';
+  head.append(head.size() - 1, '-');
+  head += '\n';
+  return head;
 }
 
-void cmQtAutoGenerator::LogInfo(cmQtAutoGen::Generator genType,
-                                std::string const& message) const
+void cmQtAutoGenerator::Logger::Info(GeneratorT genType,
+                                     std::string const& message)
 {
-  std::string msg = cmQtAutoGen::GeneratorName(genType);
+  std::string msg = GeneratorName(genType);
   msg += ": ";
   msg += message;
   if (msg.back() != '\n') {
     msg.push_back('\n');
   }
-  cmSystemTools::Stdout(msg.c_str(), msg.size());
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    cmSystemTools::Stdout(msg.c_str(), msg.size());
+  }
 }
 
-void cmQtAutoGenerator::LogWarning(cmQtAutoGen::Generator genType,
-                                   std::string const& message) const
+void cmQtAutoGenerator::Logger::Warning(GeneratorT genType,
+                                        std::string const& message)
 {
-  std::string msg = cmQtAutoGen::GeneratorName(genType);
-  msg += " warning:";
+  std::string msg;
   if (message.find('\n') == std::string::npos) {
     // Single line message
-    msg.push_back(' ');
+    msg += GeneratorName(genType);
+    msg += " warning: ";
   } else {
     // Multi line message
-    msg.push_back('\n');
+    msg += HeadLine(GeneratorName(genType) + " warning");
   }
   // Message
   msg += message;
@@ -123,55 +69,60 @@ void cmQtAutoGenerator::LogWarning(cmQtAutoGen::Generator genType,
     msg.push_back('\n');
   }
   msg.push_back('\n');
-  cmSystemTools::Stdout(msg.c_str(), msg.size());
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    cmSystemTools::Stdout(msg.c_str(), msg.size());
+  }
 }
 
-void cmQtAutoGenerator::LogFileWarning(cmQtAutoGen::Generator genType,
-                                       std::string const& filename,
-                                       std::string const& message) const
+void cmQtAutoGenerator::Logger::WarningFile(GeneratorT genType,
+                                            std::string const& filename,
+                                            std::string const& message)
 {
   std::string msg = "  ";
-  msg += cmQtAutoGen::Quoted(filename);
+  msg += Quoted(filename);
   msg.push_back('\n');
   // Message
   msg += message;
-  this->LogWarning(genType, msg);
+  Warning(genType, msg);
 }
 
-void cmQtAutoGenerator::LogError(cmQtAutoGen::Generator genType,
-                                 std::string const& message) const
+void cmQtAutoGenerator::Logger::Error(GeneratorT genType,
+                                      std::string const& message)
 {
   std::string msg;
-  msg.push_back('\n');
-  msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " error");
+  msg += HeadLine(GeneratorName(genType) + " error");
   // Message
   msg += message;
   if (msg.back() != '\n') {
     msg.push_back('\n');
   }
   msg.push_back('\n');
-  cmSystemTools::Stderr(msg.c_str(), msg.size());
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    cmSystemTools::Stderr(msg.c_str(), msg.size());
+  }
 }
 
-void cmQtAutoGenerator::LogFileError(cmQtAutoGen::Generator genType,
-                                     std::string const& filename,
-                                     std::string const& message) const
+void cmQtAutoGenerator::Logger::ErrorFile(GeneratorT genType,
+                                          std::string const& filename,
+                                          std::string const& message)
 {
   std::string emsg = "  ";
-  emsg += cmQtAutoGen::Quoted(filename);
+  emsg += Quoted(filename);
   emsg += '\n';
   // Message
   emsg += message;
-  this->LogError(genType, emsg);
+  Error(genType, emsg);
 }
 
-void cmQtAutoGenerator::LogCommandError(
-  cmQtAutoGen::Generator genType, std::string const& message,
-  std::vector<std::string> const& command, std::string const& output) const
+void cmQtAutoGenerator::Logger::ErrorCommand(
+  GeneratorT genType, std::string const& message,
+  std::vector<std::string> const& command, std::string const& output)
 {
   std::string msg;
   msg.push_back('\n');
-  msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " subprocess error");
+  msg += HeadLine(GeneratorName(genType) + " subprocess error");
   msg += message;
   if (msg.back() != '\n') {
     msg.push_back('\n');
@@ -189,135 +140,495 @@ void cmQtAutoGenerator::LogCommandError(
     msg.push_back('\n');
   }
   msg.push_back('\n');
-  cmSystemTools::Stderr(msg.c_str(), msg.size());
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    cmSystemTools::Stderr(msg.c_str(), msg.size());
+  }
 }
 
-/**
- * @brief Generates the parent directory of the given file on demand
- * @return True on success
- */
-bool cmQtAutoGenerator::MakeParentDirectory(cmQtAutoGen::Generator genType,
-                                            std::string const& filename) const
+std::string cmQtAutoGenerator::FileSystem::RealPath(
+  std::string const& filename)
 {
-  bool success = true;
-  std::string const dirName = cmSystemTools::GetFilenamePath(filename);
-  if (!dirName.empty()) {
-    if (!cmSystemTools::MakeDirectory(dirName)) {
-      this->LogFileError(genType, filename,
-                         "Could not create parent directory");
-      success = false;
-    }
-  }
-  return success;
+  std::lock_guard<std::mutex> lock(Mutex_);
+  return cmSystemTools::GetRealPath(filename);
+}
+
+bool cmQtAutoGenerator::FileSystem::FileExists(std::string const& filename)
+{
+  std::lock_guard<std::mutex> lock(Mutex_);
+  return cmSystemTools::FileExists(filename);
 }
 
-/**
- * @brief Tests if buildFile is older than sourceFile
- * @return True if buildFile  is older than sourceFile.
- *         False may indicate an error.
- */
-bool cmQtAutoGenerator::FileIsOlderThan(std::string const& buildFile,
-                                        std::string const& sourceFile,
-                                        std::string* error)
+bool cmQtAutoGenerator::FileSystem::FileIsOlderThan(
+  std::string const& buildFile, std::string const& sourceFile,
+  std::string* error)
 {
+  bool res(false);
   int result = 0;
-  if (cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result)) {
-    return (result < 0);
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    res = cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result);
   }
-  if (error != nullptr) {
-    error->append(
-      "File modification time comparison failed for the files\n  ");
-    error->append(cmQtAutoGen::Quoted(buildFile));
-    error->append("\nand\n  ");
-    error->append(cmQtAutoGen::Quoted(sourceFile));
+  if (res) {
+    res = (result < 0);
+  } else {
+    if (error != nullptr) {
+      error->append(
+        "File modification time comparison failed for the files\n  ");
+      error->append(Quoted(buildFile));
+      error->append("\nand\n  ");
+      error->append(Quoted(sourceFile));
+    }
   }
-  return false;
+  return res;
 }
 
-bool cmQtAutoGenerator::FileRead(std::string& content,
-                                 std::string const& filename,
-                                 std::string* error)
+bool cmQtAutoGenerator::FileSystem::FileRead(std::string& content,
+                                             std::string const& filename,
+                                             std::string* error)
 {
   bool success = false;
-  if (cmSystemTools::FileExists(filename)) {
-    std::size_t const length = cmSystemTools::FileLength(filename);
-    cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
-    if (ifs) {
-      content.resize(length);
-      ifs.read(&content.front(), content.size());
+  {
+    std::lock_guard<std::mutex> lock(Mutex_);
+    if (cmSystemTools::FileExists(filename)) {
+      std::size_t const length = cmSystemTools::FileLength(filename);
+      cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
       if (ifs) {
-        success = true;
-      } else {
-        content.clear();
-        if (error != nullptr) {
-          error->append("Reading from the file failed.");
+        content.resize(length);
+        ifs.read(&content.front(), content.size());
+        if (ifs) {
+          success = true;
+        } else {
+          content.clear();
+          if (error != nullptr) {
+            error->append("Reading from the file failed.");
+          }
         }
+      } else if (error != nullptr) {
+        error->append("Opening the file for reading failed.");
       }
     } else if (error != nullptr) {
-      error->append("Opening the file for reading failed.");
+      error->append("The file does not exist.");
     }
-  } else if (error != nullptr) {
-    error->append("The file does not exist.");
   }
   return success;
 }
 
-bool cmQtAutoGenerator::FileWrite(cmQtAutoGen::Generator genType,
-                                  std::string const& filename,
-                                  std::string const& content)
+bool cmQtAutoGenerator::FileSystem::FileRead(GeneratorT genType,
+                                             std::string& content,
+                                             std::string const& filename)
 {
   std::string error;
+  if (!FileRead(content, filename, &error)) {
+    Log()->ErrorFile(genType, filename, error);
+    return false;
+  }
+  return true;
+}
+
+bool cmQtAutoGenerator::FileSystem::FileWrite(std::string const& filename,
+                                              std::string const& content,
+                                              std::string* error)
+{
+  bool success = false;
   // Make sure the parent directory exists
-  if (this->MakeParentDirectory(genType, filename)) {
+  if (MakeParentDirectory(filename)) {
+    std::lock_guard<std::mutex> lock(Mutex_);
     cmsys::ofstream outfile;
     outfile.open(filename.c_str(),
                  (std::ios::out | std::ios::binary | std::ios::trunc));
     if (outfile) {
       outfile << content;
       // Check for write errors
-      if (!outfile.good()) {
-        error = "File writing failed";
+      if (outfile.good()) {
+        success = true;
+      } else {
+        if (error != nullptr) {
+          error->assign("File writing failed");
+        }
       }
     } else {
-      error = "Opening file for writing failed";
+      if (error != nullptr) {
+        error->assign("Opening file for writing failed");
+      }
+    }
+  } else {
+    if (error != nullptr) {
+      error->assign("Could not create parent directory");
     }
   }
-  if (!error.empty()) {
-    this->LogFileError(genType, filename, error);
+  return success;
+}
+
+bool cmQtAutoGenerator::FileSystem::FileWrite(GeneratorT genType,
+                                              std::string const& filename,
+                                              std::string const& content)
+{
+  std::string error;
+  if (!FileWrite(filename, content, &error)) {
+    Log()->ErrorFile(genType, filename, error);
     return false;
   }
   return true;
 }
 
-bool cmQtAutoGenerator::FileDiffers(std::string const& filename,
-                                    std::string const& content)
+bool cmQtAutoGenerator::FileSystem::FileDiffers(std::string const& filename,
+                                                std::string const& content)
 {
   bool differs = true;
   {
     std::string oldContents;
-    if (this->FileRead(oldContents, filename)) {
+    if (FileRead(oldContents, filename)) {
       differs = (oldContents != content);
     }
   }
   return differs;
 }
 
-/**
- * @brief Runs a command and returns true on success
- * @return True on success
- */
-bool cmQtAutoGenerator::RunCommand(std::vector<std::string> const& command,
-                                   std::string& output) const
-{
-  // Log command
-  if (this->Verbose) {
-    std::string qcmd = QuotedCommand(command);
-    qcmd.push_back('\n');
-    cmSystemTools::Stdout(qcmd.c_str(), qcmd.size());
-  }
-  // Execute command
-  int retVal = 0;
-  bool res = cmSystemTools::RunSingleCommand(
-    command, &output, &output, &retVal, nullptr, cmSystemTools::OUTPUT_NONE);
-  return (res && (retVal == 0));
+bool cmQtAutoGenerator::FileSystem::FileRemove(std::string const& filename)
+{
+  std::lock_guard<std::mutex> lock(Mutex_);
+  return cmSystemTools::RemoveFile(filename);
+}
+
+bool cmQtAutoGenerator::FileSystem::Touch(std::string const& filename)
+{
+  std::lock_guard<std::mutex> lock(Mutex_);
+  return cmSystemTools::Touch(filename, false);
+}
+
+bool cmQtAutoGenerator::FileSystem::MakeDirectory(std::string const& dirname)
+{
+  std::lock_guard<std::mutex> lock(Mutex_);
+  return cmSystemTools::MakeDirectory(dirname);
+}
+
+bool cmQtAutoGenerator::FileSystem::MakeDirectory(GeneratorT genType,
+                                                  std::string const& dirname)
+{
+  if (!MakeDirectory(dirname)) {
+    Log()->ErrorFile(genType, dirname, "Could not create directory");
+    return false;
+  }
+  return true;
+}
+
+bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
+  std::string const& filename)
+{
+  bool success = true;
+  std::string const dirName = cmSystemTools::GetFilenamePath(filename);
+  if (!dirName.empty()) {
+    success = MakeDirectory(dirName);
+  }
+  return success;
+}
+
+bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
+  GeneratorT genType, std::string const& filename)
+{
+  if (!MakeParentDirectory(filename)) {
+    Log()->ErrorFile(genType, filename, "Could not create parent directory");
+    return false;
+  }
+  return true;
+}
+
+int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop,
+                                                     ReadOnlyProcessT* process)
+{
+  Process_ = process;
+  Target_ = nullptr;
+  return UVPipe_.init(*uv_loop, 0, this);
+}
+
+int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target)
+{
+  Target_ = target;
+  return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData);
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset()
+{
+  Process_ = nullptr;
+  Target_ = nullptr;
+  UVPipe_.reset();
+  Buffer_.clear();
+  Buffer_.shrink_to_fit();
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle,
+                                                         size_t suggestedSize,
+                                                         uv_buf_t* buf)
+{
+  auto& pipe = *reinterpret_cast<PipeT*>(handle->data);
+  pipe.Buffer_.resize(suggestedSize);
+  buf->base = &pipe.Buffer_.front();
+  buf->len = pipe.Buffer_.size();
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream,
+                                                        ssize_t nread,
+                                                        const uv_buf_t* buf)
+{
+  auto& pipe = *reinterpret_cast<PipeT*>(stream->data);
+  if (nread > 0) {
+    // Append data to merged output
+    if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) {
+      pipe.Target_->append(buf->base, nread);
+    }
+  } else if (nread < 0) {
+    // EOF or error
+    auto* proc = pipe.Process_;
+    // Check it this an unusual error
+    if (nread != UV_EOF) {
+      if (!proc->Result()->error()) {
+        proc->Result()->ErrorMessage =
+          "libuv reading from pipe failed with error code ";
+        proc->Result()->ErrorMessage += std::to_string(nread);
+      }
+    }
+    // Clear libuv pipe handle and try to finish
+    pipe.reset();
+    proc->UVTryFinish();
+  }
+}
+
+void cmQtAutoGenerator::ProcessResultT::reset()
+{
+  ExitStatus = 0;
+  TermSignal = 0;
+  if (!StdOut.empty()) {
+    StdOut.clear();
+    StdOut.shrink_to_fit();
+  }
+  if (!StdErr.empty()) {
+    StdErr.clear();
+    StdErr.shrink_to_fit();
+  }
+  if (!ErrorMessage.empty()) {
+    ErrorMessage.clear();
+    ErrorMessage.shrink_to_fit();
+  }
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::setup(
+  ProcessResultT* result, bool mergedOutput,
+  std::vector<std::string> const& command, std::string const& workingDirectory)
+{
+  Setup_.WorkingDirectory = workingDirectory;
+  Setup_.Command = command;
+  Setup_.Result = result;
+  Setup_.MergedOutput = mergedOutput;
+}
+
+bool cmQtAutoGenerator::ReadOnlyProcessT::start(
+  uv_loop_t* uv_loop, std::function<void()>&& finishedCallback)
+{
+  if (IsStarted() || (Result() == nullptr)) {
+    return false;
+  }
+
+  // Reset result before the start
+  Result()->reset();
+
+  // Fill command string pointers
+  if (!Setup().Command.empty()) {
+    CommandPtr_.reserve(Setup().Command.size() + 1);
+    for (std::string const& arg : Setup().Command) {
+      CommandPtr_.push_back(arg.c_str());
+    }
+    CommandPtr_.push_back(nullptr);
+  } else {
+    Result()->ErrorMessage = "Empty command";
+  }
+
+  if (!Result()->error()) {
+    if (UVPipeOut_.init(uv_loop, this) != 0) {
+      Result()->ErrorMessage = "libuv stdout pipe initialization failed";
+    }
+  }
+  if (!Result()->error()) {
+    if (UVPipeErr_.init(uv_loop, this) != 0) {
+      Result()->ErrorMessage = "libuv stderr pipe initialization failed";
+    }
+  }
+  if (!Result()->error()) {
+    // -- Setup process stdio options
+    // stdin
+    UVOptionsStdIO_[0].flags = UV_IGNORE;
+    UVOptionsStdIO_[0].data.stream = nullptr;
+    // stdout
+    UVOptionsStdIO_[1].flags =
+      static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+    UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
+    // stderr
+    UVOptionsStdIO_[2].flags =
+      static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+    UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
+
+    // -- Setup process options
+    std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
+    UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit;
+    UVOptions_.file = CommandPtr_[0];
+    UVOptions_.args = const_cast<char**>(&CommandPtr_.front());
+    UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
+    UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
+    UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
+    UVOptions_.stdio = &UVOptionsStdIO_.front();
+
+    // -- Spawn process
+    if (UVProcess_.spawn(*uv_loop, UVOptions_, this) != 0) {
+      Result()->ErrorMessage = "libuv process spawn failed";
+    }
+  }
+  // -- Start reading from stdio streams
+  if (!Result()->error()) {
+    if (UVPipeOut_.startRead(&Result()->StdOut) != 0) {
+      Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
+    }
+  }
+  if (!Result()->error()) {
+    if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut
+                                                 : &Result()->StdErr) != 0) {
+      Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
+    }
+  }
+
+  if (!Result()->error()) {
+    IsStarted_ = true;
+    FinishedCallback_ = std::move(finishedCallback);
+  } else {
+    // Clear libuv handles and finish
+    UVProcess_.reset();
+    UVPipeOut_.reset();
+    UVPipeErr_.reset();
+    CommandPtr_.clear();
+  }
+
+  return IsStarted();
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle,
+                                                 int64_t exitStatus,
+                                                 int termSignal)
+{
+  auto& proc = *reinterpret_cast<ReadOnlyProcessT*>(handle->data);
+  if (proc.IsStarted() && !proc.IsFinished()) {
+    // Set error message on demand
+    proc.Result()->ExitStatus = exitStatus;
+    proc.Result()->TermSignal = termSignal;
+    if (!proc.Result()->error()) {
+      if (termSignal != 0) {
+        proc.Result()->ErrorMessage = "Process was terminated by signal ";
+        proc.Result()->ErrorMessage +=
+          std::to_string(proc.Result()->TermSignal);
+      } else if (exitStatus != 0) {
+        proc.Result()->ErrorMessage = "Process failed with return value ";
+        proc.Result()->ErrorMessage +=
+          std::to_string(proc.Result()->ExitStatus);
+      }
+    }
+
+    // Reset process handle and try to finish
+    proc.UVProcess_.reset();
+    proc.UVTryFinish();
+  }
+}
+
+void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish()
+{
+  // There still might be data in the pipes after the process has finished.
+  // Therefore check if the process is finished AND all pipes are closed before
+  // signaling the worker thread to continue.
+  if (UVProcess_.get() == nullptr) {
+    if (UVPipeOut_.uv_pipe() == nullptr) {
+      if (UVPipeErr_.uv_pipe() == nullptr) {
+        IsFinished_ = true;
+        FinishedCallback_();
+      }
+    }
+  }
+}
+
+cmQtAutoGenerator::cmQtAutoGenerator()
+  : FileSys_(&Logger_)
+{
+  // Initialize logger
+  Logger_.SetVerbose(cmSystemTools::HasEnv("VERBOSE"));
+  {
+    std::string colorEnv;
+    cmSystemTools::GetEnv("COLOR", colorEnv);
+    if (!colorEnv.empty()) {
+      Logger_.SetColorOutput(cmSystemTools::IsOn(colorEnv.c_str()));
+    } else {
+      Logger_.SetColorOutput(true);
+    }
+  }
+
+  // Initialize libuv loop
+  uv_disable_stdio_inheritance();
+#ifdef CMAKE_UV_SIGNAL_HACK
+  UVHackRAII_ = cm::make_unique<cmUVSignalHackRAII>();
+#endif
+  UVLoop_ = cm::make_unique<uv_loop_t>();
+  uv_loop_init(UVLoop());
+}
+
+cmQtAutoGenerator::~cmQtAutoGenerator()
+{
+  // Close libuv loop
+  uv_loop_close(UVLoop());
+}
+
+bool cmQtAutoGenerator::Run(std::string const& infoFile,
+                            std::string const& config)
+{
+  // Info settings
+  InfoFile_ = infoFile;
+  cmSystemTools::ConvertToUnixSlashes(InfoFile_);
+  InfoDir_ = cmSystemTools::GetFilenamePath(infoFile);
+  InfoConfig_ = config;
+
+  bool success = false;
+  {
+    cmake cm(cmake::RoleScript);
+    cm.SetHomeOutputDirectory(InfoDir());
+    cm.SetHomeDirectory(InfoDir());
+    cm.GetCurrentSnapshot().SetDefaultDefinitions();
+    cmGlobalGenerator gg(&cm);
+
+    cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
+    snapshot.GetDirectory().SetCurrentBinary(InfoDir());
+    snapshot.GetDirectory().SetCurrentSource(InfoDir());
+
+    auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
+    // The OLD/WARN behavior for policy CMP0053 caused a speed regression.
+    // https://gitlab.kitware.com/cmake/cmake/issues/17570
+    makefile->SetPolicyVersion("3.9");
+    gg.SetCurrentMakefile(makefile.get());
+    success = this->Init(makefile.get());
+  }
+  if (success) {
+    success = this->Process();
+  }
+  return success;
+}
+
+std::string cmQtAutoGenerator::SettingsFind(std::string const& content,
+                                            const char* key)
+{
+  std::string prefix(key);
+  prefix += ':';
+  std::string::size_type pos = content.find(prefix);
+  if (pos != std::string::npos) {
+    pos += prefix.size();
+    if (pos < content.size()) {
+      std::string::size_type posE = content.find('\n', pos);
+      if ((posE != std::string::npos) && (posE != pos)) {
+        return content.substr(pos, posE - pos);
+      }
+    }
+  }
+  return std::string();
 }
diff --git a/Source/cmQtAutoGenerator.h b/Source/cmQtAutoGenerator.h
index 285340d..6b35234 100644
--- a/Source/cmQtAutoGenerator.h
+++ b/Source/cmQtAutoGenerator.h
@@ -6,71 +6,283 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include "cmQtAutoGen.h"
+#include "cmUVHandlePtr.h"
+#include "cm_uv.h"
 
+#include <array>
+#include <functional>
+#include <mutex>
+#include <stddef.h>
+#include <stdint.h>
 #include <string>
 #include <vector>
 
 class cmMakefile;
 
-class cmQtAutoGenerator
+/// @brief Base class for QtAutoGen gernerators
+class cmQtAutoGenerator : public cmQtAutoGen
 {
   CM_DISABLE_COPY(cmQtAutoGenerator)
 public:
+  // -- Types
+
+  /// @brief Thread safe logging
+  class Logger
+  {
+  public:
+    // -- Verbosity
+    bool Verbose() const { return this->Verbose_; }
+    void SetVerbose(bool value);
+    bool ColorOutput() const { return this->ColorOutput_; }
+    void SetColorOutput(bool value);
+    // -- Log info
+    void Info(GeneratorT genType, std::string const& message);
+    // -- Log warning
+    void Warning(GeneratorT genType, std::string const& message);
+    void WarningFile(GeneratorT genType, std::string const& filename,
+                     std::string const& message);
+    // -- Log error
+    void Error(GeneratorT genType, std::string const& message);
+    void ErrorFile(GeneratorT genType, std::string const& filename,
+                   std::string const& message);
+    void ErrorCommand(GeneratorT genType, std::string const& message,
+                      std::vector<std::string> const& command,
+                      std::string const& output);
+
+  private:
+    static std::string HeadLine(std::string const& title);
+
+  private:
+    std::mutex Mutex_;
+    bool volatile Verbose_ = false;
+    bool volatile ColorOutput_ = false;
+  };
+
+  /// @brief Thread safe file system interface
+  class FileSystem
+  {
+  public:
+    FileSystem(Logger* log)
+      : Log_(log)
+    {
+    }
+
+    Logger* Log() const { return Log_; }
+    std::string RealPath(std::string const& filename);
+    bool FileExists(std::string const& filename);
+    bool FileIsOlderThan(std::string const& buildFile,
+                         std::string const& sourceFile,
+                         std::string* error = nullptr);
+
+    bool FileRead(std::string& content, std::string const& filename,
+                  std::string* error = nullptr);
+    /// @brief Error logging version
+    bool FileRead(GeneratorT genType, std::string& content,
+                  std::string const& filename);
+
+    bool FileWrite(std::string const& filename, std::string const& content,
+                   std::string* error = nullptr);
+    /// @brief Error logging version
+    bool FileWrite(GeneratorT genType, std::string const& filename,
+                   std::string const& content);
+
+    bool FileDiffers(std::string const& filename, std::string const& content);
+
+    bool FileRemove(std::string const& filename);
+    bool Touch(std::string const& filename);
+
+    bool MakeDirectory(std::string const& dirname);
+    /// @brief Error logging version
+    bool MakeDirectory(GeneratorT genType, std::string const& dirname);
+
+    bool MakeParentDirectory(std::string const& filename);
+    /// @brief Error logging version
+    bool MakeParentDirectory(GeneratorT genType, std::string const& filename);
+
+  private:
+    std::mutex Mutex_;
+    Logger* Log_;
+  };
+
+  /// @brief Return value and output of an external process
+  struct ProcessResultT
+  {
+    void reset();
+    bool error() const
+    {
+      return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
+    }
+
+    std::int64_t ExitStatus = 0;
+    int TermSignal = 0;
+    std::string StdOut;
+    std::string StdErr;
+    std::string ErrorMessage;
+  };
+
+  /// @brief External process management class
+  struct ReadOnlyProcessT
+  {
+    // -- Types
+
+    /// @brief libuv pipe buffer class
+    class PipeT
+    {
+    public:
+      int init(uv_loop_t* uv_loop, ReadOnlyProcessT* process);
+      int startRead(std::string* target);
+      void reset();
+
+      // -- Libuv casts
+      uv_pipe_t* uv_pipe() { return UVPipe_.get(); }
+      uv_stream_t* uv_stream()
+      {
+        return reinterpret_cast<uv_stream_t*>(uv_pipe());
+      }
+      uv_handle_t* uv_handle()
+      {
+        return reinterpret_cast<uv_handle_t*>(uv_pipe());
+      }
+
+      // -- Libuv callbacks
+      static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
+                          uv_buf_t* buf);
+      static void UVData(uv_stream_t* stream, ssize_t nread,
+                         const uv_buf_t* buf);
+
+    private:
+      ReadOnlyProcessT* Process_ = nullptr;
+      std::string* Target_ = nullptr;
+      std::vector<char> Buffer_;
+      cm::uv_pipe_ptr UVPipe_;
+    };
+
+    /// @brief Process settings
+    struct SetupT
+    {
+      std::string WorkingDirectory;
+      std::vector<std::string> Command;
+      ProcessResultT* Result = nullptr;
+      bool MergedOutput = false;
+    };
+
+    // -- Constructor
+    ReadOnlyProcessT() = default;
+
+    // -- Const accessors
+    const SetupT& Setup() const { return Setup_; }
+    ProcessResultT* Result() const { return Setup_.Result; }
+    bool IsStarted() const { return IsStarted_; }
+    bool IsFinished() const { return IsFinished_; }
+
+    // -- Runtime
+    void setup(ProcessResultT* result, bool mergedOutput,
+               std::vector<std::string> const& command,
+               std::string const& workingDirectory = std::string());
+    bool start(uv_loop_t* uv_loop, std::function<void()>&& finishedCallback);
+
+  private:
+    // -- Friends
+    friend class PipeT;
+    // -- Libuv callbacks
+    static void UVExit(uv_process_t* handle, int64_t exitStatus,
+                       int termSignal);
+    void UVTryFinish();
+
+    // -- Setup
+    SetupT Setup_;
+    // -- Runtime
+    bool IsStarted_ = false;
+    bool IsFinished_ = false;
+    std::function<void()> FinishedCallback_;
+    std::vector<const char*> CommandPtr_;
+    std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
+    uv_process_options_t UVOptions_;
+    cm::uv_process_ptr UVProcess_;
+    PipeT UVPipeOut_;
+    PipeT UVPipeErr_;
+  };
+
+#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) &&                    \
+  UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19
+#define CMAKE_UV_SIGNAL_HACK
+  /*
+     libuv does not use SA_RESTART on its signal handler, but C++ streams
+     depend on it for reliable i/o operations.  This RAII helper convinces
+     libuv to install its handler, and then revises the handler to add the
+     SA_RESTART flag.  We use a distinct uv loop that never runs to avoid
+     ever really getting a callback.  libuv may fill the hack loop's signal
+     pipe and then stop writing, but that won't break any real loops.
+   */
+  class cmUVSignalHackRAII
+  {
+    uv_loop_t HackLoop;
+    cm::uv_signal_ptr HackSignal;
+    static void HackCB(uv_signal_t*, int) {}
+  public:
+    cmUVSignalHackRAII()
+    {
+      uv_loop_init(&this->HackLoop);
+      this->HackSignal.init(this->HackLoop);
+      this->HackSignal.start(HackCB, SIGCHLD);
+      struct sigaction hack_sa;
+      sigaction(SIGCHLD, NULL, &hack_sa);
+      if (!(hack_sa.sa_flags & SA_RESTART)) {
+        hack_sa.sa_flags |= SA_RESTART;
+        sigaction(SIGCHLD, &hack_sa, NULL);
+      }
+    }
+    ~cmUVSignalHackRAII()
+    {
+      this->HackSignal.stop();
+      uv_loop_close(&this->HackLoop);
+    }
+  };
+#endif
+
+public:
+  // -- Constructors
   cmQtAutoGenerator();
-  virtual ~cmQtAutoGenerator() = default;
+  virtual ~cmQtAutoGenerator();
+
+  // -- Run
   bool Run(std::string const& infoFile, std::string const& config);
 
-  std::string const& GetInfoFile() const { return InfoFile; }
-  std::string const& GetInfoDir() const { return InfoDir; }
-  std::string const& GetInfoConfig() const { return InfoConfig; }
-  bool GetVerbose() const { return Verbose; }
+  // -- Accessors
+  // Logging
+  Logger& Log() { return Logger_; }
+  // File System
+  FileSystem& FileSys() { return FileSys_; }
+  // InfoFile
+  std::string const& InfoFile() const { return InfoFile_; }
+  std::string const& InfoDir() const { return InfoDir_; }
+  std::string const& InfoConfig() const { return InfoConfig_; }
+  // libuv loop
+  uv_loop_t* UVLoop() { return UVLoop_.get(); }
+  cm::uv_async_ptr& UVRequest() { return UVRequest_; }
 
-protected:
-  // -- Central processing
-  virtual bool Process(cmMakefile* makefile) = 0;
-
-  // -- Log info
-  void LogBold(std::string const& message) const;
-  void LogInfo(cmQtAutoGen::Generator genType,
-               std::string const& message) const;
-  // -- Log warning
-  void LogWarning(cmQtAutoGen::Generator genType,
-                  std::string const& message) const;
-  void LogFileWarning(cmQtAutoGen::Generator genType,
-                      std::string const& filename,
-                      std::string const& message) const;
-  // -- Log error
-  void LogError(cmQtAutoGen::Generator genType,
-                std::string const& message) const;
-  void LogFileError(cmQtAutoGen::Generator genType,
-                    std::string const& filename,
-                    std::string const& message) const;
-  void LogCommandError(cmQtAutoGen::Generator genType,
-                       std::string const& message,
-                       std::vector<std::string> const& command,
-                       std::string const& output) const;
   // -- Utility
-  bool MakeParentDirectory(cmQtAutoGen::Generator genType,
-                           std::string const& filename) const;
-  bool FileIsOlderThan(std::string const& buildFile,
-                       std::string const& sourceFile,
-                       std::string* error = nullptr);
-  bool FileRead(std::string& content, std::string const& filename,
-                std::string* error = nullptr);
-  bool FileWrite(cmQtAutoGen::Generator genType, std::string const& filename,
-                 std::string const& content);
-  bool FileDiffers(std::string const& filename, std::string const& content);
-  bool RunCommand(std::vector<std::string> const& command,
-                  std::string& output) const;
+  static std::string SettingsFind(std::string const& content, const char* key);
+
+protected:
+  // -- Abstract processing interface
+  virtual bool Init(cmMakefile* makefile) = 0;
+  virtual bool Process() = 0;
 
 private:
+  // -- Logging
+  Logger Logger_;
+  FileSystem FileSys_;
   // -- Info settings
-  std::string InfoFile;
-  std::string InfoDir;
-  std::string InfoConfig;
-  // -- Settings
-  bool Verbose;
-  bool ColorOutput;
+  std::string InfoFile_;
+  std::string InfoDir_;
+  std::string InfoConfig_;
+// -- libuv loop
+#ifdef CMAKE_UV_SIGNAL_HACK
+  std::unique_ptr<cmUVSignalHackRAII> UVHackRAII_;
+#endif
+  std::unique_ptr<uv_loop_t> UVLoop_;
+  cm::uv_async_ptr UVRequest_;
 };
 
 #endif
diff --git a/Source/cmQtAutoGeneratorInitializer.cxx b/Source/cmQtAutoGeneratorInitializer.cxx
index de0ba4f..29f3653 100644
--- a/Source/cmQtAutoGeneratorInitializer.cxx
+++ b/Source/cmQtAutoGeneratorInitializer.cxx
@@ -24,6 +24,7 @@
 #include "cm_sys_stat.h"
 #include "cmake.h"
 #include "cmsys/FStream.hxx"
+#include "cmsys/SystemInformation.hxx"
 
 #include <algorithm>
 #include <array>
@@ -52,6 +53,20 @@ inline static std::string GetSafeProperty(cmSourceFile const* sf,
   return std::string(SafeString(sf->GetProperty(key)));
 }
 
+static std::size_t GetParallelCPUCount()
+{
+  static std::size_t count = 0;
+  // Detect only on the first call
+  if (count == 0) {
+    cmsys::SystemInformation info;
+    info.RunCPUCheck();
+    count = info.GetNumberOfPhysicalCPU();
+    count = std::max<std::size_t>(count, 1);
+    count = std::min<std::size_t>(count, cmQtAutoGen::ParallelMax);
+  }
+  return count;
+}
+
 static void AddDefinitionEscaped(cmMakefile* makefile, const char* key,
                                  std::string const& value)
 {
@@ -85,12 +100,12 @@ static void AddDefinitionEscaped(
     seplist.push_back(std::move(blist));
   }
   makefile->AddDefinition(key, cmOutputConverter::EscapeForCMake(
-                                 cmJoin(seplist, cmQtAutoGen::listSep))
+                                 cmJoin(seplist, cmQtAutoGen::ListSep))
                                  .c_str());
 }
 
 static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
-                             cmQtAutoGen::Generator genType)
+                             cmQtAutoGen::GeneratorT genType)
 {
   cmSourceGroup* sourceGroup = nullptr;
   // Acquire source group
@@ -101,10 +116,10 @@ static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
       std::array<std::string, 2> props;
       // Use generator specific group name
       switch (genType) {
-        case cmQtAutoGen::MOC:
+        case cmQtAutoGen::GeneratorT::MOC:
           props[0] = "AUTOMOC_SOURCE_GROUP";
           break;
-        case cmQtAutoGen::RCC:
+        case cmQtAutoGen::GeneratorT::RCC:
           props[0] = "AUTORCC_SOURCE_GROUP";
           break;
         default:
@@ -219,7 +234,7 @@ cmQtAutoGeneratorInitializer::cmQtAutoGeneratorInitializer(
   , UicEnabled(uicEnabled)
   , RccEnabled(rccEnabled)
   , QtVersionMajor(qtVersionMajor)
-  , MultiConfig(cmQtAutoGen::WRAP)
+  , MultiConfig(MultiConfigT::WRAPPER)
 {
   this->QtVersionMinor = cmQtAutoGeneratorInitializer::GetQtMinorVersion(
     target, this->QtVersionMajor);
@@ -240,19 +255,19 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
   // Multi configuration
   {
     if (!globalGen->IsMultiConfig()) {
-      this->MultiConfig = cmQtAutoGen::SINGLE;
+      this->MultiConfig = MultiConfigT::SINGLE;
     }
 
     // FIXME: Xcode does not support per-config sources, yet.
     //        (EXCLUDED_SOURCE_FILE_NAMES)
     // if (globalGen->GetName().find("Xcode") != std::string::npos) {
-    //  return cmQtAutoGen::FULL;
+    //  return MultiConfigT::MULTI;
     //}
 
     // FIXME: Visual Studio does not support per-config sources, yet.
     //        (EXCLUDED_SOURCE_FILE_NAMES)
     // if (globalGen->GetName().find("Visual Studio") != std::string::npos) {
-    //  return cmQtAutoGen::FULL;
+    //  return MultiConfigT::MULTI;
     //}
   }
 
@@ -294,7 +309,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
     this->AutogenInfoFile += "/AutogenInfo.cmake";
 
     this->AutogenSettingsFile = this->DirInfo;
-    this->AutogenSettingsFile += "/AutogenOldSettings.cmake";
+    this->AutogenSettingsFile += "/AutogenOldSettings.txt";
   }
 
   // Autogen target FOLDER property
@@ -324,7 +339,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
   {
     std::string base = this->DirInfo;
     base += "/AutogenOldSettings";
-    if (this->MultiConfig == cmQtAutoGen::SINGLE) {
+    if (this->MultiConfig == MultiConfigT::SINGLE) {
       AddCleanFile(makefile, base.append(".cmake"));
     } else {
       for (std::string const& cfg : this->ConfigsList) {
@@ -340,7 +355,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
   // Add moc compilation to generated files list
   if (this->MocEnabled) {
     std::string const mocsComp = this->DirBuild + "/mocs_compilation.cpp";
-    auto files = this->AddGeneratedSource(mocsComp, cmQtAutoGen::MOC);
+    auto files = this->AddGeneratedSource(mocsComp, GeneratorT::MOC);
     for (std::string& file : files) {
       autogenProvides.push_back(std::move(file));
     }
@@ -349,7 +364,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
   // Add autogen includes directory to the origin target INCLUDE_DIRECTORIES
   if (this->MocEnabled || this->UicEnabled) {
     std::string includeDir = this->DirBuild + "/include";
-    if (this->MultiConfig != cmQtAutoGen::SINGLE) {
+    if (this->MultiConfig != MultiConfigT::SINGLE) {
       includeDir += "_$<CONFIG>";
     }
     this->Target->AddIncludeDirectory(includeDir, true);
@@ -560,10 +575,10 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
         msg += "For compatibility, CMake is excluding the GENERATED source "
                "file(s):\n";
         for (const std::string& absFile : generatedHeaders) {
-          msg.append("  ").append(cmQtAutoGen::Quoted(absFile)).append("\n");
+          msg.append("  ").append(Quoted(absFile)).append("\n");
         }
         for (const std::string& absFile : generatedSources) {
-          msg.append("  ").append(cmQtAutoGen::Quoted(absFile)).append("\n");
+          msg.append("  ").append(Quoted(absFile)).append("\n");
         }
         msg += "from processing by ";
         msg += tools;
@@ -630,7 +645,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
           qrc.InfoFile = base;
           qrc.InfoFile += "Info.cmake";
           qrc.SettingsFile = base;
-          qrc.SettingsFile += "Settings.cmake";
+          qrc.SettingsFile += "Settings.txt";
         }
       }
     }
@@ -650,16 +665,16 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
         std::vector<std::string> nameOpts;
         nameOpts.emplace_back("-name");
         nameOpts.emplace_back(std::move(name));
-        cmQtAutoGen::RccMergeOptions(opts, nameOpts, QtV5);
+        RccMergeOptions(opts, nameOpts, QtV5);
       }
       // Merge file option
-      cmQtAutoGen::RccMergeOptions(opts, qrc.Options, QtV5);
+      RccMergeOptions(opts, qrc.Options, QtV5);
       qrc.Options = std::move(opts);
     }
     for (Qrc& qrc : this->Qrcs) {
       // Register file at target
       std::vector<std::string> const ccOutput =
-        this->AddGeneratedSource(qrc.RccFile, cmQtAutoGen::RCC);
+        this->AddGeneratedSource(qrc.RccFile, GeneratorT::RCC);
 
       cmCustomCommandLines commandLines;
       {
@@ -686,8 +701,9 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
             ccName += qrc.PathChecksum;
           }
           std::vector<std::string> ccDepends;
-          // Add the .qrc file to the custom target dependencies
+          // Add the .qrc and info file to the custom target dependencies
           ccDepends.push_back(qrc.QrcFile);
+          ccDepends.push_back(qrc.InfoFile);
 
           cmTarget* autoRccTarget = makefile->AddUtilityCommand(
             ccName, cmMakefile::TargetOrigin::Generator, true,
@@ -709,15 +725,14 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
         {
           std::vector<std::string> ccByproducts;
           std::vector<std::string> ccDepends;
-          // Add the .qrc file to the custom command dependencies
+          // Add the .qrc and info file to the custom command dependencies
           ccDepends.push_back(qrc.QrcFile);
+          ccDepends.push_back(qrc.InfoFile);
 
           // Add the resource files to the dependencies
           {
             std::string error;
-            if (cmQtAutoGen::RccListInputs(this->RccExecutable,
-                                           this->RccListOptions, qrc.QrcFile,
-                                           qrc.Resources, &error)) {
+            if (RccListInputs(qrc.QrcFile, qrc.Resources, error)) {
               for (std::string const& fileName : qrc.Resources) {
                 // Add resource file to the custom command dependencies
                 ccDepends.push_back(fileName);
@@ -903,8 +918,16 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
 
   // Basic setup
   AddDefinitionEscaped(makefile, "_multi_config",
-                       cmQtAutoGen::MultiConfigName(this->MultiConfig));
+                       MultiConfigName(this->MultiConfig));
   AddDefinitionEscaped(makefile, "_build_dir", this->DirBuild);
+  {
+    std::string parallel = GetSafeProperty(this->Target, "AUTOGEN_PARALLEL");
+    // Autodetect number of CPUs
+    if (parallel.empty() || (parallel == "AUTO")) {
+      parallel = std::to_string(GetParallelCPUCount());
+    }
+    AddDefinitionEscaped(makefile, "_parallel", parallel);
+  }
 
   if (this->MocEnabled || this->UicEnabled) {
     AddDefinitionEscaped(makefile, "_qt_version_major", this->QtVersionMajor);
@@ -929,7 +952,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
   // Create info directory on demand
   if (!cmSystemTools::MakeDirectory(this->DirInfo)) {
     std::string emsg = ("Could not create directory: ");
-    emsg += cmQtAutoGen::Quoted(this->DirInfo);
+    emsg += Quoted(this->DirInfo);
     cmSystemTools::Error(emsg.c_str());
   }
 
@@ -951,7 +974,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
     if (!ofs) {
       // File open error
       std::string error = "Internal CMake error when trying to open file: ";
-      error += cmQtAutoGen::Quoted(fileName);
+      error += Quoted(fileName);
       error += " for writing.";
       cmSystemTools::Error(error.c_str());
     }
@@ -984,11 +1007,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
       OfsWriteMap("AM_MOC_INCLUDES", this->ConfigMocIncludes);
       OfsWriteMap("AM_UIC_TARGET_OPTIONS", this->ConfigUicOptions);
       // Settings files (only require for multi configuration generators)
-      if (this->MultiConfig != cmQtAutoGen::SINGLE) {
+      if (this->MultiConfig != MultiConfigT::SINGLE) {
         std::map<std::string, std::string> settingsFiles;
         for (std::string const& cfg : this->ConfigsList) {
-          settingsFiles[cfg] = cmQtAutoGen::AppendFilenameSuffix(
-            this->AutogenSettingsFile, "_" + cfg);
+          settingsFiles[cfg] =
+            AppendFilenameSuffix(this->AutogenSettingsFile, "_" + cfg);
         }
         OfsWriteMap("AM_SETTINGS_FILE", settingsFiles);
       }
@@ -1033,11 +1056,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
           OfsWriteMap("ARCC_CONFIG_SUFFIX", configSuffixes);
 
           // Settings files (only require for multi configuration generators)
-          if (this->MultiConfig != cmQtAutoGen::SINGLE) {
+          if (this->MultiConfig != MultiConfigT::SINGLE) {
             std::map<std::string, std::string> settingsFiles;
             for (std::string const& cfg : this->ConfigsList) {
               settingsFiles[cfg] =
-                cmQtAutoGen::AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
+                AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
             }
             OfsWriteMap("ARCC_SETTINGS_FILE", settingsFiles);
           }
@@ -1267,16 +1290,15 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargetsUic()
 }
 
 std::vector<std::string> cmQtAutoGeneratorInitializer::AddGeneratedSource(
-  std::string const& filename, cmQtAutoGen::Generator genType)
+  std::string const& filename, GeneratorT genType)
 {
   std::vector<std::string> genFiles;
   // Register source file in makefile and source group
-  if (this->MultiConfig != cmQtAutoGen::FULL) {
+  if (this->MultiConfig != MultiConfigT::MULTI) {
     genFiles.push_back(filename);
   } else {
     for (std::string const& cfg : this->ConfigsList) {
-      genFiles.push_back(
-        cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg));
+      genFiles.push_back(AppendFilenameSuffix(filename, "_" + cfg));
     }
   }
   {
@@ -1292,14 +1314,14 @@ std::vector<std::string> cmQtAutoGeneratorInitializer::AddGeneratedSource(
   }
 
   // Add source file to target
-  if (this->MultiConfig != cmQtAutoGen::FULL) {
+  if (this->MultiConfig != MultiConfigT::MULTI) {
     this->Target->AddSource(filename);
   } else {
     for (std::string const& cfg : this->ConfigsList) {
       std::string src = "$<$<CONFIG:";
       src += cfg;
       src += ">:";
-      src += cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg);
+      src += AppendFilenameSuffix(filename, "_" + cfg);
       src += ">";
       this->Target->AddSource(src);
     }
@@ -1356,3 +1378,84 @@ bool cmQtAutoGeneratorInitializer::QtVersionGreaterOrEqual(
   }
   return false;
 }
+
+/// @brief Reads the resource files list from from a .qrc file
+/// @arg fileName Must be the absolute path of the .qrc file
+/// @return True if the rcc file was successfully read
+bool cmQtAutoGeneratorInitializer::RccListInputs(
+  std::string const& fileName, std::vector<std::string>& files,
+  std::string& error)
+{
+  if (!cmSystemTools::FileExists(fileName.c_str())) {
+    error = "rcc resource file does not exist:\n  ";
+    error += Quoted(fileName);
+    error += "\n";
+    return false;
+  }
+  if (!RccListOptions.empty()) {
+    // Use rcc for file listing
+    if (RccExecutable.empty()) {
+      error = "rcc executable not available";
+      return false;
+    }
+
+    // Run rcc list command in the directory of the qrc file with the pathless
+    // qrc file name argument. This way rcc prints relative paths.
+    // This avoids issues on Windows when the qrc file is in a path that
+    // contains non-ASCII characters.
+    std::string const fileDir = cmSystemTools::GetFilenamePath(fileName);
+    std::string const fileNameName = cmSystemTools::GetFilenameName(fileName);
+
+    bool result = false;
+    int retVal = 0;
+    std::string rccStdOut;
+    std::string rccStdErr;
+    {
+      std::vector<std::string> cmd;
+      cmd.push_back(RccExecutable);
+      cmd.insert(cmd.end(), RccListOptions.begin(), RccListOptions.end());
+      cmd.push_back(fileNameName);
+      result = cmSystemTools::RunSingleCommand(
+        cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
+        cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto);
+    }
+    if (!result || retVal) {
+      error = "rcc list process failed for:\n  ";
+      error += Quoted(fileName);
+      error += "\n";
+      error += rccStdOut;
+      error += "\n";
+      error += rccStdErr;
+      error += "\n";
+      return false;
+    }
+    if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) {
+      return false;
+    }
+  } else {
+    // We can't use rcc for the file listing.
+    // Read the qrc file content into string and parse it.
+    {
+      std::string qrcContents;
+      {
+        cmsys::ifstream ifs(fileName.c_str());
+        if (ifs) {
+          std::ostringstream osst;
+          osst << ifs.rdbuf();
+          qrcContents = osst.str();
+        } else {
+          error = "rcc file not readable:\n  ";
+          error += Quoted(fileName);
+          error += "\n";
+          return false;
+        }
+      }
+      // Parse string content
+      RccListParseContent(qrcContents, files);
+    }
+  }
+
+  // Convert relative paths to absolute paths
+  RccListConvertFullPath(cmSystemTools::GetFilenamePath(fileName), files);
+  return true;
+}
diff --git a/Source/cmQtAutoGeneratorInitializer.h b/Source/cmQtAutoGeneratorInitializer.h
index e06e1c4..0567437 100644
--- a/Source/cmQtAutoGeneratorInitializer.h
+++ b/Source/cmQtAutoGeneratorInitializer.h
@@ -13,7 +13,8 @@
 
 class cmGeneratorTarget;
 
-class cmQtAutoGeneratorInitializer
+/// @brief Initializes the QtAutoGen generators
+class cmQtAutoGeneratorInitializer : public cmQtAutoGen
 {
 public:
   static std::string GetQtMajorVersion(cmGeneratorTarget const* target);
@@ -55,11 +56,15 @@ private:
   void SetupCustomTargetsUic();
 
   std::vector<std::string> AddGeneratedSource(std::string const& filename,
-                                              cmQtAutoGen::Generator genType);
+                                              GeneratorT genType);
 
   bool QtVersionGreaterOrEqual(unsigned long requestMajor,
                                unsigned long requestMinor) const;
 
+  bool RccListInputs(std::string const& fileName,
+                     std::vector<std::string>& files,
+                     std::string& errorMessage);
+
 private:
   cmGeneratorTarget* Target;
   bool MocEnabled;
@@ -73,7 +78,7 @@ private:
   // Configurations
   std::string ConfigDefault;
   std::vector<std::string> ConfigsList;
-  cmQtAutoGen::MultiConfig MultiConfig;
+  MultiConfigT MultiConfig;
   // Names
   std::string AutogenTargetName;
   std::string AutogenFolder;
diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoGeneratorMocUic.cxx
index bce148e..4b02e0b 100644
--- a/Source/cmQtAutoGeneratorMocUic.cxx
+++ b/Source/cmQtAutoGeneratorMocUic.cxx
@@ -5,16 +5,15 @@
 
 #include <algorithm>
 #include <array>
+#include <functional>
 #include <list>
 #include <memory>
 #include <sstream>
-#include <string.h>
 #include <utility>
 
 #include "cmAlgorithms.h"
 #include "cmCryptoHash.h"
 #include "cmMakefile.h"
-#include "cmOutputConverter.h"
 #include "cmSystemTools.h"
 #include "cmake.h"
 
@@ -22,1712 +21,2004 @@
 #include <unistd.h>
 #endif
 
-// -- Static variables
-
-static const char* SettingsKeyMoc = "AM_MOC_SETTINGS_HASH";
-static const char* SettingsKeyUic = "AM_UIC_SETTINGS_HASH";
+// -- Class methods
 
-// -- Static functions
+std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath(
+  std::string const& relativePath) const
+{
+  return cmSystemTools::CollapseCombinedPath(AutogenBuildDir, relativePath);
+}
 
-static std::string SubDirPrefix(std::string const& fileName)
+/**
+ * @brief Tries to find the header file to the given file base path by
+ * appending different header extensions
+ * @return True on success
+ */
+bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader(
+  std::string& header, std::string const& testBasePath) const
 {
-  std::string res(cmSystemTools::GetFilenamePath(fileName));
-  if (!res.empty()) {
-    res += '/';
+  for (std::string const& ext : HeaderExtensions) {
+    std::string testFilePath(testBasePath);
+    testFilePath.push_back('.');
+    testFilePath += ext;
+    if (FileSys->FileExists(testFilePath)) {
+      header = testFilePath;
+      return true;
+    }
   }
-  return res;
+  return false;
 }
 
-static bool ListContains(std::vector<std::string> const& list,
-                         std::string const& entry)
+bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped(
+  std::string const& fileName) const
 {
-  return (std::find(list.begin(), list.end(), entry) != list.end());
+  return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
 }
 
-// -- Class methods
+/**
+ * @brief Returns the first relevant Qt macro name found in the given C++ code
+ * @return The name of the Qt macro or an empty string
+ */
+std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro(
+  std::string const& content) const
+{
+  for (KeyExpT const& filter : MacroFilters) {
+    // Run a simple find string operation before the expensive
+    // regular expression check
+    if (content.find(filter.Key) != std::string::npos) {
+      cmsys::RegularExpressionMatch match;
+      if (filter.Exp.find(content.c_str(), match)) {
+        // Return macro name on demand
+        return filter.Key;
+      }
+    }
+  }
+  return std::string();
+}
 
-cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
-  : MultiConfig(cmQtAutoGen::WRAP)
-  , IncludeProjectDirsBefore(false)
-  , QtVersionMajor(4)
-  , MocSettingsChanged(false)
-  , MocPredefsChanged(false)
-  , MocRelaxedMode(false)
-  , UicSettingsChanged(false)
+std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const
 {
-  // Precompile regular expressions
-  this->MocRegExpInclude.compile(
-    "[\n][ \t]*#[ \t]*include[ \t]+"
-    "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
-  this->UicRegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+"
-                                 "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
+  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;
 }
 
-bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile)
+std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile(
+  std::string const& sourcePath, std::string const& includeString) const
 {
-  // -- Meta
-  this->HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions();
+  // Search in vicinity of the source
+  {
+    std::string testPath = sourcePath;
+    testPath += includeString;
+    if (FileSys->FileExists(testPath)) {
+      return FileSys->RealPath(testPath);
+    }
+  }
+  // Search in include directories
+  for (std::string const& path : IncludePaths) {
+    std::string fullPath = path;
+    fullPath.push_back('/');
+    fullPath += includeString;
+    if (FileSys->FileExists(fullPath)) {
+      return FileSys->RealPath(fullPath);
+    }
+  }
+  // Return empty string
+  return std::string();
+}
 
-  // 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;
-    cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
-    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(cmQtAutoGen::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);
+void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies(
+  std::string const& content, std::set<std::string>& depends) const
+{
+  if (!DependFilters.empty() && !content.empty()) {
+    for (KeyExpT const& filter : DependFilters) {
+      // Run a simple find string check
+      if (content.find(filter.Key) != std::string::npos) {
+        // Run the expensive regular expression check loop
+        const char* contentChars = content.c_str();
+        cmsys::RegularExpressionMatch match;
+        while (filter.Exp.find(contentChars, match)) {
           {
-            std::string subValue(itBeg, itEnd);
-            std::vector<std::string> list;
-            cmSystemTools::ExpandListArgument(subValue, list);
-            lists.push_back(std::move(list));
+            std::string dep = match.match(1);
+            if (!dep.empty()) {
+              depends.emplace(std::move(dep));
+            }
           }
+          contentChars += match.end();
         }
-        pos += length;
-        pos += cmQtAutoGen::listSep.size();
       }
     }
-    return lists;
-  };
-  auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
-    const char* valueConf = nullptr;
-    {
-      std::string keyConf = key;
-      keyConf += '_';
-      keyConf += this->GetInfoConfig();
-      valueConf = makefile->GetDefinition(keyConf);
+  }
+}
+
+bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped(
+  std::string const& fileName) const
+{
+  return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
+}
+
+void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk)
+{
+  if (AutoMoc && Header) {
+    // Don't parse header for moc if the file is included by a source already
+    if (wrk.Gen().ParallelMocIncluded(FileName)) {
+      AutoMoc = false;
     }
-    if (valueConf == nullptr) {
-      valueConf = makefile->GetSafeDefinition(key);
+  }
+
+  if (AutoMoc || AutoUic) {
+    std::string error;
+    MetaT meta;
+    if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) {
+      if (!meta.Content.empty()) {
+        meta.FileDir = SubDirPrefix(FileName);
+        meta.FileBase =
+          cmSystemTools::GetFilenameWithoutLastExtension(FileName);
+
+        bool success = true;
+        if (AutoMoc) {
+          if (Header) {
+            success = ParseMocHeader(wrk, meta);
+          } else {
+            success = ParseMocSource(wrk, meta);
+          }
+        }
+        if (AutoUic && success) {
+          ParseUic(wrk, meta);
+        }
+      } else {
+        wrk.LogFileWarning(GeneratorT::GEN, FileName,
+                           "The source file is empty");
+      }
+    } else {
+      wrk.LogFileError(GeneratorT::GEN, FileName,
+                       "Could not read the file: " + error);
     }
-    return std::string(valueConf);
-  };
-  auto InfoGetConfigList =
-    [&InfoGetConfig](const char* key) -> std::vector<std::string> {
-    std::vector<std::string> list;
-    cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
-    return list;
+  }
+}
+
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
+                                                        MetaT const& meta)
+{
+  struct JobPre
+  {
+    bool self;       // source file is self
+    bool underscore; // "moc_" style include
+    std::string SourceFile;
+    std::string IncludeString;
   };
 
-  // -- Read info file
-  if (!makefile->ReadListFile(this->GetInfoFile().c_str())) {
-    this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(),
-                       "File processing failed");
-    return false;
-  }
+  struct MocInclude
+  {
+    std::string Inc;  // full include string
+    std::string Dir;  // include string directory
+    std::string Base; // include string file base
+  };
 
-  // -- Meta
-  this->MultiConfig = cmQtAutoGen::MultiConfigType(InfoGet("AM_MULTI_CONFIG"));
-  this->ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX");
-  if (this->ConfigSuffix.empty()) {
-    this->ConfigSuffix = "_";
-    this->ConfigSuffix += this->GetInfoConfig();
-  }
+  // Check if this source file contains a relevant macro
+  std::string const ownMacro = wrk.Moc().FindMacro(meta.Content);
 
-  this->SettingsFile = InfoGetConfig("AM_SETTINGS_FILE");
-  if (this->SettingsFile.empty()) {
-    this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(),
-                       "Settings file name missing");
-    return false;
+  // Extract moc includes from file
+  std::deque<MocInclude> mocIncsUsc;
+  std::deque<MocInclude> mocIncsDot;
+  {
+    if (meta.Content.find("moc") != std::string::npos) {
+      const char* contentChars = meta.Content.c_str();
+      cmsys::RegularExpressionMatch match;
+      while (wrk.Moc().RegExpInclude.find(contentChars, match)) {
+        std::string incString = match.match(1);
+        std::string incDir(SubDirPrefix(incString));
+        std::string incBase =
+          cmSystemTools::GetFilenameWithoutLastExtension(incString);
+        if (cmHasLiteralPrefix(incBase, "moc_")) {
+          // moc_<BASE>.cxx
+          // Remove the moc_ part from the base name
+          mocIncsUsc.emplace_back(MocInclude{
+            std::move(incString), std::move(incDir), incBase.substr(4) });
+        } else {
+          // <BASE>.moc
+          mocIncsDot.emplace_back(MocInclude{
+            std::move(incString), std::move(incDir), std::move(incBase) });
+        }
+        // Forward content pointer
+        contentChars += match.end();
+      }
+    }
   }
 
-  // - Files and directories
-  this->ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
-  this->ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
-  this->CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
-  this->CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
-  this->IncludeProjectDirsBefore =
-    InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
-  this->AutogenBuildDir = InfoGet("AM_BUILD_DIR");
-  if (this->AutogenBuildDir.empty()) {
-    this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(),
-                       "Autogen build directory missing");
-    return false;
+  // Check if there is anything to do
+  if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) {
+    return true;
   }
 
-  // - Qt environment
-  if (!cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"),
-                                    &this->QtVersionMajor)) {
-    this->QtVersionMajor = 4;
-  }
-  this->MocExecutable = InfoGet("AM_QT_MOC_EXECUTABLE");
-  this->UicExecutable = InfoGet("AM_QT_UIC_EXECUTABLE");
+  bool ownDotMocIncluded = false;
+  bool ownMocUscIncluded = false;
+  std::deque<JobPre> jobs;
 
-  // - Moc
-  if (this->MocEnabled()) {
-    this->MocSkipList = InfoGetList("AM_MOC_SKIP");
-    this->MocDefinitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
-#ifdef _WIN32
-    {
-      std::string const win32("WIN32");
-      if (!ListContains(this->MocDefinitions, win32)) {
-        this->MocDefinitions.push_back(win32);
+  // Process moc_<BASE>.cxx includes
+  for (const MocInclude& mocInc : mocIncsUsc) {
+    std::string const header =
+      MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
+    if (!header.empty()) {
+      // Check if header is skipped
+      if (wrk.Moc().skipped(header)) {
+        continue;
       }
-    }
-#endif
-    this->MocIncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
-    this->MocOptions = InfoGetList("AM_MOC_OPTIONS");
-    this->MocRelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
-    {
-      std::vector<std::string> const MocMacroNames =
-        InfoGetList("AM_MOC_MACRO_NAMES");
-      for (std::string const& item : MocMacroNames) {
-        this->MocMacroFilters.emplace_back(
-          item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
+      // Register moc job
+      const bool ownMoc = (mocInc.Base == meta.FileBase);
+      jobs.emplace_back(JobPre{ ownMoc, true, header, mocInc.Inc });
+      // Store meta information for relaxed mode
+      if (ownMoc) {
+        ownMocUscIncluded = true;
       }
-    }
-    {
-      std::vector<std::string> const mocDependFilters =
-        InfoGetList("AM_MOC_DEPEND_FILTERS");
-      // Insert Q_PLUGIN_METADATA dependency filter
-      if (this->QtVersionMajor != 4) {
-        this->MocDependFilterPush("Q_PLUGIN_METADATA",
-                                  "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
-                                  "[^\\)]*FILE[ \t]*\"([^\"]+)\"");
+    } else {
+      {
+        std::string emsg = "The file includes the moc file ";
+        emsg += Quoted(mocInc.Inc);
+        emsg += ", but the header ";
+        emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
+        emsg += " could not be found.";
+        wrk.LogFileError(GeneratorT::MOC, FileName, emsg);
       }
-      // Insert user defined dependency filters
-      if ((mocDependFilters.size() % 2) == 0) {
-        for (std::vector<std::string>::const_iterator
-               dit = mocDependFilters.begin(),
-               ditEnd = mocDependFilters.end();
-             dit != ditEnd; dit += 2) {
-          if (!this->MocDependFilterPush(*dit, *(dit + 1))) {
-            return false;
+      return false;
+    }
+  }
+
+  // Process <BASE>.moc includes
+  for (const MocInclude& mocInc : mocIncsDot) {
+    const bool ownMoc = (mocInc.Base == meta.FileBase);
+    if (wrk.Moc().RelaxedMode) {
+      // Relaxed mode
+      if (!ownMacro.empty() && ownMoc) {
+        // Add self
+        jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
+        ownDotMocIncluded = true;
+      } else {
+        // In relaxed mode try to find a header instead but issue a warning.
+        // This is for KDE4 compatibility
+        std::string const header =
+          MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
+        if (!header.empty()) {
+          // Check if header is skipped
+          if (wrk.Moc().skipped(header)) {
+            continue;
           }
+          // Register moc job
+          jobs.emplace_back(JobPre{ ownMoc, false, header, mocInc.Inc });
+          if (ownMacro.empty()) {
+            if (ownMoc) {
+              std::string emsg = "The file includes the moc file ";
+              emsg += Quoted(mocInc.Inc);
+              emsg += ", but does not contain a ";
+              emsg += wrk.Moc().MacrosString();
+              emsg += " macro.\nRunning moc on\n  ";
+              emsg += Quoted(header);
+              emsg += "!\nBetter include ";
+              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
+              emsg += " for a compatibility with strict mode.\n"
+                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
+              wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg);
+            } else {
+              std::string emsg = "The file includes the moc file ";
+              emsg += Quoted(mocInc.Inc);
+              emsg += " instead of ";
+              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
+              emsg += ".\nRunning moc on\n  ";
+              emsg += Quoted(header);
+              emsg += "!\nBetter include ";
+              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
+              emsg += " for compatibility with strict mode.\n"
+                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
+              wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg);
+            }
+          }
+        } else {
+          {
+            std::string emsg = "The file includes the moc file ";
+            emsg += Quoted(mocInc.Inc);
+            emsg += ", which seems to be the moc file from a different "
+                    "source file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a "
+                    "matching header ";
+            emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
+            emsg += " could not be found.";
+            wrk.LogFileError(GeneratorT::MOC, FileName, emsg);
+          }
+          return false;
+        }
+      }
+    } else {
+      // Strict mode
+      if (ownMoc) {
+        // Include self
+        jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
+        ownDotMocIncluded = true;
+        // Accept but issue a warning if moc isn't required
+        if (ownMacro.empty()) {
+          std::string emsg = "The file includes the moc file ";
+          emsg += Quoted(mocInc.Inc);
+          emsg += ", but does not contain a ";
+          emsg += wrk.Moc().MacrosString();
+          emsg += " macro.";
+          wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg);
         }
       } else {
-        this->LogFileError(
-          cmQtAutoGen::MOC, this->GetInfoFile(),
-          "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
+        // Don't allow <BASE>.moc include other than self in strict mode
+        {
+          std::string emsg = "The file includes the moc file ";
+          emsg += Quoted(mocInc.Inc);
+          emsg += ", which seems to be the moc file from a different "
+                  "source file.\nThis is not supported. Include ";
+          emsg += Quoted(meta.FileBase + ".moc");
+          emsg += " to run moc on this source file.";
+          wrk.LogFileError(GeneratorT::MOC, FileName, emsg);
+        }
         return false;
       }
     }
-    this->MocPredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
   }
 
-  // - Uic
-  if (this->UicEnabled()) {
-    this->UicSkipList = InfoGetList("AM_UIC_SKIP");
-    this->UicSearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
-    this->UicTargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
-    {
-      auto sources = InfoGetList("AM_UIC_OPTIONS_FILES");
-      auto options = InfoGetLists("AM_UIC_OPTIONS_OPTIONS");
-      // Compare list sizes
-      if (sources.size() != options.size()) {
-        std::ostringstream ost;
-        ost << "files/options lists sizes mismatch (" << sources.size() << "/"
-            << options.size() << ")";
-        this->LogFileError(cmQtAutoGen::UIC, this->GetInfoFile(), ost.str());
-        return false;
+  if (!ownMacro.empty() && !ownDotMocIncluded) {
+    // In this case, check whether the scanned file itself contains a
+    // Q_OBJECT.
+    // If this is the case, the moc_foo.cpp should probably be generated from
+    // foo.cpp instead of foo.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.
+    if (wrk.Moc().RelaxedMode && ownMocUscIncluded) {
+      JobPre uscJobPre;
+      // Remove underscore job request
+      {
+        auto itC = jobs.begin();
+        auto itE = jobs.end();
+        for (; itC != itE; ++itC) {
+          JobPre& job(*itC);
+          if (job.self && job.underscore) {
+            uscJobPre = std::move(job);
+            jobs.erase(itC);
+            break;
+          }
+        }
       }
-      auto fitEnd = sources.cend();
-      auto fit = sources.begin();
-      auto oit = options.begin();
-      while (fit != fitEnd) {
-        this->UicOptions[*fit] = std::move(*oit);
-        ++fit;
-        ++oit;
+      // Issue a warning
+      {
+        std::string emsg = "The file contains a ";
+        emsg += ownMacro;
+        emsg += " macro, but does not include ";
+        emsg += Quoted(meta.FileBase + ".moc");
+        emsg += ". Instead it includes ";
+        emsg += Quoted(uscJobPre.IncludeString);
+        emsg += ".\nRunning moc on\n  ";
+        emsg += Quoted(FileName);
+        emsg += "!\nBetter include ";
+        emsg += Quoted(meta.FileBase + ".moc");
+        emsg += " for compatibility with strict mode.\n"
+                "(CMAKE_AUTOMOC_RELAXED_MODE warning)";
+        wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg);
       }
+      // Add own source job
+      jobs.emplace_back(
+        JobPre{ true, false, FileName, uscJobPre.IncludeString });
+    } else {
+      // Otherwise always error out since it will not compile.
+      {
+        std::string emsg = "The file contains a ";
+        emsg += ownMacro;
+        emsg += " macro, but does not include ";
+        emsg += Quoted(meta.FileBase + ".moc");
+        emsg += "!\nConsider to\n - add #include \"";
+        emsg += meta.FileBase;
+        emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
+        wrk.LogFileError(GeneratorT::MOC, FileName, emsg);
+      }
+      return false;
     }
   }
 
-  // Initialize source file jobs
+  // Convert pre jobs to actual jobs
+  for (JobPre& jobPre : jobs) {
+    JobHandleT jobHandle(new JobMocT(std::move(jobPre.SourceFile), FileName,
+                                     std::move(jobPre.IncludeString)));
+    if (jobPre.self) {
+      // Read depdendencies from this source
+      static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
+    }
+    if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk,
+                                                        MetaT const& meta)
+{
+  bool success = true;
+  std::string const macroName = wrk.Moc().FindMacro(meta.Content);
+  if (!macroName.empty()) {
+    JobHandleT jobHandle(
+      new JobMocT(std::string(FileName), std::string(), std::string()));
+    // Read depdendencies from this source
+    static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
+    success = wrk.Gen().ParallelJobPushMoc(jobHandle);
+  }
+  return success;
+}
+
+std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
+  WorkerT& wrk, std::string const& fileBase) const
+{
+  std::string res = fileBase;
+  res += ".{";
+  res += cmJoin(wrk.Base().HeaderExtensions, ",");
+  res += "}";
+  return res;
+}
+
+std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
+  WorkerT& wrk, std::string const& includerDir, std::string const& includeBase)
+{
+  std::string header;
+  // Search in vicinity of the source
+  if (!wrk.Base().FindHeader(header, includerDir + includeBase)) {
+    // Search in include directories
+    for (std::string const& path : wrk.Moc().IncludePaths) {
+      std::string fullPath = path;
+      fullPath.push_back('/');
+      fullPath += includeBase;
+      if (wrk.Base().FindHeader(header, fullPath)) {
+        break;
+      }
+    }
+  }
+  // Sanitize
+  if (!header.empty()) {
+    header = wrk.FileSys().RealPath(header);
+  }
+  return header;
+}
+
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk,
+                                                  MetaT const& meta)
+{
+  bool success = true;
+  if (meta.Content.find("ui_") != std::string::npos) {
+    const char* contentChars = meta.Content.c_str();
+    cmsys::RegularExpressionMatch match;
+    while (wrk.Uic().RegExpInclude.find(contentChars, match)) {
+      if (!ParseUicInclude(wrk, meta, match.match(1))) {
+        success = false;
+        break;
+      }
+      contentChars += match.end();
+    }
+  }
+  return success;
+}
+
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
+  WorkerT& wrk, MetaT const& meta, std::string&& includeString)
+{
+  bool success = false;
+  std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString);
+  if (!uiInputFile.empty()) {
+    if (!wrk.Uic().skipped(uiInputFile)) {
+      JobHandleT jobHandle(new JobUicT(std::move(uiInputFile), FileName,
+                                       std::move(includeString)));
+      success = wrk.Gen().ParallelJobPushUic(jobHandle);
+    } else {
+      // A skipped file is successful
+      success = true;
+    }
+  }
+  return success;
+}
+
+std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
+  WorkerT& wrk, MetaT const& meta, std::string const& includeString)
+{
+  std::string res;
+  std::string searchFile =
+    cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3);
+  searchFile += ".ui";
+  // Collect search paths list
+  std::deque<std::string> testFiles;
   {
-    // Utility lambdas
-    auto AddJob = [this](std::map<std::string, SourceJob>& jobs,
-                         std::string&& sourceFile) {
-      const bool moc = !this->MocSkip(sourceFile);
-      const bool uic = !this->UicSkip(sourceFile);
-      if (moc || uic) {
-        SourceJob& job = jobs[std::move(sourceFile)];
-        job.Moc = moc;
-        job.Uic = uic;
-      }
-    };
+    std::string const searchPath = SubDirPrefix(includeString);
 
-    // Add header jobs
-    for (std::string& hdr : InfoGetList("AM_HEADERS")) {
-      AddJob(this->HeaderJobs, std::move(hdr));
+    std::string searchFileFull;
+    if (!searchPath.empty()) {
+      searchFileFull = searchPath;
+      searchFileFull += searchFile;
     }
-    // Add source jobs
+    // Vicinity of the source
     {
-      std::vector<std::string> sources = InfoGetList("AM_SOURCES");
-      // Add header(s) for the source file
-      for (std::string const& src : sources) {
-        const bool srcMoc = !this->MocSkip(src);
-        const bool srcUic = !this->UicSkip(src);
-        if (!srcMoc && !srcUic) {
-          continue;
-        }
-        // Search for the default header file and a private header
-        std::array<std::string, 2> headerBases;
-        headerBases[0] = SubDirPrefix(src);
-        headerBases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src);
-        headerBases[1] = headerBases[0];
-        headerBases[1] += "_p";
-        for (std::string const& headerBase : headerBases) {
-          std::string header;
-          if (this->FindHeader(header, headerBase)) {
-            const bool moc = srcMoc && !this->MocSkip(header);
-            const bool uic = srcUic && !this->UicSkip(header);
-            if (moc || uic) {
-              SourceJob& job = this->HeaderJobs[std::move(header)];
-              job.Moc = moc;
-              job.Uic = uic;
-            }
-          }
-        }
+      std::string const sourcePath = meta.FileDir;
+      testFiles.push_back(sourcePath + searchFile);
+      if (!searchPath.empty()) {
+        testFiles.push_back(sourcePath + searchFileFull);
       }
-      // Add Source jobs
-      for (std::string& src : sources) {
-        AddJob(this->SourceJobs, std::move(src));
+    }
+    // AUTOUIC search paths
+    if (!wrk.Uic().SearchPaths.empty()) {
+      for (std::string const& sPath : wrk.Uic().SearchPaths) {
+        testFiles.push_back((sPath + "/").append(searchFile));
+      }
+      if (!searchPath.empty()) {
+        for (std::string const& sPath : wrk.Uic().SearchPaths) {
+          testFiles.push_back((sPath + "/").append(searchFileFull));
+        }
       }
     }
   }
 
-  // Init derived information
-  // ------------------------
-
-  // Init file path checksum generator
-  this->FilePathChecksum.setupParentDirs(
-    this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir,
-    this->ProjectBinaryDir);
-
-  // include directory
-  this->AutogenIncludeDir = "include";
-  if (this->MultiConfig != cmQtAutoGen::SINGLE) {
-    this->AutogenIncludeDir += this->ConfigSuffix;
+  // Search for the .ui file!
+  for (std::string const& testFile : testFiles) {
+    if (wrk.FileSys().FileExists(testFile)) {
+      res = wrk.FileSys().RealPath(testFile);
+      break;
+    }
   }
-  this->AutogenIncludeDir += "/";
 
-  // Moc variables
-  if (this->MocEnabled()) {
-    // Mocs compilation file
-    this->MocCompFileRel = "mocs_compilation";
-    if (this->MultiConfig == cmQtAutoGen::FULL) {
-      this->MocCompFileRel += this->ConfigSuffix;
+  // Log error
+  if (res.empty()) {
+    std::string emsg = "Could not find ";
+    emsg += Quoted(searchFile);
+    emsg += " in\n";
+    for (std::string const& testFile : testFiles) {
+      emsg += "  ";
+      emsg += Quoted(testFile);
+      emsg += "\n";
     }
-    this->MocCompFileRel += ".cpp";
-    this->MocCompFileAbs = cmSystemTools::CollapseCombinedPath(
-      this->AutogenBuildDir, this->MocCompFileRel);
+    wrk.LogFileError(GeneratorT::UIC, FileName, emsg);
+  }
 
-    // Moc predefs file
-    if (!this->MocPredefsCmd.empty()) {
-      this->MocPredefsFileRel = "moc_predefs";
-      if (this->MultiConfig != cmQtAutoGen::SINGLE) {
-        this->MocPredefsFileRel += this->ConfigSuffix;
+  return res;
+}
+
+void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk)
+{
+  // (Re)generate moc_predefs.h on demand
+  bool generate(false);
+  bool fileExists(wrk.FileSys().FileExists(wrk.Moc().PredefsFileAbs));
+  if (!fileExists) {
+    if (wrk.Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(wrk.Moc().PredefsFileRel);
+      reason += " because it doesn't exist";
+      wrk.LogInfo(GeneratorT::MOC, reason);
+    }
+    generate = true;
+  } else if (wrk.Moc().SettingsChanged) {
+    if (wrk.Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(wrk.Moc().PredefsFileRel);
+      reason += " because the settings changed.";
+      wrk.LogInfo(GeneratorT::MOC, reason);
+    }
+    generate = true;
+  }
+  if (generate) {
+    ProcessResultT result;
+    {
+      // Compose command
+      std::vector<std::string> cmd = wrk.Moc().PredefsCmd;
+      // Add includes
+      cmd.insert(cmd.end(), wrk.Moc().Includes.begin(),
+                 wrk.Moc().Includes.end());
+      // Add definitions
+      for (std::string const& def : wrk.Moc().Definitions) {
+        cmd.push_back("-D" + def);
+      }
+      // Execute command
+      if (!wrk.RunProcess(GeneratorT::MOC, result, cmd)) {
+        std::string emsg = "The content generation command for ";
+        emsg += Quoted(wrk.Moc().PredefsFileRel);
+        emsg += " failed.\n";
+        emsg += result.ErrorMessage;
+        wrk.LogCommandError(GeneratorT::MOC, emsg, cmd, result.StdOut);
       }
-      this->MocPredefsFileRel += ".h";
-      this->MocPredefsFileAbs = cmSystemTools::CollapseCombinedPath(
-        this->AutogenBuildDir, this->MocPredefsFileRel);
     }
 
-    // Sort include directories on demand
-    if (this->IncludeProjectDirsBefore) {
-      // Move strings to temporary list
-      std::list<std::string> includes;
-      includes.insert(includes.end(), this->MocIncludePaths.begin(),
-                      this->MocIncludePaths.end());
-      this->MocIncludePaths.clear();
-      this->MocIncludePaths.reserve(includes.size());
-      // Append project directories only
-      {
-        std::array<std::string const*, 2> const movePaths = {
-          { &this->ProjectBinaryDir, &this->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 (cmSystemTools::StringStartsWith(path, ppath->c_str())) {
-              this->MocIncludePaths.push_back(path);
-              it = includes.erase(it);
-            } else {
-              ++it;
-            }
-          }
+    // (Re)write predefs file only on demand
+    if (!result.error()) {
+      if (!fileExists ||
+          wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) {
+        if (wrk.FileSys().FileWrite(GeneratorT::MOC, wrk.Moc().PredefsFileAbs,
+                                    result.StdOut)) {
+          // Success
+        } else {
+          std::string emsg = "Writing ";
+          emsg += Quoted(wrk.Moc().PredefsFileRel);
+          emsg += " failed.";
+          wrk.LogFileError(GeneratorT::MOC, wrk.Moc().PredefsFileAbs, emsg);
         }
+      } else {
+        // Touch to update the time stamp
+        if (wrk.Log().Verbose()) {
+          std::string msg = "Touching ";
+          msg += Quoted(wrk.Moc().PredefsFileRel);
+          msg += ".";
+          wrk.LogInfo(GeneratorT::MOC, msg);
+        }
+        wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs);
       }
-      // Append remaining directories
-      this->MocIncludePaths.insert(this->MocIncludePaths.end(),
-                                   includes.begin(), includes.end());
     }
-    // Compose moc includes list
+  }
+}
+
+void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies(
+  WorkerT& wrk, std::string const& content)
+{
+  wrk.Moc().FindDependencies(content, Depends);
+  DependsValid = true;
+}
+
+void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk)
+{
+  // Compute build file name
+  if (!IncludeString.empty()) {
+    BuildFile = wrk.Base().AutogenIncludeDirAbs;
+    BuildFile += IncludeString;
+  } else {
+    std::string buildRel = wrk.Base().FilePathChecksum.getPart(SourceFile);
+    buildRel += '/';
+    buildRel += "moc_";
+    buildRel += cmSystemTools::GetFilenameWithoutLastExtension(SourceFile);
+    if (wrk.Base().MultiConfig != MultiConfigT::SINGLE) {
+      buildRel += wrk.Base().ConfigSuffix;
+    }
+    buildRel += ".cpp";
+    wrk.Gen().ParallelMocAutoRegister(buildRel);
+    BuildFile = wrk.Base().AbsoluteBuildPath(buildRel);
+  }
+
+  if (UpdateRequired(wrk)) {
+    GenerateMoc(wrk);
+  }
+}
+
+bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
+{
+  bool const verbose = wrk.Gen().Log().Verbose();
+
+  // Test if the build file exists
+  if (!wrk.FileSys().FileExists(BuildFile)) {
+    if (verbose) {
+      std::string reason = "Generating ";
+      reason += Quoted(BuildFile);
+      reason += " from its source file ";
+      reason += Quoted(SourceFile);
+      reason += " because it doesn't exist";
+      wrk.LogInfo(GeneratorT::MOC, reason);
+    }
+    return true;
+  }
+
+  // Test if any setting changed
+  if (wrk.Moc().SettingsChanged) {
+    if (verbose) {
+      std::string reason = "Generating ";
+      reason += Quoted(BuildFile);
+      reason += " from ";
+      reason += Quoted(SourceFile);
+      reason += " because the MOC settings changed";
+      wrk.LogInfo(GeneratorT::MOC, reason);
+    }
+    return true;
+  }
+
+  // Test if the moc_predefs file is newer
+  if (!wrk.Moc().PredefsFileAbs.empty()) {
+    bool isOlder = false;
     {
-      std::set<std::string> frameworkPaths;
-      for (std::string const& path : this->MocIncludePaths) {
-        this->MocIncludes.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);
-          std::string frameworkPath = cmSystemTools::JoinPath(
-            pathComponents.begin(), pathComponents.end() - 2);
-          frameworkPaths.insert(frameworkPath);
-        }
-      }
-      // Append framework includes
-      for (std::string const& path : frameworkPaths) {
-        this->MocIncludes.push_back("-F");
-        this->MocIncludes.push_back(path);
+      std::string error;
+      isOlder = wrk.FileSys().FileIsOlderThan(
+        BuildFile, wrk.Moc().PredefsFileAbs, &error);
+      if (!isOlder && !error.empty()) {
+        wrk.LogError(GeneratorT::MOC, error);
+        return false;
       }
     }
-    // Setup single list with all options
-    {
-      // Add includes
-      this->MocAllOptions.insert(this->MocAllOptions.end(),
-                                 this->MocIncludes.begin(),
-                                 this->MocIncludes.end());
-      // Add definitions
-      for (std::string const& def : this->MocDefinitions) {
-        this->MocAllOptions.push_back("-D" + def);
+    if (isOlder) {
+      if (verbose) {
+        std::string reason = "Generating ";
+        reason += Quoted(BuildFile);
+        reason += " because it's older than: ";
+        reason += Quoted(wrk.Moc().PredefsFileAbs);
+        wrk.LogInfo(GeneratorT::MOC, reason);
       }
-      // Add options
-      this->MocAllOptions.insert(this->MocAllOptions.end(),
-                                 this->MocOptions.begin(),
-                                 this->MocOptions.end());
+      return true;
     }
   }
 
-  return true;
-}
-
-void cmQtAutoGeneratorMocUic::SettingsFileRead(cmMakefile* makefile)
-{
-  // Compose current settings strings
+  // Test if the source file is newer
   {
-    cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
-    std::string const sep(" ~~~ ");
-    if (this->MocEnabled()) {
-      std::string str;
-      str += this->MocExecutable;
-      str += sep;
-      str += cmJoin(this->MocAllOptions, ";");
-      str += sep;
-      str += this->IncludeProjectDirsBefore ? "TRUE" : "FALSE";
-      str += sep;
-      str += cmJoin(this->MocPredefsCmd, ";");
-      str += sep;
-      this->SettingsStringMoc = crypt.HashString(str);
+    bool isOlder = false;
+    {
+      std::string error;
+      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
+      if (!isOlder && !error.empty()) {
+        wrk.LogError(GeneratorT::MOC, error);
+        return false;
+      }
     }
-    if (this->UicEnabled()) {
-      std::string str;
-      str += this->UicExecutable;
-      str += sep;
-      str += cmJoin(this->UicTargetOptions, ";");
-      for (const auto& item : this->UicOptions) {
-        str += sep;
-        str += item.first;
-        str += sep;
-        str += cmJoin(item.second, ";");
+    if (isOlder) {
+      if (verbose) {
+        std::string reason = "Generating ";
+        reason += Quoted(BuildFile);
+        reason += " because it's older than its source file ";
+        reason += Quoted(SourceFile);
+        wrk.LogInfo(GeneratorT::MOC, reason);
       }
-      str += sep;
-      this->SettingsStringUic = crypt.HashString(str);
+      return true;
     }
   }
 
-  // Read old settings
-  if (makefile->ReadListFile(this->SettingsFile.c_str())) {
-    {
-      auto SMatch = [makefile](const char* key, std::string const& value) {
-        return (value == makefile->GetSafeDefinition(key));
-      };
-      if (!SMatch(SettingsKeyMoc, this->SettingsStringMoc)) {
-        this->MocSettingsChanged = true;
-      }
-      if (!SMatch(SettingsKeyUic, this->SettingsStringUic)) {
-        this->UicSettingsChanged = true;
+  // Test if a dependency file is newer
+  {
+    // Read dependencies on demand
+    if (!DependsValid) {
+      std::string content;
+      {
+        std::string error;
+        if (!wrk.FileSys().FileRead(content, SourceFile, &error)) {
+          std::string emsg = "Could not read file\n  ";
+          emsg += Quoted(SourceFile);
+          emsg += "\nrequired by moc include ";
+          emsg += Quoted(IncludeString);
+          emsg += " in\n  ";
+          emsg += Quoted(IncluderFile);
+          emsg += ".\n";
+          emsg += error;
+          wrk.LogError(GeneratorT::MOC, emsg);
+          return false;
+        }
       }
+      FindDependencies(wrk, content);
     }
-    // In case any setting changed remove the old settings file.
-    // This triggers a full rebuild on the next run if the current
-    // build is aborted before writing the current settings in the end.
-    if (this->SettingsChanged()) {
-      cmSystemTools::RemoveFile(this->SettingsFile);
+    // Check dependency timestamps
+    std::string error;
+    std::string sourceDir = SubDirPrefix(SourceFile);
+    for (std::string const& depFileRel : Depends) {
+      std::string depFileAbs =
+        wrk.Moc().FindIncludedFile(sourceDir, depFileRel);
+      if (!depFileAbs.empty()) {
+        if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) {
+          if (verbose) {
+            std::string reason = "Generating ";
+            reason += Quoted(BuildFile);
+            reason += " from ";
+            reason += Quoted(SourceFile);
+            reason += " because it is older than it's dependency file ";
+            reason += Quoted(depFileAbs);
+            wrk.LogInfo(GeneratorT::MOC, reason);
+          }
+          return true;
+        }
+        if (!error.empty()) {
+          wrk.LogError(GeneratorT::MOC, error);
+          return false;
+        }
+      } else {
+        std::string message = "Could not find dependency file ";
+        message += Quoted(depFileRel);
+        wrk.LogFileWarning(GeneratorT::MOC, SourceFile, message);
+      }
     }
-  } else {
-    // If the file could not be read re-generate everythiung.
-    this->MocSettingsChanged = true;
-    this->UicSettingsChanged = true;
   }
+
+  return false;
 }
 
-bool cmQtAutoGeneratorMocUic::SettingsFileWrite()
+void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk)
 {
-  bool success = true;
-  // Only write if any setting changed
-  if (this->SettingsChanged()) {
-    if (this->GetVerbose()) {
-      this->LogInfo(cmQtAutoGen::GEN, "Writing settings file " +
-                      cmQtAutoGen::Quoted(this->SettingsFile));
-    }
-    // Compose settings file content
-    std::string settings;
-    {
-      auto SettingAppend = [&settings](const char* key,
-                                       std::string const& value) {
-        settings += "set(";
-        settings += key;
-        settings += " ";
-        settings += cmOutputConverter::EscapeForCMake(value);
-        settings += ")\n";
-      };
-      SettingAppend(SettingsKeyMoc, this->SettingsStringMoc);
-      SettingAppend(SettingsKeyUic, this->SettingsStringUic);
-    }
-    // Write settings file
-    if (!this->FileWrite(cmQtAutoGen::GEN, this->SettingsFile, settings)) {
-      this->LogFileError(cmQtAutoGen::GEN, this->SettingsFile,
-                         "Settings file writing failed");
-      // Remove old settings file to trigger a full rebuild on the next run
-      cmSystemTools::RemoveFile(this->SettingsFile);
-      success = false;
+  // Make sure the parent directory exists
+  if (wrk.FileSys().MakeParentDirectory(GeneratorT::MOC, BuildFile)) {
+    // Compose moc command
+    std::vector<std::string> cmd;
+    cmd.push_back(wrk.Moc().Executable);
+    // Add options
+    cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(),
+               wrk.Moc().AllOptions.end());
+    // Add predefs include
+    if (!wrk.Moc().PredefsFileAbs.empty()) {
+      cmd.push_back("--include");
+      cmd.push_back(wrk.Moc().PredefsFileAbs);
+    }
+    cmd.push_back("-o");
+    cmd.push_back(BuildFile);
+    cmd.push_back(SourceFile);
+
+    // Execute moc command
+    ProcessResultT result;
+    if (wrk.RunProcess(GeneratorT::MOC, result, cmd)) {
+      // Moc command success
+      if (IncludeString.empty()) {
+        // Notify the generator that a not included file changed
+        wrk.Gen().ParallelMocAutoUpdated();
+      }
+    } else {
+      // Moc command failed
+      {
+        std::string emsg = "The moc process failed to compile\n  ";
+        emsg += Quoted(SourceFile);
+        emsg += "\ninto\n  ";
+        emsg += Quoted(BuildFile);
+        emsg += ".\n";
+        emsg += result.ErrorMessage;
+        wrk.LogCommandError(GeneratorT::MOC, emsg, cmd, result.StdOut);
+      }
+      wrk.FileSys().FileRemove(BuildFile);
     }
   }
-  return success;
 }
 
-bool cmQtAutoGeneratorMocUic::Process(cmMakefile* makefile)
+void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk)
 {
-  // the program goes through all .cpp files to see which moc files are
-  // included. It is not really interesting how the moc file is named, but
-  // what file the moc is created from. Once a moc is included the same moc
-  // may not be included in the mocs_compilation.cpp file anymore.
-  // OTOH if there's a header containing Q_OBJECT where no corresponding
-  // moc file is included anywhere a moc_<filename>.cpp file is created and
-  // included in the mocs_compilation.cpp file.
-
-  if (!this->InitInfoFile(makefile)) {
-    return false;
-  }
-  // Read latest settings
-  this->SettingsFileRead(makefile);
+  // Compute build file name
+  BuildFile = wrk.Base().AutogenIncludeDirAbs;
+  BuildFile += IncludeString;
 
-  // Create AUTOGEN include directory
-  {
-    std::string const incDirAbs = cmSystemTools::CollapseCombinedPath(
-      this->AutogenBuildDir, this->AutogenIncludeDir);
-    if (!cmSystemTools::MakeDirectory(incDirAbs)) {
-      this->LogFileError(cmQtAutoGen::GEN, incDirAbs,
-                         "Could not create directory");
-      return false;
-    }
+  if (UpdateRequired(wrk)) {
+    GenerateUic(wrk);
   }
+}
 
-  // Parse source files
-  for (const auto& item : this->SourceJobs) {
-    if (!this->ParseSourceFile(item.first, item.second)) {
-      return false;
-    }
-  }
-  // Parse header files
-  for (const auto& item : this->HeaderJobs) {
-    if (!this->ParseHeaderFile(item.first, item.second)) {
-      return false;
+bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
+{
+  bool const verbose = wrk.Gen().Log().Verbose();
+
+  // Test if the build file exists
+  if (!wrk.FileSys().FileExists(BuildFile)) {
+    if (verbose) {
+      std::string reason = "Generating ";
+      reason += Quoted(BuildFile);
+      reason += " from its source file ";
+      reason += Quoted(SourceFile);
+      reason += " because it doesn't exist";
+      wrk.LogInfo(GeneratorT::UIC, reason);
     }
-  }
-  // Read missing dependency information
-  if (!this->ParsePostprocess()) {
-    return false;
+    return true;
   }
 
-  // Generate files
-  if (!this->MocGenerateAll()) {
-    return false;
-  }
-  if (!this->UicGenerateAll()) {
-    return false;
+  // Test if the uic settings changed
+  if (wrk.Uic().SettingsChanged) {
+    if (verbose) {
+      std::string reason = "Generating ";
+      reason += Quoted(BuildFile);
+      reason += " from ";
+      reason += Quoted(SourceFile);
+      reason += " because the UIC settings changed";
+      wrk.LogInfo(GeneratorT::UIC, reason);
+    }
+    return true;
   }
 
-  if (!this->SettingsFileWrite()) {
-    return false;
+  // Test if the source file is newer
+  {
+    bool isOlder = false;
+    {
+      std::string error;
+      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
+      if (!isOlder && !error.empty()) {
+        wrk.LogError(GeneratorT::UIC, error);
+        return false;
+      }
+    }
+    if (isOlder) {
+      if (verbose) {
+        std::string reason = "Generating ";
+        reason += Quoted(BuildFile);
+        reason += " because it's older than its source file ";
+        reason += Quoted(SourceFile);
+        wrk.LogInfo(GeneratorT::UIC, reason);
+      }
+      return true;
+    }
   }
 
-  return true;
+  return false;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::ParseSourceFile(std::string const& absFilename,
-                                              const SourceJob& job)
+void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk)
 {
-  std::string contentText;
-  std::string error;
-  bool success = this->FileRead(contentText, absFilename, &error);
-  if (success) {
-    if (!contentText.empty()) {
-      if (job.Moc) {
-        success = this->MocParseSourceContent(absFilename, contentText);
-      }
-      if (success && job.Uic) {
-        success = this->UicParseContent(absFilename, contentText);
+  // Make sure the parent directory exists
+  if (wrk.FileSys().MakeParentDirectory(GeneratorT::UIC, BuildFile)) {
+    // Compose uic command
+    std::vector<std::string> cmd;
+    cmd.push_back(wrk.Uic().Executable);
+    {
+      std::vector<std::string> allOpts = wrk.Uic().TargetOptions;
+      auto optionIt = wrk.Uic().Options.find(SourceFile);
+      if (optionIt != wrk.Uic().Options.end()) {
+        UicMergeOptions(allOpts, optionIt->second,
+                        (wrk.Base().QtVersionMajor == 5));
       }
+      cmd.insert(cmd.end(), allOpts.begin(), allOpts.end());
+    }
+    cmd.push_back("-o");
+    cmd.push_back(BuildFile);
+    cmd.push_back(SourceFile);
+
+    ProcessResultT result;
+    if (wrk.RunProcess(GeneratorT::UIC, result, cmd)) {
+      // Success
     } else {
-      this->LogFileWarning(cmQtAutoGen::GEN, absFilename,
-                           "The source file is empty");
+      // Command failed
+      {
+        std::string emsg = "The uic process failed to compile\n  ";
+        emsg += Quoted(SourceFile);
+        emsg += "\ninto\n  ";
+        emsg += Quoted(BuildFile);
+        emsg += "\nincluded by\n  ";
+        emsg += Quoted(IncluderFile);
+        emsg += ".\n";
+        emsg += result.ErrorMessage;
+        wrk.LogCommandError(GeneratorT::UIC, emsg, cmd, result.StdOut);
+      }
+      wrk.FileSys().FileRemove(BuildFile);
     }
-  } else {
-    this->LogFileError(cmQtAutoGen::GEN, absFilename,
-                       "Could not read the source file: " + error);
   }
-  return success;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::ParseHeaderFile(std::string const& absFilename,
-                                              const SourceJob& job)
+void cmQtAutoGeneratorMocUic::JobDeleterT::operator()(JobT* job)
 {
-  std::string contentText;
-  std::string error;
-  bool success = this->FileRead(contentText, absFilename, &error);
-  if (success) {
-    if (!contentText.empty()) {
-      if (job.Moc) {
-        this->MocParseHeaderContent(absFilename, contentText);
-      }
-      if (job.Uic) {
-        success = this->UicParseContent(absFilename, contentText);
-      }
-    } else {
-      this->LogFileWarning(cmQtAutoGen::GEN, absFilename,
-                           "The header file is empty");
-    }
-  } else {
-    this->LogFileError(cmQtAutoGen::GEN, absFilename,
-                       "Could not read the header file: " + error);
+  delete job;
+}
+
+cmQtAutoGeneratorMocUic::WorkerT::WorkerT(cmQtAutoGeneratorMocUic* gen,
+                                          uv_loop_t* uvLoop)
+  : Gen_(gen)
+{
+  // Initialize uv asynchronous callback for process starting
+  ProcessRequest_.init(*uvLoop, &WorkerT::UVProcessStart, this);
+  // Start thread
+  Thread_ = std::thread(&WorkerT::Loop, this);
+}
+
+cmQtAutoGeneratorMocUic::WorkerT::~WorkerT()
+{
+  // Join thread
+  if (Thread_.joinable()) {
+    Thread_.join();
   }
-  return success;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::ParsePostprocess()
+void cmQtAutoGeneratorMocUic::WorkerT::LogInfo(
+  GeneratorT genType, std::string const& message) const
 {
-  bool success = true;
-  // Read missing dependencies
-  for (auto& item : this->MocJobsIncluded) {
-    if (!item->DependsValid) {
-      std::string content;
-      std::string error;
-      if (this->FileRead(content, item->SourceFile, &error)) {
-        this->MocFindDepends(item->SourceFile, content, item->Depends);
-        item->DependsValid = true;
-      } else {
-        std::string emsg = "Could not read file\n  ";
-        emsg += item->SourceFile;
-        emsg += "\nrequired by moc include \"";
-        emsg += item->IncludeString;
-        emsg += "\".\n";
-        emsg += error;
-        this->LogFileError(cmQtAutoGen::MOC, item->Includer, emsg);
-        success = false;
-        break;
-      }
+  return Log().Info(genType, message);
+}
+
+void cmQtAutoGeneratorMocUic::WorkerT::LogWarning(
+  GeneratorT genType, std::string const& message) const
+{
+  return Log().Warning(genType, message);
+}
+
+void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning(
+  GeneratorT genType, std::string const& filename,
+  std::string const& message) const
+{
+  return Log().WarningFile(genType, filename, message);
+}
+
+void cmQtAutoGeneratorMocUic::WorkerT::LogError(
+  GeneratorT genType, std::string const& message) const
+{
+  Gen().ParallelRegisterJobError();
+  Log().Error(genType, message);
+}
+
+void cmQtAutoGeneratorMocUic::WorkerT::LogFileError(
+  GeneratorT genType, std::string const& filename,
+  std::string const& message) const
+{
+  Gen().ParallelRegisterJobError();
+  Log().ErrorFile(genType, filename, message);
+}
+
+void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError(
+  GeneratorT genType, std::string const& message,
+  std::vector<std::string> const& command, std::string const& output) const
+{
+  Gen().ParallelRegisterJobError();
+  Log().ErrorCommand(genType, message, command, output);
+}
+
+bool cmQtAutoGeneratorMocUic::WorkerT::RunProcess(
+  GeneratorT genType, ProcessResultT& result,
+  std::vector<std::string> const& command)
+{
+  if (command.empty()) {
+    return false;
+  }
+
+  // Create process instance
+  {
+    std::lock_guard<std::mutex> lock(ProcessMutex_);
+    Process_ = cm::make_unique<ReadOnlyProcessT>();
+    Process_->setup(&result, true, command, Gen().Base().AutogenBuildDir);
+  }
+
+  // Send asynchronous process start request to libuv loop
+  ProcessRequest_.send();
+
+  // Log command
+  if (this->Log().Verbose()) {
+    std::string msg = "Running command:\n";
+    msg += QuotedCommand(command);
+    msg += '\n';
+    this->LogInfo(genType, msg);
+  }
+
+  // Wait until the process has been finished and destroyed
+  {
+    std::unique_lock<std::mutex> ulock(ProcessMutex_);
+    while (Process_) {
+      ProcessCondition_.wait(ulock);
     }
   }
-  return success;
+  return !result.error();
 }
 
-/**
- * @brief Tests if the file should be ignored for moc scanning
- * @return True if the file should be ignored
- */
-bool cmQtAutoGeneratorMocUic::MocSkip(std::string const& absFilename) const
+void cmQtAutoGeneratorMocUic::WorkerT::Loop()
 {
-  if (this->MocEnabled()) {
-    // Test if the file name is on the skip list
-    if (!ListContains(this->MocSkipList, absFilename)) {
-      return false;
+  while (true) {
+    Gen().WorkerSwapJob(JobHandle_);
+    if (JobHandle_) {
+      JobHandle_->Process(*this);
+    } else {
+      break;
     }
   }
-  return true;
 }
 
-/**
- * @brief Tests if the C++ content requires moc processing
- * @return True if moc is required
- */
-bool cmQtAutoGeneratorMocUic::MocRequired(std::string const& contentText,
-                                          std::string* macroName)
+void cmQtAutoGeneratorMocUic::WorkerT::UVProcessStart(uv_async_t* handle)
 {
-  for (KeyRegExp& filter : this->MocMacroFilters) {
-    // Run a simple find string operation before the expensive
-    // regular expression check
-    if (contentText.find(filter.Key) != std::string::npos) {
-      if (filter.RegExp.find(contentText)) {
-        // Return macro name on demand
-        if (macroName != nullptr) {
-          *macroName = filter.Key;
-        }
-        return true;
-      }
+  auto& wrk = *reinterpret_cast<WorkerT*>(handle->data);
+  {
+    std::lock_guard<std::mutex> lock(wrk.ProcessMutex_);
+    if (wrk.Process_ && !wrk.Process_->IsStarted()) {
+      wrk.Process_->start(handle->loop,
+                          std::bind(&WorkerT::UVProcessFinished, &wrk));
     }
   }
-  return false;
 }
 
-std::string cmQtAutoGeneratorMocUic::MocStringMacros() const
+void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished()
 {
-  std::string res;
-  const auto itB = this->MocMacroFilters.cbegin();
-  const auto itE = this->MocMacroFilters.cend();
-  const auto itL = itE - 1;
-  auto itC = itB;
-  for (; itC != itE; ++itC) {
-    // Separator
-    if (itC != itB) {
-      if (itC != itL) {
-        res += ", ";
-      } else {
-        res += " or ";
-      }
+  {
+    std::lock_guard<std::mutex> lock(ProcessMutex_);
+    if (Process_ && Process_->IsFinished()) {
+      Process_.reset();
     }
-    // Key
-    res += itC->Key;
   }
-  return res;
+  // Notify idling thread
+  ProcessCondition_.notify_one();
 }
 
-std::string cmQtAutoGeneratorMocUic::MocStringHeaders(
-  std::string const& fileBase) const
+cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
+  : Base_(&FileSys())
+  , Moc_(&FileSys())
+  , Stage_(StageT::SETTINGS_READ)
+  , JobsRemain_(0)
+  , JobError_(false)
+  , JobThreadsAbort_(false)
+  , MocAutoFileUpdated_(false)
 {
-  std::string res = fileBase;
-  res += ".{";
-  res += cmJoin(this->HeaderExtensions, ",");
-  res += "}";
-  return res;
+  // Precompile regular expressions
+  Moc_.RegExpInclude.compile(
+    "[\n][ \t]*#[ \t]*include[ \t]+"
+    "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
+  Uic_.RegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+"
+                             "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
+
+  // Initialize libuv asynchronous iteration request
+  UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this);
 }
 
-std::string cmQtAutoGeneratorMocUic::MocFindIncludedHeader(
-  std::string const& sourcePath, std::string const& includeBase) const
+cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic()
 {
-  std::string header;
-  // Search in vicinity of the source
-  if (!this->FindHeader(header, sourcePath + includeBase)) {
-    // Search in include directories
-    for (std::string const& path : this->MocIncludePaths) {
-      std::string fullPath = path;
-      fullPath.push_back('/');
-      fullPath += includeBase;
-      if (this->FindHeader(header, fullPath)) {
-        break;
-      }
-    }
-  }
-  // Sanitize
-  if (!header.empty()) {
-    header = cmSystemTools::GetRealPath(header);
-  }
-  return header;
 }
 
-bool cmQtAutoGeneratorMocUic::MocFindIncludedFile(
-  std::string& absFile, std::string const& sourcePath,
-  std::string const& includeString) const
+bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
 {
-  bool success = false;
-  // Search in vicinity of the source
-  {
-    std::string testPath = sourcePath;
-    testPath += includeString;
-    if (cmSystemTools::FileExists(testPath.c_str())) {
-      absFile = cmSystemTools::GetRealPath(testPath);
-      success = true;
-    }
-  }
-  // Search in include directories
-  if (!success) {
-    for (std::string const& path : this->MocIncludePaths) {
-      std::string fullPath = path;
-      fullPath.push_back('/');
-      fullPath += includeString;
-      if (cmSystemTools::FileExists(fullPath.c_str())) {
-        absFile = cmSystemTools::GetRealPath(fullPath);
-        success = true;
-        break;
+  // -- Meta
+  Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions();
+
+  // 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;
+    cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
+    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);
+          {
+            std::string subValue(itBeg, itEnd);
+            std::vector<std::string> list;
+            cmSystemTools::ExpandListArgument(subValue, list);
+            lists.push_back(std::move(list));
+          }
+        }
+        pos += length;
+        pos += ListSep.size();
       }
     }
-  }
-  return success;
-}
-
-bool cmQtAutoGeneratorMocUic::MocDependFilterPush(std::string const& key,
-                                                  std::string const& regExp)
-{
-  std::string error;
-  if (!key.empty()) {
-    if (!regExp.empty()) {
-      KeyRegExp filter;
-      filter.Key = key;
-      if (filter.RegExp.compile(regExp)) {
-        this->MocDependFilters.push_back(std::move(filter));
-      } else {
-        error = "Regular expression compiling failed";
-      }
-    } else {
-      error = "Regular expression is empty";
+    return lists;
+  };
+  auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
+    const char* valueConf = nullptr;
+    {
+      std::string keyConf = key;
+      keyConf += '_';
+      keyConf += InfoConfig();
+      valueConf = makefile->GetDefinition(keyConf);
     }
-  } else {
-    error = "Key is empty";
-  }
-  if (!error.empty()) {
-    std::string emsg = "AUTOMOC_DEPEND_FILTERS: ";
-    emsg += error;
-    emsg += "\n";
-    emsg += "  Key:    ";
-    emsg += cmQtAutoGen::Quoted(key);
-    emsg += "\n";
-    emsg += "  RegExp: ";
-    emsg += cmQtAutoGen::Quoted(regExp);
-    emsg += "\n";
-    this->LogError(cmQtAutoGen::MOC, emsg);
+    if (valueConf == nullptr) {
+      valueConf = makefile->GetSafeDefinition(key);
+    }
+    return std::string(valueConf);
+  };
+  auto InfoGetConfigList =
+    [&InfoGetConfig](const char* key) -> std::vector<std::string> {
+    std::vector<std::string> list;
+    cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
+    return list;
+  };
+
+  // -- Read info file
+  if (!makefile->ReadListFile(InfoFile().c_str())) {
+    Log().ErrorFile(GeneratorT::GEN, InfoFile(), "File processing failed");
     return false;
   }
-  return true;
-}
 
-void cmQtAutoGeneratorMocUic::MocFindDepends(std::string const& absFilename,
-                                             std::string const& contentText,
-                                             std::set<std::string>& depends)
-{
-  if (this->MocDependFilters.empty() && contentText.empty()) {
-    return;
-  }
-
-  std::vector<std::string> matches;
-  for (KeyRegExp& filter : this->MocDependFilters) {
-    // Run a simple find string check
-    if (contentText.find(filter.Key) != std::string::npos) {
-      // Run the expensive regular expression check loop
-      const char* contentChars = contentText.c_str();
-      while (filter.RegExp.find(contentChars)) {
-        std::string match = filter.RegExp.match(1);
-        if (!match.empty()) {
-          matches.emplace_back(std::move(match));
-        }
-        contentChars += filter.RegExp.end();
-      }
-    }
-  }
+  // -- Meta
+  Base_.MultiConfig = MultiConfigType(InfoGet("AM_MULTI_CONFIG"));
 
-  if (!matches.empty()) {
-    std::string const sourcePath = SubDirPrefix(absFilename);
-    for (std::string const& match : matches) {
-      // Find the dependency file
-      std::string incFile;
-      if (this->MocFindIncludedFile(incFile, sourcePath, match)) {
-        depends.insert(incFile);
-        if (this->GetVerbose()) {
-          this->LogInfo(cmQtAutoGen::MOC, "Found dependency:\n  " +
-                          cmQtAutoGen::Quoted(absFilename) + "\n  " +
-                          cmQtAutoGen::Quoted(incFile));
-        }
-      } else {
-        this->LogFileWarning(cmQtAutoGen::MOC, absFilename,
-                             "Could not find dependency file " +
-                               cmQtAutoGen::Quoted(match));
-      }
-    }
+  Base_.ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX");
+  if (Base_.ConfigSuffix.empty()) {
+    Base_.ConfigSuffix = "_";
+    Base_.ConfigSuffix += InfoConfig();
   }
-}
-
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::MocParseSourceContent(
-  std::string const& absFilename, std::string const& contentText)
-{
-  if (this->GetVerbose()) {
-    this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename);
-  }
-
-  auto AddJob = [this, &absFilename](std::string const& sourceFile,
-                                     std::string const& includeString,
-                                     std::string const* content) {
-    auto job = cm::make_unique<MocJobIncluded>();
-    job->SourceFile = sourceFile;
-    job->BuildFileRel = this->AutogenIncludeDir;
-    job->BuildFileRel += includeString;
-    job->Includer = absFilename;
-    job->IncludeString = includeString;
-    job->DependsValid = (content != nullptr);
-    if (job->DependsValid) {
-      this->MocFindDepends(sourceFile, *content, job->Depends);
-    }
-    this->MocJobsIncluded.push_back(std::move(job));
-  };
 
-  struct MocInc
-  {
-    std::string Inc;  // full include string
-    std::string Dir;  // include string directory
-    std::string Base; // include string file base
-  };
+  SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE");
+  if (SettingsFile_.empty()) {
+    Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Settings file name missing");
+    return false;
+  }
 
-  // Extract moc includes from file
-  std::vector<MocInc> mocIncsUsc;
-  std::vector<MocInc> mocIncsDot;
   {
-    const char* contentChars = contentText.c_str();
-    if (strstr(contentChars, "moc") != nullptr) {
-      while (this->MocRegExpInclude.find(contentChars)) {
-        std::string incString = this->MocRegExpInclude.match(1);
-        std::string incDir(SubDirPrefix(incString));
-        std::string incBase =
-          cmSystemTools::GetFilenameWithoutLastExtension(incString);
-        if (cmHasLiteralPrefix(incBase, "moc_")) {
-          // moc_<BASE>.cxx
-          // Remove the moc_ part from the base name
-          mocIncsUsc.push_back(MocInc{ std::move(incString), std::move(incDir),
-                                       incBase.substr(4) });
-        } else {
-          // <BASE>.moc
-          mocIncsDot.push_back(MocInc{ std::move(incString), std::move(incDir),
-                                       std::move(incBase) });
-        }
-        // Forward content pointer
-        contentChars += this->MocRegExpInclude.end();
-      }
+    unsigned long num = Base_.NumThreads;
+    if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL"), &num)) {
+      num = std::max<unsigned long>(num, 1);
+      num = std::min<unsigned long>(num, ParallelMax);
+      Base_.NumThreads = static_cast<unsigned int>(num);
     }
   }
 
-  std::string selfMacroName;
-  const bool selfRequiresMoc = this->MocRequired(contentText, &selfMacroName);
-
-  // Check if there is anything to do
-  if (!selfRequiresMoc && mocIncsUsc.empty() && mocIncsDot.empty()) {
-    return true;
+  // - Files and directories
+  Base_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
+  Base_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
+  Base_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
+  Base_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
+  Base_.IncludeProjectDirsBefore =
+    InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
+  Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR");
+  if (Base_.AutogenBuildDir.empty()) {
+    Log().ErrorFile(GeneratorT::GEN, InfoFile(),
+                    "Autogen build directory missing");
+    return false;
   }
 
-  // Scan file variables
-  std::string const scanFileDir = SubDirPrefix(absFilename);
-  std::string const scanFileBase =
-    cmSystemTools::GetFilenameWithoutLastExtension(absFilename);
-  // Relaxed mode variables
-  bool ownDotMocIncluded = false;
-  std::string ownMocUscInclude;
-  std::string ownMocUscHeader;
-
-  // Process moc_<BASE>.cxx includes
-  for (const MocInc& mocInc : mocIncsUsc) {
-    std::string const header =
-      this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base);
-    if (!header.empty()) {
-      // Check if header is skipped
-      if (this->MocSkip(header)) {
-        continue;
-      }
-      // Register moc job
-      AddJob(header, mocInc.Inc, nullptr);
-      // Store meta information for relaxed mode
-      if (this->MocRelaxedMode && (mocInc.Base == scanFileBase)) {
-        ownMocUscInclude = mocInc.Inc;
-        ownMocUscHeader = header;
-      }
-    } else {
-      std::string emsg = "The file includes the moc file ";
-      emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-      emsg += ", but could not find the header ";
-      emsg += cmQtAutoGen::Quoted(this->MocStringHeaders(mocInc.Base));
-      this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg);
-      return false;
+  // - Qt environment
+  {
+    unsigned long qtv = Base_.QtVersionMajor;
+    if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"), &qtv)) {
+      Base_.QtVersionMajor = static_cast<unsigned int>(qtv);
     }
   }
 
-  // Process <BASE>.moc includes
-  for (const MocInc& mocInc : mocIncsDot) {
-    const bool ownMoc = (mocInc.Base == scanFileBase);
-    if (this->MocRelaxedMode) {
-      // Relaxed mode
-      if (selfRequiresMoc && ownMoc) {
-        // Add self
-        AddJob(absFilename, mocInc.Inc, &contentText);
-        ownDotMocIncluded = true;
-      } else {
-        // In relaxed mode try to find a header instead but issue a warning.
-        // This is for KDE4 compatibility
-        std::string const header =
-          this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base);
-        if (!header.empty()) {
-          // Check if header is skipped
-          if (this->MocSkip(header)) {
-            continue;
-          }
-          // Register moc job
-          AddJob(header, mocInc.Inc, nullptr);
-          if (!selfRequiresMoc) {
-            if (ownMoc) {
-              std::string emsg = "The file includes the moc file ";
-              emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-              emsg += ", but does not contain a ";
-              emsg += this->MocStringMacros();
-              emsg += " macro.\nRunning moc on\n  ";
-              emsg += cmQtAutoGen::Quoted(header);
-              emsg += "!\nBetter include ";
-              emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp");
-              emsg += " for a compatibility with strict mode.\n"
-                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
-              this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg);
+  // - Moc
+  Moc_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE");
+  Moc_.Enabled = !Moc().Executable.empty();
+  if (Moc().Enabled) {
+    {
+      auto lst = InfoGetList("AM_MOC_SKIP");
+      Moc_.SkipList.insert(lst.begin(), lst.end());
+    }
+    Moc_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
+#ifdef _WIN32
+    {
+      std::string win32("WIN32");
+      auto itB = Moc().Definitions.cbegin();
+      auto itE = Moc().Definitions.cend();
+      if (std::find(itB, itE, win32) == itE) {
+        Moc_.Definitions.emplace_back(std::move(win32));
+      }
+    }
+#endif
+    Moc_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
+    Moc_.Options = InfoGetList("AM_MOC_OPTIONS");
+    Moc_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
+    for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) {
+      Moc_.MacroFilters.emplace_back(
+        item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
+    }
+    {
+      auto pushFilter = [this](std::string const& key, std::string const& exp,
+                               std::string& error) {
+        if (!key.empty()) {
+          if (!exp.empty()) {
+            Moc_.DependFilters.push_back(KeyExpT());
+            KeyExpT& filter(Moc_.DependFilters.back());
+            if (filter.Exp.compile(exp)) {
+              filter.Key = key;
             } else {
-              std::string emsg = "The file includes the moc file ";
-              emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-              emsg += " instead of ";
-              emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp");
-              emsg += ".\nRunning moc on\n  ";
-              emsg += cmQtAutoGen::Quoted(header);
-              emsg += "!\nBetter include ";
-              emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp");
-              emsg += " for compatibility with strict mode.\n"
-                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
-              this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg);
+              error = "Regular expression compiling failed";
             }
+          } else {
+            error = "Regular expression is empty";
           }
         } else {
-          std::string emsg = "The file includes the moc file ";
-          emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-          emsg += ", which seems to be the moc file from a different "
-                  "source file. CMake also could not find a matching "
-                  "header.";
-          this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg);
-          return false;
+          error = "Key is empty";
         }
-      }
-    } else {
-      // Strict mode
-      if (ownMoc) {
-        // Include self
-        AddJob(absFilename, mocInc.Inc, &contentText);
-        ownDotMocIncluded = true;
-        // Accept but issue a warning if moc isn't required
-        if (!selfRequiresMoc) {
-          std::string emsg = "The file includes the moc file ";
-          emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-          emsg += ", but does not contain a ";
-          emsg += this->MocStringMacros();
-          emsg += " macro.";
-          this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg);
+        if (!error.empty()) {
+          error = ("AUTOMOC_DEPEND_FILTERS: " + error);
+          error += "\n";
+          error += "  Key: ";
+          error += Quoted(key);
+          error += "\n";
+          error += "  Exp: ";
+          error += Quoted(exp);
+          error += "\n";
         }
-      } else {
-        // Don't allow <BASE>.moc include other than self in strict mode
-        std::string emsg = "The file includes the moc file ";
-        emsg += cmQtAutoGen::Quoted(mocInc.Inc);
-        emsg += ", which seems to be the moc file from a different "
-                "source file.\nThis is not supported. Include ";
-        emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc");
-        emsg += " to run moc on this source file.";
-        this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg);
-        return false;
-      }
-    }
-  }
+      };
 
-  if (selfRequiresMoc && !ownDotMocIncluded) {
-    // In this case, check whether the scanned file itself contains a Q_OBJECT.
-    // If this is the case, the moc_foo.cpp should probably be generated from
-    // foo.cpp instead of foo.h, because otherwise it won't build.
-    // But warn, since this is not how it is supposed to be used.
-    if (this->MocRelaxedMode && !ownMocUscInclude.empty()) {
-      // This is for KDE4 compatibility:
-      std::string emsg = "The file contains a ";
-      emsg += selfMacroName;
-      emsg += " macro, but does not include ";
-      emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc");
-      emsg += ". Instead it includes ";
-      emsg += cmQtAutoGen::Quoted(ownMocUscInclude);
-      emsg += ".\nRunning moc on\n  ";
-      emsg += cmQtAutoGen::Quoted(absFilename);
-      emsg += "!\nBetter include ";
-      emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc");
-      emsg += " for compatibility with strict mode.\n"
-              "(CMAKE_AUTOMOC_RELAXED_MODE warning)";
-      this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg);
-
-      // Remove own header job
+      std::string error;
+      // Insert default filter for Q_PLUGIN_METADATA
+      if (Base().QtVersionMajor != 4) {
+        pushFilter("Q_PLUGIN_METADATA", "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
+                                        "[^\\)]*FILE[ \t]*\"([^\"]+)\"",
+                   error);
+      }
+      // Insert user defined dependency filters
       {
-        auto itC = this->MocJobsIncluded.begin();
-        auto itE = this->MocJobsIncluded.end();
-        for (; itC != itE; ++itC) {
-          if ((*itC)->SourceFile == ownMocUscHeader) {
-            if ((*itC)->IncludeString == ownMocUscInclude) {
-              this->MocJobsIncluded.erase(itC);
+        std::vector<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS");
+        if ((flts.size() % 2) == 0) {
+          for (std::vector<std::string>::iterator itC = flts.begin(),
+                                                  itE = flts.end();
+               itC != itE; itC += 2) {
+            pushFilter(*itC, *(itC + 1), error);
+            if (!error.empty()) {
               break;
             }
           }
+        } else {
+          Log().ErrorFile(
+            GeneratorT::MOC, InfoFile(),
+            "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
+          return false;
         }
       }
-      // Add own source job
-      AddJob(absFilename, ownMocUscInclude, &contentText);
-    } else {
-      // Otherwise always error out since it will not compile:
-      std::string emsg = "The file contains a ";
-      emsg += selfMacroName;
-      emsg += " macro, but does not include ";
-      emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc");
-      emsg += "!\nConsider to\n - add #include \"";
-      emsg += scanFileBase;
-      emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
-      this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg);
-      return false;
+      if (!error.empty()) {
+        Log().ErrorFile(GeneratorT::MOC, InfoFile(), error);
+        return false;
+      }
+    }
+    Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
+    // Install moc predefs job
+    if (!Moc().PredefsCmd.empty()) {
+      JobQueues_.MocPredefs.emplace_back(new JobMocPredefsT());
     }
   }
-  return true;
-}
 
-void cmQtAutoGeneratorMocUic::MocParseHeaderContent(
-  std::string const& absFilename, std::string const& contentText)
-{
-  if (this->GetVerbose()) {
-    this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename);
-  }
-
-  auto const fit =
-    std::find_if(this->MocJobsIncluded.cbegin(), this->MocJobsIncluded.cend(),
-                 [&absFilename](std::unique_ptr<MocJobIncluded> const& job) {
-                   return job->SourceFile == absFilename;
-                 });
-  if (fit == this->MocJobsIncluded.cend()) {
-    if (this->MocRequired(contentText)) {
-      auto job = cm::make_unique<MocJobAuto>();
-      job->SourceFile = absFilename;
-      {
-        std::string& bld = job->BuildFileRel;
-        bld = this->FilePathChecksum.getPart(absFilename);
-        bld += '/';
-        bld += "moc_";
-        bld += cmSystemTools::GetFilenameWithoutLastExtension(absFilename);
-        if (this->MultiConfig != cmQtAutoGen::SINGLE) {
-          bld += this->ConfigSuffix;
-        }
-        bld += ".cpp";
+  // - Uic
+  Uic_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE");
+  Uic_.Enabled = !Uic().Executable.empty();
+  if (Uic().Enabled) {
+    {
+      auto lst = InfoGetList("AM_UIC_SKIP");
+      Uic_.SkipList.insert(lst.begin(), lst.end());
+    }
+    Uic_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
+    Uic_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
+    {
+      auto sources = InfoGetList("AM_UIC_OPTIONS_FILES");
+      auto options = InfoGetLists("AM_UIC_OPTIONS_OPTIONS");
+      // Compare list sizes
+      if (sources.size() != options.size()) {
+        std::ostringstream ost;
+        ost << "files/options lists sizes missmatch (" << sources.size() << "/"
+            << options.size() << ")";
+        Log().ErrorFile(GeneratorT::UIC, InfoFile(), ost.str());
+        return false;
+      }
+      auto fitEnd = sources.cend();
+      auto fit = sources.begin();
+      auto oit = options.begin();
+      while (fit != fitEnd) {
+        Uic_.Options[*fit] = std::move(*oit);
+        ++fit;
+        ++oit;
       }
-      this->MocFindDepends(absFilename, contentText, job->Depends);
-      this->MocJobsAuto.push_back(std::move(job));
     }
   }
-}
-
-bool cmQtAutoGeneratorMocUic::MocGenerateAll()
-{
-  if (!this->MocEnabled()) {
-    return true;
-  }
 
-  // Look for name collisions in included moc files
+  // Initialize source file jobs
   {
-    bool collision = false;
-    std::map<std::string, std::vector<MocJobIncluded const*>> collisions;
-    for (auto const& job : this->MocJobsIncluded) {
-      auto& list = collisions[job->IncludeString];
-      if (!list.empty()) {
-        collision = true;
-      }
-      list.push_back(job.get());
-    }
-    if (collision) {
-      std::string emsg =
-        "Included moc files with the same name will be "
-        "generated from different sources.\n"
-        "Consider 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"
-        "Include conflicts\n"
-        "-----------------\n";
-      const auto& colls = collisions;
-      for (auto const& coll : colls) {
-        if (coll.second.size() > 1) {
-          emsg += cmQtAutoGen::Quoted(coll.first);
-          emsg += " included in\n";
-          for (const MocJobIncluded* job : coll.second) {
-            emsg += " - ";
-            emsg += cmQtAutoGen::Quoted(job->Includer);
-            emsg += "\n";
-          }
-          emsg += "would be generated from\n";
-          for (const MocJobIncluded* job : coll.second) {
-            emsg += " - ";
-            emsg += cmQtAutoGen::Quoted(job->SourceFile);
-            emsg += "\n";
+    std::hash<std::string> stringHash;
+    std::set<std::size_t> uniqueHeaders;
+
+    // Add header jobs
+    for (std::string& hdr : InfoGetList("AM_HEADERS")) {
+      const bool moc = !Moc().skipped(hdr);
+      const bool uic = !Uic().skipped(hdr);
+      if ((moc || uic) && uniqueHeaders.emplace(stringHash(hdr)).second) {
+        JobQueues_.Headers.emplace_back(
+          new JobParseT(std::move(hdr), moc, uic, true));
+      }
+    }
+    // Add source jobs
+    {
+      std::vector<std::string> sources = InfoGetList("AM_SOURCES");
+      // Add header(s) for the source file
+      for (std::string& src : sources) {
+        const bool srcMoc = !Moc().skipped(src);
+        const bool srcUic = !Uic().skipped(src);
+        if (!srcMoc && !srcUic) {
+          continue;
+        }
+        // Search for the default header file and a private header
+        {
+          std::array<std::string, 2> bases;
+          bases[0] = SubDirPrefix(src);
+          bases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src);
+          bases[1] = bases[0];
+          bases[1] += "_p";
+          for (std::string const& headerBase : bases) {
+            std::string header;
+            if (Base().FindHeader(header, headerBase)) {
+              const bool moc = srcMoc && !Moc().skipped(header);
+              const bool uic = srcUic && !Uic().skipped(header);
+              if ((moc || uic) &&
+                  uniqueHeaders.emplace(stringHash(header)).second) {
+                JobQueues_.Headers.emplace_back(
+                  new JobParseT(std::move(header), moc, uic, true));
+              }
+            }
           }
         }
+        // Add source job
+        JobQueues_.Sources.emplace_back(
+          new JobParseT(std::move(src), srcMoc, srcUic));
       }
-      this->LogError(cmQtAutoGen::MOC, emsg);
-      return false;
     }
   }
 
-  // (Re)generate moc_predefs.h on demand
-  if (!this->MocPredefsCmd.empty()) {
-    if (this->MocSettingsChanged ||
-        !cmSystemTools::FileExists(this->MocPredefsFileAbs)) {
-      if (this->GetVerbose()) {
-        this->LogBold("Generating MOC predefs " + this->MocPredefsFileRel);
+  // Init derived information
+  // ------------------------
+
+  // Init file path checksum generator
+  Base_.FilePathChecksum.setupParentDirs(
+    Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir,
+    Base().ProjectBinaryDir);
+
+  // include directory
+  Base_.AutogenIncludeDirRel = "include";
+  if (Base().MultiConfig != MultiConfigT::SINGLE) {
+    Base_.AutogenIncludeDirRel += Base().ConfigSuffix;
+  }
+  Base_.AutogenIncludeDirRel += "/";
+  Base_.AutogenIncludeDirAbs =
+    Base_.AbsoluteBuildPath(Base().AutogenIncludeDirRel);
+
+  // Moc variables
+  if (Moc().Enabled) {
+    // Mocs compilation file
+    Moc_.CompFileRel = "mocs_compilation";
+    if (Base_.MultiConfig == MultiConfigT::MULTI) {
+      Moc_.CompFileRel += Base().ConfigSuffix;
+    }
+    Moc_.CompFileRel += ".cpp";
+    Moc_.CompFileAbs = Base_.AbsoluteBuildPath(Moc().CompFileRel);
+
+    // Moc predefs file
+    if (!Moc_.PredefsCmd.empty()) {
+      Moc_.PredefsFileRel = "moc_predefs";
+      if (Base_.MultiConfig != MultiConfigT::SINGLE) {
+        Moc_.PredefsFileRel += Base().ConfigSuffix;
       }
+      Moc_.PredefsFileRel += ".h";
+      Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel);
+    }
 
-      std::string output;
+    // Sort include directories on demand
+    if (Base().IncludeProjectDirsBefore) {
+      // Move strings to temporary list
+      std::list<std::string> includes;
+      includes.insert(includes.end(), Moc().IncludePaths.begin(),
+                      Moc().IncludePaths.end());
+      Moc_.IncludePaths.clear();
+      Moc_.IncludePaths.reserve(includes.size());
+      // Append project directories only
       {
-        // Compose command
-        std::vector<std::string> cmd = this->MocPredefsCmd;
-        // Add includes
-        cmd.insert(cmd.end(), this->MocIncludes.begin(),
-                   this->MocIncludes.end());
-        // Add definitions
-        for (std::string const& def : this->MocDefinitions) {
-          cmd.push_back("-D" + def);
-        }
-        // Execute command
-        if (!this->RunCommand(cmd, output)) {
-          this->LogCommandError(cmQtAutoGen::MOC,
-                                "moc_predefs generation failed", cmd, output);
-          return false;
+        std::array<std::string const*, 2> const movePaths = {
+          { &Base().ProjectBinaryDir, &Base().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 (cmSystemTools::StringStartsWith(path, ppath->c_str())) {
+              Moc_.IncludePaths.push_back(path);
+              it = includes.erase(it);
+            } else {
+              ++it;
+            }
+          }
         }
       }
-
-      // (Re)write predefs file only on demand
-      if (this->FileDiffers(this->MocPredefsFileAbs, output)) {
-        if (this->FileWrite(cmQtAutoGen::MOC, this->MocPredefsFileAbs,
-                            output)) {
-          this->MocPredefsChanged = true;
-        } else {
-          this->LogFileError(cmQtAutoGen::MOC, this->MocPredefsFileAbs,
-                             "moc_predefs file writing failed");
-          return false;
-        }
-      } else {
-        // Touch to update the time stamp
-        if (this->GetVerbose()) {
-          this->LogInfo(cmQtAutoGen::MOC,
-                        "Touching moc_predefs " + this->MocPredefsFileRel);
+      // Append remaining directories
+      Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(),
+                               includes.end());
+    }
+    // Compose moc includes list
+    {
+      std::set<std::string> frameworkPaths;
+      for (std::string const& path : Moc().IncludePaths) {
+        Moc_.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);
+          std::string frameworkPath = cmSystemTools::JoinPath(
+            pathComponents.begin(), pathComponents.end() - 2);
+          frameworkPaths.insert(frameworkPath);
         }
-        cmSystemTools::Touch(this->MocPredefsFileAbs, false);
+      }
+      // Append framework includes
+      for (std::string const& path : frameworkPaths) {
+        Moc_.Includes.push_back("-F");
+        Moc_.Includes.push_back(path);
       }
     }
-
-    // Add moc_predefs.h to moc file dependencies
-    for (auto const& item : this->MocJobsIncluded) {
-      item->Depends.insert(this->MocPredefsFileAbs);
-    }
-    for (auto const& item : this->MocJobsAuto) {
-      item->Depends.insert(this->MocPredefsFileAbs);
+    // Setup single list with all options
+    {
+      // Add includes
+      Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(),
+                             Moc().Includes.end());
+      // Add definitions
+      for (std::string const& def : Moc().Definitions) {
+        Moc_.AllOptions.push_back("-D" + def);
+      }
+      // Add options
+      Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(),
+                             Moc().Options.end());
     }
   }
 
-  // Generate moc files that are included by source files.
-  for (auto const& item : this->MocJobsIncluded) {
-    if (!this->MocGenerateFile(*item)) {
-      return false;
-    }
-  }
-  // Generate moc files that are _not_ included by source files.
-  bool autoNameGenerated = false;
-  for (auto const& item : this->MocJobsAuto) {
-    if (!this->MocGenerateFile(*item, &autoNameGenerated)) {
+  return true;
+}
+
+bool cmQtAutoGeneratorMocUic::Process()
+{
+  // Run libuv event loop
+  UVRequest().send();
+  if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
+    if (JobError_) {
       return false;
     }
+  } else {
+    return false;
   }
+  return true;
+}
 
-  // Compose mocs compilation file content
-  {
-    std::string mocs =
-      "// This file is autogenerated. Changes will be overwritten.\n";
-    if (this->MocJobsAuto.empty()) {
-      // Placeholder content
-      mocs +=
-        "// No files found that require moc or the moc files are included\n";
-      mocs += "enum some_compilers { need_more_than_nothing };\n";
-    } else {
-      // Valid content
-      for (const auto& item : this->MocJobsAuto) {
-        mocs += "#include \"";
-        mocs += item->BuildFileRel;
-        mocs += "\"\n";
-      }
-    }
+void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle)
+{
+  reinterpret_cast<cmQtAutoGeneratorMocUic*>(handle->data)->PollStage();
+}
 
-    if (this->FileDiffers(this->MocCompFileAbs, mocs)) {
-      // Actually write mocs compilation file
-      if (this->GetVerbose()) {
-        this->LogBold("Generating MOC compilation " + this->MocCompFileRel);
+void cmQtAutoGeneratorMocUic::PollStage()
+{
+  switch (Stage_) {
+    case StageT::SETTINGS_READ:
+      SettingsFileRead();
+      SetStage(StageT::CREATE_DIRECTORIES);
+      break;
+    case StageT::CREATE_DIRECTORIES:
+      CreateDirectories();
+      SetStage(StageT::PARSE_SOURCES);
+      break;
+    case StageT::PARSE_SOURCES:
+      if (ThreadsStartJobs(JobQueues_.Sources)) {
+        SetStage(StageT::PARSE_HEADERS);
       }
-      if (!this->FileWrite(cmQtAutoGen::MOC, this->MocCompFileAbs, mocs)) {
-        this->LogFileError(cmQtAutoGen::MOC, this->MocCompFileAbs,
-                           "mocs compilation file writing failed");
-        return false;
+      break;
+    case StageT::PARSE_HEADERS:
+      if (ThreadsStartJobs(JobQueues_.Headers)) {
+        SetStage(StageT::MOC_PREDEFS);
       }
-    } else if (autoNameGenerated) {
-      // Only touch mocs compilation file
-      if (this->GetVerbose()) {
-        this->LogInfo(cmQtAutoGen::MOC,
-                      "Touching mocs compilation " + this->MocCompFileRel);
+      break;
+    case StageT::MOC_PREDEFS:
+      if (ThreadsStartJobs(JobQueues_.MocPredefs)) {
+        SetStage(StageT::MOC_PROCESS);
       }
-      cmSystemTools::Touch(this->MocCompFileAbs, false);
-    }
+      break;
+    case StageT::MOC_PROCESS:
+      if (ThreadsStartJobs(JobQueues_.Moc)) {
+        SetStage(StageT::MOCS_COMPILATION);
+      }
+      break;
+    case StageT::MOCS_COMPILATION:
+      if (ThreadsJobsDone()) {
+        MocGenerateCompilation();
+        SetStage(StageT::UIC_PROCESS);
+      }
+      break;
+    case StageT::UIC_PROCESS:
+      if (ThreadsStartJobs(JobQueues_.Uic)) {
+        SetStage(StageT::SETTINGS_WRITE);
+      }
+      break;
+    case StageT::SETTINGS_WRITE:
+      SettingsFileWrite();
+      SetStage(StageT::FINISH);
+      break;
+    case StageT::FINISH:
+      if (ThreadsJobsDone()) {
+        // Clear all libuv handles
+        ThreadsStop();
+        UVRequest().reset();
+        // Set highest END stage manually
+        Stage_ = StageT::END;
+      }
+      break;
+    case StageT::END:
+      break;
   }
-
-  return true;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::MocGenerateFile(const MocJobAuto& mocJob,
-                                              bool* generated)
+void cmQtAutoGeneratorMocUic::SetStage(StageT stage)
 {
-  bool success = true;
-
-  std::string const mocFileAbs = cmSystemTools::CollapseCombinedPath(
-    this->AutogenBuildDir, mocJob.BuildFileRel);
-
-  bool generate = false;
-  std::string generateReason;
-  if (!generate && !cmSystemTools::FileExists(mocFileAbs.c_str())) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(mocFileAbs);
-      generateReason += " from its source file ";
-      generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile);
-      generateReason += " because it doesn't exist";
-    }
-    generate = true;
+  if (JobError_) {
+    stage = StageT::FINISH;
   }
-  if (!generate && this->MocSettingsChanged) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(mocFileAbs);
-      generateReason += " from ";
-      generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile);
-      generateReason += " because the MOC settings changed";
-    }
-    generate = true;
+  // Only allow to increase the stage
+  if (Stage_ < stage) {
+    Stage_ = stage;
+    UVRequest().send();
   }
-  if (!generate && this->MocPredefsChanged) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(mocFileAbs);
-      generateReason += " from ";
-      generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile);
-      generateReason += " because moc_predefs.h changed";
+}
+
+void cmQtAutoGeneratorMocUic::SettingsFileRead()
+{
+  // Compose current settings strings
+  {
+    cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
+    std::string const sep(" ~~~ ");
+    if (Moc_.Enabled) {
+      std::string str;
+      str += Moc().Executable;
+      str += sep;
+      str += cmJoin(Moc().AllOptions, ";");
+      str += sep;
+      str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE";
+      str += sep;
+      str += cmJoin(Moc().PredefsCmd, ";");
+      str += sep;
+      SettingsStringMoc_ = crypt.HashString(str);
     }
-    generate = true;
-  }
-  if (!generate) {
-    std::string error;
-    if (FileIsOlderThan(mocFileAbs, mocJob.SourceFile, &error)) {
-      if (this->GetVerbose()) {
-        generateReason = "Generating ";
-        generateReason += cmQtAutoGen::Quoted(mocFileAbs);
-        generateReason += " because it's older than its source file ";
-        generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile);
-      }
-      generate = true;
-    } else {
-      if (!error.empty()) {
-        this->LogError(cmQtAutoGen::MOC, error);
-        success = false;
+    if (Uic().Enabled) {
+      std::string str;
+      str += Uic().Executable;
+      str += sep;
+      str += cmJoin(Uic().TargetOptions, ";");
+      for (const auto& item : Uic().Options) {
+        str += sep;
+        str += item.first;
+        str += sep;
+        str += cmJoin(item.second, ";");
       }
+      str += sep;
+      SettingsStringUic_ = crypt.HashString(str);
     }
   }
-  if (success && !generate) {
-    // Test if a dependency file is newer
-    std::string error;
-    for (std::string const& depFile : mocJob.Depends) {
-      if (FileIsOlderThan(mocFileAbs, depFile, &error)) {
-        if (this->GetVerbose()) {
-          generateReason = "Generating ";
-          generateReason += cmQtAutoGen::Quoted(mocFileAbs);
-          generateReason += " from ";
-          generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile);
-          generateReason += " because it is older than ";
-          generateReason += cmQtAutoGen::Quoted(depFile);
+
+  // Read old settings and compare
+  {
+    std::string content;
+    if (FileSys().FileRead(content, SettingsFile_)) {
+      if (Moc().Enabled) {
+        if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
+          Moc_.SettingsChanged = true;
         }
-        generate = true;
-        break;
       }
-      if (!error.empty()) {
-        this->LogError(cmQtAutoGen::MOC, error);
-        success = false;
-        break;
+      if (Uic().Enabled) {
+        if (SettingsStringUic_ != SettingsFind(content, "uic")) {
+          Uic_.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 (Moc().SettingsChanged || Uic().SettingsChanged) {
+        FileSys().FileRemove(SettingsFile_);
+      }
+    } else {
+      // Settings file read failed
+      if (Moc().Enabled) {
+        Moc_.SettingsChanged = true;
+      }
+      if (Uic().Enabled) {
+        Uic_.SettingsChanged = true;
       }
     }
   }
+}
 
-  if (generate) {
-    // Log
-    if (this->GetVerbose()) {
-      this->LogBold("Generating MOC source " + mocJob.BuildFileRel);
-      this->LogInfo(cmQtAutoGen::MOC, generateReason);
+void cmQtAutoGeneratorMocUic::SettingsFileWrite()
+{
+  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+  // Only write if any setting changed
+  if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) {
+    if (Log().Verbose()) {
+      Log().Info(GeneratorT::GEN,
+                 "Writing settings file " + Quoted(SettingsFile_));
     }
-
-    // Make sure the parent directory exists
-    if (this->MakeParentDirectory(cmQtAutoGen::MOC, mocFileAbs)) {
-      // Compose moc command
-      std::vector<std::string> cmd;
-      cmd.push_back(this->MocExecutable);
-      // Add options
-      cmd.insert(cmd.end(), this->MocAllOptions.begin(),
-                 this->MocAllOptions.end());
-      // Add predefs include
-      if (!this->MocPredefsFileAbs.empty()) {
-        cmd.push_back("--include");
-        cmd.push_back(this->MocPredefsFileAbs);
-      }
-      cmd.push_back("-o");
-      cmd.push_back(mocFileAbs);
-      cmd.push_back(mocJob.SourceFile);
-
-      // Execute moc command
-      std::string output;
-      if (this->RunCommand(cmd, output)) {
-        // Success
-        if (generated != nullptr) {
-          *generated = true;
-        }
-      } else {
-        // Moc command failed
-        {
-          std::string emsg = "moc failed for\n  ";
-          emsg += cmQtAutoGen::Quoted(mocJob.SourceFile);
-          this->LogCommandError(cmQtAutoGen::MOC, emsg, cmd, output);
+    // 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';
         }
-        cmSystemTools::RemoveFile(mocFileAbs);
-        success = false;
-      }
-    } else {
-      // Parent directory creation failed
-      success = false;
+      };
+      SettingAppend("moc", SettingsStringMoc_);
+      SettingAppend("uic", SettingsStringUic_);
+    }
+    // Write settings file
+    if (!FileSys().FileWrite(GeneratorT::GEN, SettingsFile_, content)) {
+      Log().ErrorFile(GeneratorT::GEN, SettingsFile_,
+                      "Settings file writing failed");
+      // Remove old settings file to trigger a full rebuild on the next run
+      FileSys().FileRemove(SettingsFile_);
+      RegisterJobError();
     }
   }
-  return success;
 }
 
-/**
- * @brief Tests if the file name is in the skip list
- */
-bool cmQtAutoGeneratorMocUic::UicSkip(std::string const& absFilename) const
+void cmQtAutoGeneratorMocUic::CreateDirectories()
 {
-  if (this->UicEnabled()) {
-    // Test if the file name is on the skip list
-    if (!ListContains(this->UicSkipList, absFilename)) {
-      return false;
-    }
+  // Create AUTOGEN include directory
+  if (!FileSys().MakeDirectory(GeneratorT::GEN, Base().AutogenIncludeDirAbs)) {
+    RegisterJobError();
   }
-  return true;
 }
 
-bool cmQtAutoGeneratorMocUic::UicParseContent(std::string const& absFilename,
-                                              std::string const& contentText)
+bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue)
 {
-  if (this->GetVerbose()) {
-    this->LogInfo(cmQtAutoGen::UIC, "Checking: " + absFilename);
-  }
+  bool done = false;
+  std::size_t queueSize = queue.size();
 
-  std::vector<std::string> includes;
-  // Extracte includes
+  // Change the active queue
   {
-    const char* contentChars = contentText.c_str();
-    if (strstr(contentChars, "ui_") != nullptr) {
-      while (this->UicRegExpInclude.find(contentChars)) {
-        includes.push_back(this->UicRegExpInclude.match(1));
-        contentChars += this->UicRegExpInclude.end();
+    std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+    // Check if there are still unfinished jobs from the previous queue
+    if (JobsRemain_ == 0) {
+      if (!JobThreadsAbort_) {
+        JobQueue_.swap(queue);
+        JobsRemain_ = queueSize;
+      } else {
+        // Abort requested
+        queue.clear();
+        queueSize = 0;
       }
+      done = true;
     }
   }
 
-  for (std::string const& includeString : includes) {
-    std::string uiInputFile;
-    if (!UicFindIncludedFile(uiInputFile, absFilename, includeString)) {
-      return false;
-    }
-    // Check if this file should be skipped
-    if (this->UicSkip(uiInputFile)) {
-      continue;
-    }
-    // Check if the job already exists
-    bool jobExists = false;
-    for (const auto& job : this->UicJobs) {
-      if ((job->SourceFile == uiInputFile) &&
-          (job->IncludeString == includeString)) {
-        jobExists = true;
-        break;
+  if (done && (queueSize != 0)) {
+    // Start new threads on demand
+    if (Workers_.empty()) {
+      Workers_.resize(Base().NumThreads);
+      for (auto& item : Workers_) {
+        item = cm::make_unique<WorkerT>(this, UVLoop());
+      }
+    } else {
+      // Notify threads
+      if (queueSize == 1) {
+        JobsConditionRead_.notify_one();
+      } else {
+        JobsConditionRead_.notify_all();
       }
-    }
-    if (!jobExists) {
-      auto job = cm::make_unique<UicJob>();
-      job->SourceFile = uiInputFile;
-      job->BuildFileRel = this->AutogenIncludeDir;
-      job->BuildFileRel += includeString;
-      job->Includer = absFilename;
-      job->IncludeString = includeString;
-      this->UicJobs.push_back(std::move(job));
     }
   }
 
-  return true;
+  return done;
 }
 
-bool cmQtAutoGeneratorMocUic::UicFindIncludedFile(
-  std::string& absFile, std::string const& sourceFile,
-  std::string const& includeString)
+void cmQtAutoGeneratorMocUic::ThreadsStop()
 {
-  bool success = false;
-  std::string searchFile =
-    cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3);
-  searchFile += ".ui";
-  // Collect search paths list
-  std::vector<std::string> testFiles;
-  {
-    std::string const searchPath = SubDirPrefix(includeString);
-
-    std::string searchFileFull;
-    if (!searchPath.empty()) {
-      searchFileFull = searchPath;
-      searchFileFull += searchFile;
-    }
-    // Vicinity of the source
+  if (!Workers_.empty()) {
+    // Clear all jobs
     {
-      std::string const sourcePath = SubDirPrefix(sourceFile);
-      testFiles.push_back(sourcePath + searchFile);
-      if (!searchPath.empty()) {
-        testFiles.push_back(sourcePath + searchFileFull);
-      }
-    }
-    // AUTOUIC search paths
-    if (!this->UicSearchPaths.empty()) {
-      for (std::string const& sPath : this->UicSearchPaths) {
-        testFiles.push_back((sPath + "/").append(searchFile));
-      }
-      if (!searchPath.empty()) {
-        for (std::string const& sPath : this->UicSearchPaths) {
-          testFiles.push_back((sPath + "/").append(searchFileFull));
-        }
-      }
-    }
+      std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+      JobThreadsAbort_ = true;
+      JobsRemain_ -= JobQueue_.size();
+      JobQueue_.clear();
+
+      JobQueues_.Sources.clear();
+      JobQueues_.Headers.clear();
+      JobQueues_.MocPredefs.clear();
+      JobQueues_.Moc.clear();
+      JobQueues_.Uic.clear();
+    }
+    // Wake threads
+    JobsConditionRead_.notify_all();
+    // Join and clear threads
+    Workers_.clear();
   }
+}
 
-  // Search for the .ui file!
-  for (std::string const& testFile : testFiles) {
-    if (cmSystemTools::FileExists(testFile.c_str())) {
-      absFile = cmSystemTools::GetRealPath(testFile);
-      success = true;
-      break;
-    }
-  }
+bool cmQtAutoGeneratorMocUic::ThreadsJobsDone()
+{
+  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+  return (JobsRemain_ == 0);
+}
 
-  // Log error
-  if (!success) {
-    std::string emsg = "Could not find ";
-    emsg += cmQtAutoGen::Quoted(searchFile);
-    emsg += " in\n";
-    for (std::string const& testFile : testFiles) {
-      emsg += "  ";
-      emsg += cmQtAutoGen::Quoted(testFile);
-      emsg += "\n";
+void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle)
+{
+  bool const jobProcessed(jobHandle);
+  if (jobProcessed) {
+    jobHandle.reset(nullptr);
+  }
+  {
+    std::unique_lock<std::mutex> jobsLock(JobsMutex_);
+    // Reduce the remaining job count and notify the libuv loop
+    // when all jobs are done
+    if (jobProcessed) {
+      --JobsRemain_;
+      if (JobsRemain_ == 0) {
+        UVRequest().send();
+      }
+    }
+    // Wait for new jobs
+    while (!JobThreadsAbort_ && JobQueue_.empty()) {
+      JobsConditionRead_.wait(jobsLock);
+    }
+    // Try to pick up a new job handle
+    if (!JobThreadsAbort_ && !JobQueue_.empty()) {
+      jobHandle = std::move(JobQueue_.front());
+      JobQueue_.pop_front();
     }
-    this->LogFileError(cmQtAutoGen::UIC, sourceFile, emsg);
   }
+}
 
-  return success;
+void cmQtAutoGeneratorMocUic::ParallelRegisterJobError()
+{
+  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+  RegisterJobError();
 }
 
-bool cmQtAutoGeneratorMocUic::UicGenerateAll()
+// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be
+// locked
+void cmQtAutoGeneratorMocUic::RegisterJobError()
 {
-  if (!this->UicEnabled()) {
-    return true;
+  JobError_ = true;
+  if (!JobThreadsAbort_) {
+    JobThreadsAbort_ = true;
+    // Clear remaining jobs
+    if (JobsRemain_ != 0) {
+      JobsRemain_ -= JobQueue_.size();
+      JobQueue_.clear();
+    }
   }
+}
 
-  // Look for name collisions in included uic files
-  {
-    bool collision = false;
-    std::map<std::string, std::vector<UicJob const*>> collisions;
-    for (auto const& job : this->UicJobs) {
-      auto& list = collisions[job->IncludeString];
-      if (!list.empty()) {
-        collision = true;
-      }
-      list.push_back(job.get());
-    }
-    if (collision) {
-      std::string emsg =
-        "Included uic files with the same name will be "
-        "generated from different sources.\n"
-        "Consider 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"
-        "Include conflicts\n"
-        "-----------------\n";
-      const auto& colls = collisions;
-      for (auto const& coll : colls) {
-        if (coll.second.size() > 1) {
-          emsg += cmQtAutoGen::Quoted(coll.first);
-          emsg += " included in\n";
-          for (const UicJob* job : coll.second) {
-            emsg += " - ";
-            emsg += cmQtAutoGen::Quoted(job->Includer);
-            emsg += "\n";
-          }
-          emsg += "would be generated from\n";
-          for (const UicJob* job : coll.second) {
-            emsg += " - ";
-            emsg += cmQtAutoGen::Quoted(job->SourceFile);
-            emsg += "\n";
+bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle)
+{
+  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+  if (!JobThreadsAbort_) {
+    bool pushJobHandle = true;
+    // Do additional tests if this is an included moc job
+    const JobMocT& mocJob(static_cast<JobMocT&>(*jobHandle));
+    if (!mocJob.IncludeString.empty()) {
+      // Register included moc file and look for collisions
+      MocIncludedFiles_.emplace(mocJob.SourceFile);
+      if (!MocIncludedStrings_.emplace(mocJob.IncludeString).second) {
+        // Another source file includes the same moc file!
+        for (const JobHandleT& otherHandle : JobQueues_.Moc) {
+          const JobMocT& otherJob(static_cast<JobMocT&>(*otherHandle));
+          if (otherJob.IncludeString == mocJob.IncludeString) {
+            // Check if the same moc file would be generated from different
+            // source files which is an error.
+            if (otherJob.SourceFile != mocJob.SourceFile) {
+              // Include string collision
+              std::string error = "The two source files\n  ";
+              error += Quoted(mocJob.IncluderFile);
+              error += " and\n  ";
+              error += Quoted(otherJob.IncluderFile);
+              error += "\ncontain the the same moc include string ";
+              error += Quoted(mocJob.IncludeString);
+              error += "\nbut the moc file would be generated from different "
+                       "source files\n  ";
+              error += Quoted(mocJob.SourceFile);
+              error += " and\n  ";
+              error += Quoted(otherJob.SourceFile);
+              error += ".\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";
+              Log().Error(GeneratorT::MOC, error);
+              RegisterJobError();
+            }
+            // Do not push this job in since the included moc file already
+            // gets generated by an other job.
+            pushJobHandle = false;
+            break;
           }
         }
       }
-      this->LogError(cmQtAutoGen::UIC, emsg);
-      return false;
+    }
+    // Push job on demand
+    if (pushJobHandle) {
+      JobQueues_.Moc.emplace_back(std::move(jobHandle));
     }
   }
+  return !JobError_;
+}
 
-  // Generate ui header files
-  for (const auto& item : this->UicJobs) {
-    if (!this->UicGenerateFile(*item)) {
-      return false;
+bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle)
+{
+  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
+  if (!JobThreadsAbort_) {
+    bool pushJobHandle = true;
+    // Look for include collisions.
+    const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle));
+    for (const JobHandleT& otherHandle : JobQueues_.Uic) {
+      const JobUicT& otherJob(static_cast<JobUicT&>(*otherHandle));
+      if (otherJob.IncludeString == uicJob.IncludeString) {
+        // Check if the same uic file would be generated from different
+        // source files which would be an error.
+        if (otherJob.SourceFile != uicJob.SourceFile) {
+          // Include string collision
+          std::string error = "The two source files\n  ";
+          error += Quoted(uicJob.IncluderFile);
+          error += " and\n  ";
+          error += Quoted(otherJob.IncluderFile);
+          error += "\ncontain the the same uic include string ";
+          error += Quoted(uicJob.IncludeString);
+          error += "\nbut the uic file would be generated from different "
+                   "source files\n  ";
+          error += Quoted(uicJob.SourceFile);
+          error += " and\n  ";
+          error += Quoted(otherJob.SourceFile);
+          error +=
+            ".\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";
+          Log().Error(GeneratorT::UIC, error);
+          RegisterJobError();
+        }
+        // Do not push this job in since the uic file already
+        // gets generated by an other job.
+        pushJobHandle = false;
+        break;
+      }
+    }
+    if (pushJobHandle) {
+      JobQueues_.Uic.emplace_back(std::move(jobHandle));
     }
   }
-
-  return true;
+  return !JobError_;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::UicGenerateFile(const UicJob& uicJob)
+bool cmQtAutoGeneratorMocUic::ParallelMocIncluded(
+  std::string const& sourceFile)
 {
-  bool success = true;
+  std::lock_guard<std::mutex> mocLock(JobsMutex_);
+  return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end());
+}
 
-  std::string const uicFileAbs = cmSystemTools::CollapseCombinedPath(
-    this->AutogenBuildDir, uicJob.BuildFileRel);
+void cmQtAutoGeneratorMocUic::ParallelMocAutoRegister(
+  std::string const& mocFile)
+{
+  std::lock_guard<std::mutex> mocLock(JobsMutex_);
+  MocAutoFiles_.emplace(mocFile);
+}
 
-  bool generate = false;
-  std::string generateReason;
-  if (!generate && !cmSystemTools::FileExists(uicFileAbs.c_str())) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(uicFileAbs);
-      generateReason += " from its source file ";
-      generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile);
-      generateReason += " because it doesn't exist";
-    }
-    generate = true;
-  }
-  if (!generate && this->UicSettingsChanged) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(uicFileAbs);
-      generateReason += " from ";
-      generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile);
-      generateReason += " because the UIC settings changed";
-    }
-    generate = true;
-  }
-  if (!generate) {
-    std::string error;
-    if (FileIsOlderThan(uicFileAbs, uicJob.SourceFile, &error)) {
-      if (this->GetVerbose()) {
-        generateReason = "Generating ";
-        generateReason += cmQtAutoGen::Quoted(uicFileAbs);
-        generateReason += " because it's older than its source file ";
-        generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile);
-      }
-      generate = true;
-    } else {
-      if (!error.empty()) {
-        this->LogError(cmQtAutoGen::UIC, error);
-        success = false;
-      }
-    }
-  }
-  if (generate) {
-    // Log
-    if (this->GetVerbose()) {
-      this->LogBold("Generating UIC header " + uicJob.BuildFileRel);
-      this->LogInfo(cmQtAutoGen::UIC, generateReason);
-    }
+void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated()
+{
+  std::lock_guard<std::mutex> mocLock(JobsMutex_);
+  MocAutoFileUpdated_ = true;
+}
 
-    // Make sure the parent directory exists
-    if (this->MakeParentDirectory(cmQtAutoGen::UIC, uicFileAbs)) {
-      // Compose uic command
-      std::vector<std::string> cmd;
-      cmd.push_back(this->UicExecutable);
-      {
-        std::vector<std::string> allOpts = this->UicTargetOptions;
-        auto optionIt = this->UicOptions.find(uicJob.SourceFile);
-        if (optionIt != this->UicOptions.end()) {
-          cmQtAutoGen::UicMergeOptions(allOpts, optionIt->second,
-                                       (this->QtVersionMajor == 5));
+void cmQtAutoGeneratorMocUic::MocGenerateCompilation()
+{
+  std::lock_guard<std::mutex> mocLock(JobsMutex_);
+  if (!JobThreadsAbort_ && Moc().Enabled) {
+    // Compose mocs compilation file content
+    {
+      std::string content =
+        "// This file is autogenerated. Changes will be overwritten.\n";
+      if (MocAutoFiles_.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
+        for (std::string const& mocfile : MocAutoFiles_) {
+          content += "#include \"";
+          content += mocfile;
+          content += "\"\n";
         }
-        cmd.insert(cmd.end(), allOpts.begin(), allOpts.end());
       }
-      cmd.push_back("-o");
-      cmd.push_back(uicFileAbs);
-      cmd.push_back(uicJob.SourceFile);
 
-      std::string output;
-      if (this->RunCommand(cmd, output)) {
-        // Success
-      } else {
-        // Command failed
-        {
-          std::string emsg = "uic failed for\n  ";
-          emsg += cmQtAutoGen::Quoted(uicJob.SourceFile);
-          emsg += "\nincluded by\n  ";
-          emsg += cmQtAutoGen::Quoted(uicJob.Includer);
-          this->LogCommandError(cmQtAutoGen::UIC, emsg, cmd, output);
+      std::string const& compRel = Moc().CompFileRel;
+      std::string const& compAbs = Moc().CompFileAbs;
+      if (FileSys().FileDiffers(compAbs, content)) {
+        // Actually write mocs compilation file
+        if (Log().Verbose()) {
+          Log().Info(GeneratorT::MOC, "Generating MOC compilation " + compRel);
         }
-        cmSystemTools::RemoveFile(uicFileAbs);
-        success = false;
+        if (!FileSys().FileWrite(GeneratorT::MOC, compAbs, content)) {
+          Log().ErrorFile(GeneratorT::MOC, compAbs,
+                          "mocs compilation file writing failed");
+          RegisterJobError();
+          return;
+        }
+      } else if (MocAutoFileUpdated_) {
+        // Only touch mocs compilation file
+        if (Log().Verbose()) {
+          Log().Info(GeneratorT::MOC, "Touching mocs compilation " + compRel);
+        }
+        FileSys().Touch(compAbs);
       }
-    } else {
-      // Parent directory creation failed
-      success = false;
-    }
-  }
-  return success;
-}
-
-/**
- * @brief Tries to find the header file to the given file base path by
- * appending different header extensions
- * @return True on success
- */
-bool cmQtAutoGeneratorMocUic::FindHeader(std::string& header,
-                                         std::string const& testBasePath) const
-{
-  for (std::string const& ext : this->HeaderExtensions) {
-    std::string testFilePath(testBasePath);
-    testFilePath.push_back('.');
-    testFilePath += ext;
-    if (cmSystemTools::FileExists(testFilePath.c_str())) {
-      header = testFilePath;
-      return true;
     }
   }
-  return false;
 }
diff --git a/Source/cmQtAutoGeneratorMocUic.h b/Source/cmQtAutoGeneratorMocUic.h
index d510939..215e25a 100644
--- a/Source/cmQtAutoGeneratorMocUic.h
+++ b/Source/cmQtAutoGeneratorMocUic.h
@@ -8,188 +8,434 @@
 #include "cmFilePathChecksum.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenerator.h"
+#include "cmUVHandlePtr.h"
+#include "cm_uv.h"
 #include "cmsys/RegularExpression.hxx"
 
+#include <algorithm>
+#include <condition_variable>
+#include <cstddef>
+#include <deque>
 #include <map>
 #include <memory> // IWYU pragma: keep
+#include <mutex>
 #include <set>
 #include <string>
+#include <thread>
 #include <vector>
 
 class cmMakefile;
 
+// @brief AUTOMOC and AUTOUIC generator
 class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator
 {
   CM_DISABLE_COPY(cmQtAutoGeneratorMocUic)
 public:
   cmQtAutoGeneratorMocUic();
+  ~cmQtAutoGeneratorMocUic() override;
 
-private:
+public:
   // -- Types
+  class WorkerT;
 
   /// @brief Search key plus regular expression pair
-  struct KeyRegExp
+  ///
+  struct KeyExpT
   {
-    KeyRegExp() = default;
+    KeyExpT() = default;
 
-    KeyRegExp(const char* key, const char* regExp)
+    KeyExpT(const char* key, const char* exp)
       : Key(key)
-      , RegExp(regExp)
+      , Exp(exp)
     {
     }
 
-    KeyRegExp(std::string const& key, std::string const& regExp)
+    KeyExpT(std::string const& key, std::string const& exp)
       : Key(key)
-      , RegExp(regExp)
+      , Exp(exp)
     {
     }
 
     std::string Key;
-    cmsys::RegularExpression RegExp;
+    cmsys::RegularExpression Exp;
   };
 
-  /// @brief Source file job
-  struct SourceJob
+  /// @brief Common settings
+  ///
+  class BaseSettingsT
   {
-    bool Moc = false;
-    bool Uic = false;
+    CM_DISABLE_COPY(BaseSettingsT)
+  public:
+    // -- Volatile methods
+    BaseSettingsT(FileSystem* fileSystem)
+      : MultiConfig(MultiConfigT::WRAPPER)
+      , IncludeProjectDirsBefore(false)
+      , QtVersionMajor(4)
+      , NumThreads(1)
+      , FileSys(fileSystem)
+    {
+    }
+
+    // -- Const methods
+    std::string AbsoluteBuildPath(std::string const& relativePath) const;
+    bool FindHeader(std::string& header,
+                    std::string const& testBasePath) const;
+
+    // -- Attributes
+    // - Config
+    std::string ConfigSuffix;
+    MultiConfigT MultiConfig;
+    bool IncludeProjectDirsBefore;
+    unsigned int QtVersionMajor;
+    unsigned int NumThreads;
+    // - Directories
+    std::string ProjectSourceDir;
+    std::string ProjectBinaryDir;
+    std::string CurrentSourceDir;
+    std::string CurrentBinaryDir;
+    std::string AutogenBuildDir;
+    std::string AutogenIncludeDirRel;
+    std::string AutogenIncludeDirAbs;
+    // - Files
+    cmFilePathChecksum FilePathChecksum;
+    std::vector<std::string> HeaderExtensions;
+    // - File system
+    FileSystem* FileSys;
   };
 
-  /// @brief MOC job
-  struct MocJobAuto
+  /// @brief Moc settings
+  ///
+  class MocSettingsT
   {
-    std::string SourceFile;
-    std::string BuildFileRel;
-    std::set<std::string> Depends;
+    CM_DISABLE_COPY(MocSettingsT)
+  public:
+    MocSettingsT(FileSystem* fileSys)
+      : FileSys(fileSys)
+    {
+    }
+
+    // -- Const methods
+    bool skipped(std::string const& fileName) const;
+    std::string FindMacro(std::string const& content) const;
+    std::string MacrosString() const;
+    std::string FindIncludedFile(std::string const& sourcePath,
+                                 std::string const& includeString) const;
+    void FindDependencies(std::string const& content,
+                          std::set<std::string>& depends) const;
+
+    // -- Attributes
+    bool Enabled = false;
+    bool SettingsChanged = false;
+    bool RelaxedMode = false;
+    std::string Executable;
+    std::string CompFileRel;
+    std::string CompFileAbs;
+    std::string PredefsFileRel;
+    std::string PredefsFileAbs;
+    std::set<std::string> SkipList;
+    std::vector<std::string> IncludePaths;
+    std::vector<std::string> Includes;
+    std::vector<std::string> Definitions;
+    std::vector<std::string> Options;
+    std::vector<std::string> AllOptions;
+    std::vector<std::string> PredefsCmd;
+    std::vector<KeyExpT> DependFilters;
+    std::vector<KeyExpT> MacroFilters;
+    cmsys::RegularExpression RegExpInclude;
+    // - File system
+    FileSystem* FileSys;
   };
 
-  /// @brief MOC job
-  struct MocJobIncluded : MocJobAuto
+  /// @brief Uic settings
+  ///
+  class UicSettingsT
   {
-    bool DependsValid = false;
-    std::string Includer;
+    CM_DISABLE_COPY(UicSettingsT)
+  public:
+    UicSettingsT() = default;
+    // -- Const methods
+    bool skipped(std::string const& fileName) const;
+
+    // -- Attributes
+    bool Enabled = false;
+    bool SettingsChanged = false;
+    std::string Executable;
+    std::set<std::string> SkipList;
+    std::vector<std::string> TargetOptions;
+    std::map<std::string, std::vector<std::string>> Options;
+    std::vector<std::string> SearchPaths;
+    cmsys::RegularExpression RegExpInclude;
+  };
+
+  /// @brief Abstract job class for threaded processing
+  ///
+  class JobT
+  {
+    CM_DISABLE_COPY(JobT)
+  public:
+    JobT() = default;
+    virtual ~JobT() = default;
+    // -- Abstract processing interface
+    virtual void Process(WorkerT& wrk) = 0;
+  };
+
+  /// @brief Deleter for classes derived from Job
+  ///
+  struct JobDeleterT
+  {
+    void operator()(JobT* job);
+  };
+
+  // Job management types
+  typedef std::unique_ptr<JobT, JobDeleterT> JobHandleT;
+  typedef std::deque<JobHandleT> JobQueueT;
+
+  /// @brief Parse source job
+  ///
+  class JobParseT : public JobT
+  {
+  public:
+    JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false)
+      : FileName(std::move(fileName))
+      , AutoMoc(moc)
+      , AutoUic(uic)
+      , Header(header)
+    {
+    }
+
+  private:
+    struct MetaT
+    {
+      std::string Content;
+      std::string FileDir;
+      std::string FileBase;
+    };
+
+    void Process(WorkerT& wrk) override;
+    bool ParseMocSource(WorkerT& wrk, MetaT const& meta);
+    bool ParseMocHeader(WorkerT& wrk, MetaT const& meta);
+    std::string MocStringHeaders(WorkerT& wrk,
+                                 std::string const& fileBase) const;
+    std::string MocFindIncludedHeader(WorkerT& wrk,
+                                      std::string const& includerDir,
+                                      std::string const& includeBase);
+    bool ParseUic(WorkerT& wrk, MetaT const& meta);
+    bool ParseUicInclude(WorkerT& wrk, MetaT const& meta,
+                         std::string&& includeString);
+    std::string UicFindIncludedFile(WorkerT& wrk, MetaT const& meta,
+                                    std::string const& includeString);
+
+  private:
+    std::string FileName;
+    bool AutoMoc = false;
+    bool AutoUic = false;
+    bool Header = false;
+  };
+
+  /// @brief Generate moc_predefs
+  ///
+  class JobMocPredefsT : public JobT
+  {
+  private:
+    void Process(WorkerT& wrk) override;
+  };
+
+  /// @brief Moc a file job
+  ///
+  class JobMocT : public JobT
+  {
+  public:
+    JobMocT(std::string&& sourceFile, std::string const& includerFile,
+            std::string&& includeString)
+      : SourceFile(std::move(sourceFile))
+      , IncluderFile(includerFile)
+      , IncludeString(std::move(includeString))
+    {
+    }
+
+    void FindDependencies(WorkerT& wrk, std::string const& content);
+
+  private:
+    void Process(WorkerT& wrk) override;
+    bool UpdateRequired(WorkerT& wrk);
+    void GenerateMoc(WorkerT& wrk);
+
+  public:
+    std::string SourceFile;
+    std::string IncluderFile;
     std::string IncludeString;
+    std::string BuildFile;
+    bool DependsValid = false;
+    std::set<std::string> Depends;
   };
 
-  /// @brief UIC job
-  struct UicJob
+  /// @brief Uic a file job
+  ///
+  class JobUicT : public JobT
   {
+  public:
+    JobUicT(std::string&& sourceFile, std::string const& includerFile,
+            std::string&& includeString)
+      : SourceFile(std::move(sourceFile))
+      , IncluderFile(includerFile)
+      , IncludeString(std::move(includeString))
+    {
+    }
+
+  private:
+    void Process(WorkerT& wrk) override;
+    bool UpdateRequired(WorkerT& wrk);
+    void GenerateUic(WorkerT& wrk);
+
+  public:
     std::string SourceFile;
-    std::string BuildFileRel;
-    std::string Includer;
+    std::string IncluderFile;
     std::string IncludeString;
+    std::string BuildFile;
   };
 
-  // -- Initialization
-  bool InitInfoFile(cmMakefile* makefile);
+  /// @brief Worker Thread
+  ///
+  class WorkerT
+  {
+    CM_DISABLE_COPY(WorkerT)
+  public:
+    WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop);
+    ~WorkerT();
+
+    // -- Const accessors
+    cmQtAutoGeneratorMocUic& Gen() const { return *Gen_; }
+    Logger& Log() const { return Gen_->Log(); }
+    FileSystem& FileSys() const { return Gen_->FileSys(); }
+    const BaseSettingsT& Base() const { return Gen_->Base(); }
+    const MocSettingsT& Moc() const { return Gen_->Moc(); }
+    const UicSettingsT& Uic() const { return Gen_->Uic(); }
 
-  // -- Settings file
-  void SettingsFileRead(cmMakefile* makefile);
-  bool SettingsFileWrite();
-  bool SettingsChanged() const
+    // -- Log info
+    void LogInfo(GeneratorT genType, std::string const& message) const;
+    // -- Log warning
+    void LogWarning(GeneratorT genType, std::string const& message) const;
+    void LogFileWarning(GeneratorT genType, std::string const& filename,
+                        std::string const& message) const;
+    // -- Log error
+    void LogError(GeneratorT genType, std::string const& message) const;
+    void LogFileError(GeneratorT genType, std::string const& filename,
+                      std::string const& message) const;
+    void LogCommandError(GeneratorT genType, std::string const& message,
+                         std::vector<std::string> const& command,
+                         std::string const& output) const;
+
+    // -- External processes
+    /// @brief Verbose logging version
+    bool RunProcess(GeneratorT genType, ProcessResultT& result,
+                    std::vector<std::string> const& command);
+
+  private:
+    /// @brief Thread main loop
+    void Loop();
+
+    // -- Libuv callbacks
+    static void UVProcessStart(uv_async_t* handle);
+    void UVProcessFinished();
+
+  private:
+    // -- Generator
+    cmQtAutoGeneratorMocUic* Gen_;
+    // -- Job handle
+    JobHandleT JobHandle_;
+    // -- Process management
+    std::mutex ProcessMutex_;
+    cm::uv_async_ptr ProcessRequest_;
+    std::condition_variable ProcessCondition_;
+    std::unique_ptr<ReadOnlyProcessT> Process_;
+    // -- System thread
+    std::thread Thread_;
+  };
+
+  /// @brief Processing stage
+  enum class StageT
   {
-    return (this->MocSettingsChanged || this->UicSettingsChanged);
-  }
-
-  // -- Central processing
-  bool Process(cmMakefile* makefile) override;
-
-  // -- Source parsing
-  bool ParseSourceFile(std::string const& absFilename, const SourceJob& job);
-  bool ParseHeaderFile(std::string const& absFilename, const SourceJob& job);
-  bool ParsePostprocess();
-
-  // -- Moc
-  bool MocEnabled() const { return !this->MocExecutable.empty(); }
-  bool MocSkip(std::string const& absFilename) const;
-  bool MocRequired(std::string const& contentText,
-                   std::string* macroName = nullptr);
-  // Moc strings
-  std::string MocStringMacros() const;
-  std::string MocStringHeaders(std::string const& fileBase) const;
-  std::string MocFindIncludedHeader(std::string const& sourcePath,
-                                    std::string const& includeBase) const;
-  bool MocFindIncludedFile(std::string& absFile, std::string const& sourceFile,
-                           std::string const& includeString) const;
-  // Moc depends
-  bool MocDependFilterPush(std::string const& key, std::string const& regExp);
-  void MocFindDepends(std::string const& absFilename,
-                      std::string const& contentText,
-                      std::set<std::string>& depends);
-  // Moc
-  bool MocParseSourceContent(std::string const& absFilename,
-                             std::string const& contentText);
-  void MocParseHeaderContent(std::string const& absFilename,
-                             std::string const& contentText);
-
-  bool MocGenerateAll();
-  bool MocGenerateFile(const MocJobAuto& mocJob, bool* generated = nullptr);
-
-  // -- Uic
-  bool UicEnabled() const { return !this->UicExecutable.empty(); }
-  bool UicSkip(std::string const& absFilename) const;
-  bool UicParseContent(std::string const& fileName,
-                       std::string const& contentText);
-  bool UicFindIncludedFile(std::string& absFile, std::string const& sourceFile,
-                           std::string const& includeString);
-  bool UicGenerateAll();
-  bool UicGenerateFile(const UicJob& uicJob);
-
-  // -- Utility
-  bool FindHeader(std::string& header, std::string const& testBasePath) const;
-
-  // -- Meta
-  std::string ConfigSuffix;
-  cmQtAutoGen::MultiConfig MultiConfig;
+    SETTINGS_READ,
+    CREATE_DIRECTORIES,
+    PARSE_SOURCES,
+    PARSE_HEADERS,
+    MOC_PREDEFS,
+    MOC_PROCESS,
+    MOCS_COMPILATION,
+    UIC_PROCESS,
+    SETTINGS_WRITE,
+    FINISH,
+    END
+  };
+
+  // -- Const settings interface
+  const BaseSettingsT& Base() const { return this->Base_; }
+  const MocSettingsT& Moc() const { return this->Moc_; }
+  const UicSettingsT& Uic() const { return this->Uic_; }
+
+  // -- Worker thread interface
+  void WorkerSwapJob(JobHandleT& jobHandle);
+  // -- Parallel job processing interface
+  void ParallelRegisterJobError();
+  bool ParallelJobPushMoc(JobHandleT& jobHandle);
+  bool ParallelJobPushUic(JobHandleT& jobHandle);
+  bool ParallelMocIncluded(std::string const& sourceFile);
+  void ParallelMocAutoRegister(std::string const& mocFile);
+  void ParallelMocAutoUpdated();
+
+private:
+  // -- Abstract processing interface
+  bool Init(cmMakefile* makefile) override;
+  bool Process() override;
+  // -- Process stage
+  static void UVPollStage(uv_async_t* handle);
+  void PollStage();
+  void SetStage(StageT stage);
+  // -- Settings file
+  void SettingsFileRead();
+  void SettingsFileWrite();
+  // -- Thread processing
+  bool ThreadsStartJobs(JobQueueT& queue);
+  bool ThreadsJobsDone();
+  void ThreadsStop();
+  void RegisterJobError();
+  // -- Generation
+  void CreateDirectories();
+  void MocGenerateCompilation();
+
+private:
   // -- Settings
-  bool IncludeProjectDirsBefore;
-  std::string SettingsFile;
-  std::string SettingsStringMoc;
-  std::string SettingsStringUic;
-  // -- Directories
-  std::string ProjectSourceDir;
-  std::string ProjectBinaryDir;
-  std::string CurrentSourceDir;
-  std::string CurrentBinaryDir;
-  std::string AutogenBuildDir;
-  std::string AutogenIncludeDir;
-  // -- Qt environment
-  unsigned long QtVersionMajor;
-  std::string MocExecutable;
-  std::string UicExecutable;
-  // -- File lists
-  std::map<std::string, SourceJob> HeaderJobs;
-  std::map<std::string, SourceJob> SourceJobs;
-  std::vector<std::string> HeaderExtensions;
-  cmFilePathChecksum FilePathChecksum;
-  // -- Moc
-  bool MocSettingsChanged;
-  bool MocPredefsChanged;
-  bool MocRelaxedMode;
-  std::string MocCompFileRel;
-  std::string MocCompFileAbs;
-  std::string MocPredefsFileRel;
-  std::string MocPredefsFileAbs;
-  std::vector<std::string> MocSkipList;
-  std::vector<std::string> MocIncludePaths;
-  std::vector<std::string> MocIncludes;
-  std::vector<std::string> MocDefinitions;
-  std::vector<std::string> MocOptions;
-  std::vector<std::string> MocAllOptions;
-  std::vector<std::string> MocPredefsCmd;
-  std::vector<KeyRegExp> MocDependFilters;
-  std::vector<KeyRegExp> MocMacroFilters;
-  cmsys::RegularExpression MocRegExpInclude;
-  std::vector<std::unique_ptr<MocJobIncluded>> MocJobsIncluded;
-  std::vector<std::unique_ptr<MocJobAuto>> MocJobsAuto;
-  // -- Uic
-  bool UicSettingsChanged;
-  std::vector<std::string> UicSkipList;
-  std::vector<std::string> UicTargetOptions;
-  std::map<std::string, std::vector<std::string>> UicOptions;
-  std::vector<std::string> UicSearchPaths;
-  cmsys::RegularExpression UicRegExpInclude;
-  std::vector<std::unique_ptr<UicJob>> UicJobs;
+  BaseSettingsT Base_;
+  MocSettingsT Moc_;
+  UicSettingsT Uic_;
+  // -- Progress
+  StageT Stage_;
+  // -- Job queues
+  std::mutex JobsMutex_;
+  struct
+  {
+    JobQueueT Sources;
+    JobQueueT Headers;
+    JobQueueT MocPredefs;
+    JobQueueT Moc;
+    JobQueueT Uic;
+  } JobQueues_;
+  JobQueueT JobQueue_;
+  std::size_t volatile JobsRemain_;
+  bool volatile JobError_;
+  bool volatile JobThreadsAbort_;
+  std::condition_variable JobsConditionRead_;
+  // -- Moc meta
+  std::set<std::string> MocIncludedStrings_;
+  std::set<std::string> MocIncludedFiles_;
+  std::set<std::string> MocAutoFiles_;
+  bool volatile MocAutoFileUpdated_;
+  // -- Settings file
+  std::string SettingsFile_;
+  std::string SettingsStringMoc_;
+  std::string SettingsStringUic_;
+  // -- Threads and loops
+  std::vector<std::unique_ptr<WorkerT>> Workers_;
 };
 
 #endif
diff --git a/Source/cmQtAutoGeneratorRcc.cxx b/Source/cmQtAutoGeneratorRcc.cxx
index 3c9f1a8..e8ff75a 100644
--- a/Source/cmQtAutoGeneratorRcc.cxx
+++ b/Source/cmQtAutoGeneratorRcc.cxx
@@ -6,22 +6,30 @@
 #include "cmAlgorithms.h"
 #include "cmCryptoHash.h"
 #include "cmMakefile.h"
-#include "cmOutputConverter.h"
 #include "cmSystemTools.h"
+#include "cmUVHandlePtr.h"
 
-// -- Static variables
-
-static const char* SettingsKeyRcc = "ARCC_SETTINGS_HASH";
+#include <functional>
 
 // -- Class methods
 
 cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
-  : MultiConfig(cmQtAutoGen::WRAP)
-  , SettingsChanged(false)
+  : SettingsChanged_(false)
+  , MultiConfig_(MultiConfigT::WRAPPER)
+  , Stage_(StageT::SETTINGS_READ)
+  , Error_(false)
+  , Generate_(false)
+  , BuildFileChanged_(false)
 {
+  // Initialize libuv asynchronous iteration request
+  UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this);
 }
 
-bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile)
+cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc()
+{
+}
+
+bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile)
 {
   // Utility lambdas
   auto InfoGet = [makefile](const char* key) {
@@ -37,7 +45,7 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile)
     {
       std::string keyConf = key;
       keyConf += '_';
-      keyConf += this->GetInfoConfig();
+      keyConf += InfoConfig();
       valueConf = makefile->GetDefinition(keyConf);
     }
     if (valueConf == nullptr) {
@@ -53,79 +61,180 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile)
   };
 
   // -- Read info file
-  if (!makefile->ReadListFile(this->GetInfoFile().c_str())) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "File processing failed");
+  if (!makefile->ReadListFile(InfoFile().c_str())) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed");
     return false;
   }
 
   // -- Meta
-  this->MultiConfig =
-    cmQtAutoGen::MultiConfigType(InfoGet("ARCC_MULTI_CONFIG"));
-  this->ConfigSuffix = InfoGetConfig("ARCC_CONFIG_SUFFIX");
-  if (this->ConfigSuffix.empty()) {
-    this->ConfigSuffix = "_";
-    this->ConfigSuffix += this->GetInfoConfig();
+  MultiConfig_ = MultiConfigType(InfoGet("ARCC_MULTI_CONFIG"));
+  ConfigSuffix_ = InfoGetConfig("ARCC_CONFIG_SUFFIX");
+  if (ConfigSuffix_.empty()) {
+    ConfigSuffix_ = "_";
+    ConfigSuffix_ += InfoConfig();
   }
 
-  this->SettingsFile = InfoGetConfig("ARCC_SETTINGS_FILE");
+  SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
 
   // - Files and directories
-  this->ProjectSourceDir = InfoGet("ARCC_CMAKE_SOURCE_DIR");
-  this->ProjectBinaryDir = InfoGet("ARCC_CMAKE_BINARY_DIR");
-  this->CurrentSourceDir = InfoGet("ARCC_CMAKE_CURRENT_SOURCE_DIR");
-  this->CurrentBinaryDir = InfoGet("ARCC_CMAKE_CURRENT_BINARY_DIR");
-  this->AutogenBuildDir = InfoGet("ARCC_BUILD_DIR");
+  AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
 
   // - Qt environment
-  this->RccExecutable = InfoGet("ARCC_RCC_EXECUTABLE");
-  this->RccListOptions = InfoGetList("ARCC_RCC_LIST_OPTIONS");
+  RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
+  RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
 
   // - Job
-  this->QrcFile = InfoGet("ARCC_SOURCE");
-  this->RccFile = InfoGet("ARCC_OUTPUT");
-  this->Options = InfoGetConfigList("ARCC_OPTIONS");
-  this->Inputs = InfoGetList("ARCC_INPUTS");
+  QrcFile_ = InfoGet("ARCC_SOURCE");
+  QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
+  QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
+  RccFile_ = InfoGet("ARCC_OUTPUT");
+  Options_ = InfoGetConfigList("ARCC_OPTIONS");
+  Inputs_ = InfoGetList("ARCC_INPUTS");
 
   // - Validity checks
-  if (this->SettingsFile.empty()) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "Settings file name missing");
+  if (SettingsFile_.empty()) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing");
     return false;
   }
-  if (this->AutogenBuildDir.empty()) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "Autogen build directory missing");
+  if (AutogenBuildDir_.empty()) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(),
+                    "Autogen build directory missing");
     return false;
   }
-  if (this->RccExecutable.empty()) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "rcc executable missing");
+  if (RccExecutable_.empty()) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing");
     return false;
   }
-  if (this->QrcFile.empty()) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "rcc input file missing");
+  if (QrcFile_.empty()) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing");
     return false;
   }
-  if (this->RccFile.empty()) {
-    this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
-                       "rcc output file missing");
+  if (RccFile_.empty()) {
+    Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing");
     return false;
   }
 
   // Init derived information
   // ------------------------
 
-  // Init file path checksum generator
-  this->FilePathChecksum.setupParentDirs(
-    this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir,
-    this->ProjectBinaryDir);
+  // Compute rcc output file name
+  {
+    std::string suffix;
+    switch (MultiConfig_) {
+      case MultiConfigT::SINGLE:
+        break;
+      case MultiConfigT::WRAPPER:
+        suffix = "_CMAKE";
+        suffix += ConfigSuffix_;
+        suffix += "_";
+        break;
+      case MultiConfigT::MULTI:
+        suffix = ConfigSuffix_;
+        break;
+    }
+    RccFileBuild_ = AppendFilenameSuffix(RccFile_, suffix);
+  }
+
+  return true;
+}
 
+bool cmQtAutoGeneratorRcc::Process()
+{
+  // Run libuv event loop
+  UVRequest().send();
+  if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
+    if (Error_) {
+      return false;
+    }
+  } else {
+    return false;
+  }
   return true;
 }
 
-void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile)
+void cmQtAutoGeneratorRcc::UVPollStage(uv_async_t* handle)
+{
+  reinterpret_cast<cmQtAutoGeneratorRcc*>(handle->data)->PollStage();
+}
+
+void cmQtAutoGeneratorRcc::PollStage()
+{
+  switch (Stage_) {
+    // -- Initialize
+    case StageT::SETTINGS_READ:
+      SettingsFileRead();
+      SetStage(StageT::TEST_QRC_RCC_FILES);
+      break;
+
+    // -- Change detection
+    case StageT::TEST_QRC_RCC_FILES:
+      if (TestQrcRccFiles()) {
+        SetStage(StageT::GENERATE);
+      } else {
+        SetStage(StageT::TEST_RESOURCES_READ);
+      }
+      break;
+    case StageT::TEST_RESOURCES_READ:
+      if (TestResourcesRead()) {
+        SetStage(StageT::TEST_RESOURCES);
+      }
+      break;
+    case StageT::TEST_RESOURCES:
+      if (TestResources()) {
+        SetStage(StageT::GENERATE);
+      } else {
+        SetStage(StageT::TEST_INFO_FILE);
+      }
+      break;
+    case StageT::TEST_INFO_FILE:
+      TestInfoFile();
+      SetStage(StageT::GENERATE_WRAPPER);
+      break;
+
+    // -- Generation
+    case StageT::GENERATE:
+      GenerateParentDir();
+      SetStage(StageT::GENERATE_RCC);
+      break;
+    case StageT::GENERATE_RCC:
+      if (GenerateRcc()) {
+        SetStage(StageT::GENERATE_WRAPPER);
+      }
+      break;
+    case StageT::GENERATE_WRAPPER:
+      GenerateWrapper();
+      SetStage(StageT::SETTINGS_WRITE);
+      break;
+
+    // -- Finalize
+    case StageT::SETTINGS_WRITE:
+      SettingsFileWrite();
+      SetStage(StageT::FINISH);
+      break;
+    case StageT::FINISH:
+      // Clear all libuv handles
+      UVRequest().reset();
+      // Set highest END stage manually
+      Stage_ = StageT::END;
+      break;
+    case StageT::END:
+      break;
+  }
+}
+
+void cmQtAutoGeneratorRcc::SetStage(StageT stage)
+{
+  if (Error_) {
+    stage = StageT::FINISH;
+  }
+  // Only allow to increase the stage
+  if (Stage_ < stage) {
+    Stage_ = stage;
+    UVRequest().send();
+  }
+}
+
+void cmQtAutoGeneratorRcc::SettingsFileRead()
 {
   // Compose current settings strings
   {
@@ -133,293 +242,375 @@ void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile)
     std::string const sep(" ~~~ ");
     {
       std::string str;
-      str += this->RccExecutable;
+      str += RccExecutable_;
       str += sep;
-      str += cmJoin(this->RccListOptions, ";");
+      str += cmJoin(RccListOptions_, ";");
       str += sep;
-      str += this->QrcFile;
+      str += QrcFile_;
       str += sep;
-      str += this->RccFile;
+      str += RccFile_;
       str += sep;
-      str += cmJoin(this->Options, ";");
+      str += cmJoin(Options_, ";");
       str += sep;
-      str += cmJoin(this->Inputs, ";");
+      str += cmJoin(Inputs_, ";");
       str += sep;
-      this->SettingsString = crypt.HashString(str);
+      SettingsString_ = crypt.HashString(str);
     }
   }
 
   // Read old settings
-  if (makefile->ReadListFile(this->SettingsFile.c_str())) {
-    {
-      auto SMatch = [makefile](const char* key, std::string const& value) {
-        return (value == makefile->GetSafeDefinition(key));
-      };
-      if (!SMatch(SettingsKeyRcc, this->SettingsString)) {
-        this->SettingsChanged = true;
+  {
+    std::string content;
+    if (FileSys().FileRead(content, SettingsFile_)) {
+      SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
+      // 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 (SettingsChanged_) {
+        FileSys().FileRemove(SettingsFile_);
       }
+    } else {
+      SettingsChanged_ = true;
     }
-    // In case any setting changed remove the old settings file.
-    // This triggers a full rebuild on the next run if the current
-    // build is aborted before writing the current settings in the end.
-    if (this->SettingsChanged) {
-      cmSystemTools::RemoveFile(this->SettingsFile);
-    }
-  } else {
-    // If the file could not be read re-generate everythiung.
-    this->SettingsChanged = true;
   }
 }
 
-bool cmQtAutoGeneratorRcc::SettingsFileWrite()
+void cmQtAutoGeneratorRcc::SettingsFileWrite()
 {
-  bool success = true;
   // Only write if any setting changed
-  if (this->SettingsChanged) {
-    if (this->GetVerbose()) {
-      this->LogInfo(cmQtAutoGen::RCC, "Writing settings file " +
-                      cmQtAutoGen::Quoted(this->SettingsFile));
-    }
-    // Compose settings file content
-    std::string settings;
-    {
-      auto SettingAppend = [&settings](const char* key,
-                                       std::string const& value) {
-        settings += "set(";
-        settings += key;
-        settings += " ";
-        settings += cmOutputConverter::EscapeForCMake(value);
-        settings += ")\n";
-      };
-      SettingAppend(SettingsKeyRcc, this->SettingsString);
+  if (SettingsChanged_) {
+    if (Log().Verbose()) {
+      Log().Info(GeneratorT::RCC,
+                 "Writing settings file " + Quoted(SettingsFile_));
     }
     // Write settings file
-    if (!this->FileWrite(cmQtAutoGen::RCC, this->SettingsFile, settings)) {
-      this->LogFileError(cmQtAutoGen::RCC, this->SettingsFile,
-                         "Settings file writing failed");
+    std::string content = "rcc:";
+    content += SettingsString_;
+    content += '\n';
+    if (!FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, content)) {
+      Log().ErrorFile(GeneratorT::RCC, SettingsFile_,
+                      "Settings file writing failed");
       // Remove old settings file to trigger a full rebuild on the next run
-      cmSystemTools::RemoveFile(this->SettingsFile);
-      success = false;
+      FileSys().FileRemove(SettingsFile_);
+      Error_ = true;
     }
   }
-  return success;
 }
 
-bool cmQtAutoGeneratorRcc::Process(cmMakefile* makefile)
+bool cmQtAutoGeneratorRcc::TestQrcRccFiles()
 {
-  // Read info file
-  if (!this->InfoFileRead(makefile)) {
-    return false;
+  // Do basic checks if rcc generation is required
+
+  // Test if the rcc output file exists
+  if (!FileSys().FileExists(RccFileBuild_)) {
+    if (Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(RccFileBuild_);
+      reason += " from its source file ";
+      reason += Quoted(QrcFile_);
+      reason += " because it doesn't exist";
+      Log().Info(GeneratorT::RCC, reason);
+    }
+    Generate_ = true;
+    return Generate_;
   }
-  // Read latest settings
-  this->SettingsFileRead(makefile);
-  // Generate rcc file
-  if (!this->RccGenerate()) {
-    return false;
+
+  // Test if the settings changed
+  if (SettingsChanged_) {
+    if (Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(RccFileBuild_);
+      reason += " from ";
+      reason += Quoted(QrcFile_);
+      reason += " because the RCC settings changed";
+      Log().Info(GeneratorT::RCC, reason);
+    }
+    Generate_ = true;
+    return Generate_;
   }
-  // Write latest settings
-  if (!this->SettingsFileWrite()) {
-    return false;
+
+  // Test if the rcc output file is older than the .qrc file
+  {
+    bool isOlder = false;
+    {
+      std::string error;
+      isOlder = FileSys().FileIsOlderThan(RccFileBuild_, QrcFile_, &error);
+      if (!error.empty()) {
+        Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
+        Error_ = true;
+      }
+    }
+    if (isOlder) {
+      if (Log().Verbose()) {
+        std::string reason = "Generating ";
+        reason += Quoted(RccFileBuild_);
+        reason += " because it is older than ";
+        reason += Quoted(QrcFile_);
+        Log().Info(GeneratorT::RCC, reason);
+      }
+      Generate_ = true;
+    }
   }
-  return true;
+
+  return Generate_;
 }
 
-/**
- * @return True on success
- */
-bool cmQtAutoGeneratorRcc::RccGenerate()
+bool cmQtAutoGeneratorRcc::TestResourcesRead()
 {
-  bool success = true;
-  bool rccGenerated = false;
-
-  std::string rccFileAbs;
-  {
-    std::string suffix;
-    switch (this->MultiConfig) {
-      case cmQtAutoGen::SINGLE:
-        break;
-      case cmQtAutoGen::WRAP:
-        suffix = "_CMAKE";
-        suffix += this->ConfigSuffix;
-        suffix += "_";
-        break;
-      case cmQtAutoGen::FULL:
-        suffix = this->ConfigSuffix;
-        break;
-    }
-    rccFileAbs = cmQtAutoGen::AppendFilenameSuffix(this->RccFile, suffix);
+  if (!Inputs_.empty()) {
+    // Inputs are known already
+    return true;
   }
-  std::string const rccFileRel = cmSystemTools::RelativePath(
-    this->AutogenBuildDir.c_str(), rccFileAbs.c_str());
 
-  // Check if regeneration is required
-  bool generate = false;
-  std::string generateReason;
-  if (!cmSystemTools::FileExists(this->QrcFile)) {
-    {
-      std::string error = "Could not find the file\n  ";
-      error += cmQtAutoGen::Quoted(this->QrcFile);
-      this->LogError(cmQtAutoGen::RCC, error);
+  if (!RccListOptions_.empty()) {
+    // Start a rcc list process and parse the output
+    if (Process_) {
+      // Process is running already
+      if (Process_->IsFinished()) {
+        // Process is finished
+        if (!ProcessResult_.error()) {
+          // Process success
+          std::string parseError;
+          if (!RccListParseOutput(ProcessResult_.StdOut, ProcessResult_.StdErr,
+                                  Inputs_, parseError)) {
+            Log().ErrorFile(GeneratorT::RCC, QrcFile_, parseError);
+            Error_ = true;
+          }
+        } else {
+          Log().ErrorFile(GeneratorT::RCC, QrcFile_,
+                          ProcessResult_.ErrorMessage);
+          Error_ = true;
+        }
+        // Clean up
+        Process_.reset();
+        ProcessResult_.reset();
+      } else {
+        // Process is not finished, yet.
+        return false;
+      }
+    } else {
+      // Start a new process
+      // rcc prints relative entry paths when started in the directory of the
+      // qrc file with a pathless qrc file name argument.
+      // This is important because on Windows absolute paths returned by rcc
+      // might contain bad multibyte characters when the qrc file path
+      // contains non-ASCII pcharacters.
+      std::vector<std::string> cmd;
+      cmd.push_back(RccExecutable_);
+      cmd.insert(cmd.end(), RccListOptions_.begin(), RccListOptions_.end());
+      cmd.push_back(QrcFileName_);
+      // We're done here if the process fails to start
+      return !StartProcess(QrcFileDir_, cmd, false);
     }
-    success = false;
-  }
-  if (success && !generate && !cmSystemTools::FileExists(rccFileAbs.c_str())) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(rccFileAbs);
-      generateReason += " from its source file ";
-      generateReason += cmQtAutoGen::Quoted(this->QrcFile);
-      generateReason += " because it doesn't exist";
+  } else {
+    // rcc does not support the --list command.
+    // Read the qrc file content and parse it.
+    std::string qrcContent;
+    if (FileSys().FileRead(GeneratorT::RCC, qrcContent, QrcFile_)) {
+      RccListParseContent(qrcContent, Inputs_);
     }
-    generate = true;
   }
-  if (success && !generate && this->SettingsChanged) {
-    if (this->GetVerbose()) {
-      generateReason = "Generating ";
-      generateReason += cmQtAutoGen::Quoted(rccFileAbs);
-      generateReason += " from ";
-      generateReason += cmQtAutoGen::Quoted(this->QrcFile);
-      generateReason += " because the RCC settings changed";
-    }
-    generate = true;
+
+  if (!Inputs_.empty()) {
+    // Convert relative paths to absolute paths
+    RccListConvertFullPath(QrcFileDir_, Inputs_);
+  }
+
+  return true;
+}
+
+bool cmQtAutoGeneratorRcc::TestResources()
+{
+  if (Inputs_.empty()) {
+    return true;
   }
-  if (success && !generate) {
+  {
     std::string error;
-    if (FileIsOlderThan(rccFileAbs, this->QrcFile, &error)) {
-      if (this->GetVerbose()) {
-        generateReason = "Generating ";
-        generateReason += cmQtAutoGen::Quoted(rccFileAbs);
-        generateReason += " because it is older than ";
-        generateReason += cmQtAutoGen::Quoted(this->QrcFile);
+    for (std::string const& resFile : Inputs_) {
+      // Check if the resource file exists
+      if (!FileSys().FileExists(resFile)) {
+        error = "Could not find the resource file\n  ";
+        error += Quoted(resFile);
+        error += '\n';
+        Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
+        Error_ = true;
+        break;
       }
-      generate = true;
-    } else {
+      // Check if the resource file is newer than the build file
+      if (FileSys().FileIsOlderThan(RccFileBuild_, resFile, &error)) {
+        if (Log().Verbose()) {
+          std::string reason = "Generating ";
+          reason += Quoted(RccFileBuild_);
+          reason += " from ";
+          reason += Quoted(QrcFile_);
+          reason += " because it is older than ";
+          reason += Quoted(resFile);
+          Log().Info(GeneratorT::RCC, reason);
+        }
+        Generate_ = true;
+        break;
+      }
+      // Print error and break on demand
       if (!error.empty()) {
-        this->LogError(cmQtAutoGen::RCC, error);
-        success = false;
+        Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
+        Error_ = true;
+        break;
       }
     }
   }
-  if (success && !generate) {
-    // Acquire input file list
-    std::vector<std::string> readFiles;
-    std::vector<std::string> const* files = nullptr;
-    if (!this->Inputs.empty()) {
-      files = &this->Inputs;
-    } else {
-      // Read input file list from qrc file
+
+  return Generate_;
+}
+
+void cmQtAutoGeneratorRcc::TestInfoFile()
+{
+  // Test if the rcc output file is older than the info file
+  {
+    bool isOlder = false;
+    {
       std::string error;
-      if (cmQtAutoGen::RccListInputs(this->RccExecutable, this->RccListOptions,
-                                     this->QrcFile, readFiles, &error)) {
-        files = &readFiles;
-      } else {
-        this->LogFileError(cmQtAutoGen::RCC, this->QrcFile, error);
-        success = false;
+      isOlder = FileSys().FileIsOlderThan(RccFileBuild_, InfoFile(), &error);
+      if (!error.empty()) {
+        Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
+        Error_ = true;
       }
     }
-    // Test if any input file is newer than the build file
-    if (files != nullptr) {
-      std::string error;
-      for (std::string const& resFile : *files) {
-        if (!cmSystemTools::FileExists(resFile.c_str())) {
-          error = "Could not find the file\n  ";
-          error += cmQtAutoGen::Quoted(resFile);
-          error += "\nwhich is listed in\n  ";
-          error += cmQtAutoGen::Quoted(this->QrcFile);
-          break;
-        }
-        if (FileIsOlderThan(rccFileAbs, resFile, &error)) {
-          if (this->GetVerbose()) {
-            generateReason = "Generating ";
-            generateReason += cmQtAutoGen::Quoted(rccFileAbs);
-            generateReason += " from ";
-            generateReason += cmQtAutoGen::Quoted(this->QrcFile);
-            generateReason += " because it is older than ";
-            generateReason += cmQtAutoGen::Quoted(resFile);
-          }
-          generate = true;
-          break;
-        }
-        if (!error.empty()) {
-          break;
-        }
-      }
-      // Print error
-      if (!error.empty()) {
-        this->LogError(cmQtAutoGen::RCC, error);
-        success = false;
+    if (isOlder) {
+      if (Log().Verbose()) {
+        std::string reason = "Touching ";
+        reason += Quoted(RccFileBuild_);
+        reason += " because it is older than ";
+        reason += Quoted(InfoFile());
+        Log().Info(GeneratorT::RCC, reason);
       }
+      // Touch build file
+      FileSys().Touch(RccFileBuild_);
+      BuildFileChanged_ = true;
     }
   }
-  // Regenerate on demand
-  if (generate) {
-    // Log
-    if (this->GetVerbose()) {
-      this->LogBold("Generating RCC source " + rccFileRel);
-      this->LogInfo(cmQtAutoGen::RCC, generateReason);
-    }
+}
 
-    // Make sure the parent directory exists
-    if (this->MakeParentDirectory(cmQtAutoGen::RCC, rccFileAbs)) {
-      // Compose rcc command
-      std::vector<std::string> cmd;
-      cmd.push_back(this->RccExecutable);
-      cmd.insert(cmd.end(), this->Options.begin(), this->Options.end());
-      cmd.push_back("-o");
-      cmd.push_back(rccFileAbs);
-      cmd.push_back(this->QrcFile);
-
-      std::string output;
-      if (this->RunCommand(cmd, output)) {
-        // Success
-        rccGenerated = true;
+void cmQtAutoGeneratorRcc::GenerateParentDir()
+{
+  // Make sure the parent directory exists
+  if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileBuild_)) {
+    Error_ = true;
+  }
+}
+
+/**
+ * @return True when finished
+ */
+bool cmQtAutoGeneratorRcc::GenerateRcc()
+{
+  if (!Generate_) {
+    // Nothing to do
+    return true;
+  }
+
+  if (Process_) {
+    // Process is running already
+    if (Process_->IsFinished()) {
+      // Process is finished
+      if (!ProcessResult_.error()) {
+        // Process success
+        BuildFileChanged_ = true;
       } else {
+        // Process failed
         {
-          std::string emsg = "rcc failed for\n  ";
-          emsg += cmQtAutoGen::Quoted(this->QrcFile);
-          this->LogCommandError(cmQtAutoGen::RCC, emsg, cmd, output);
+          std::string emsg = "The rcc process failed to compile\n  ";
+          emsg += Quoted(QrcFile_);
+          emsg += "\ninto\n  ";
+          emsg += Quoted(RccFileBuild_);
+          if (ProcessResult_.error()) {
+            emsg += "\n";
+            emsg += ProcessResult_.ErrorMessage;
+          }
+          Log().ErrorCommand(GeneratorT::RCC, emsg, Process_->Setup().Command,
+                             ProcessResult_.StdOut);
         }
-        cmSystemTools::RemoveFile(rccFileAbs);
-        success = false;
+        FileSys().FileRemove(RccFileBuild_);
+        Error_ = true;
       }
+      // Clean up
+      Process_.reset();
+      ProcessResult_.reset();
     } else {
-      // Parent directory creation failed
-      success = false;
+      // Process is not finished, yet.
+      return false;
     }
+  } else {
+    // Start a rcc process
+    std::vector<std::string> cmd;
+    cmd.push_back(RccExecutable_);
+    cmd.insert(cmd.end(), Options_.begin(), Options_.end());
+    cmd.push_back("-o");
+    cmd.push_back(RccFileBuild_);
+    cmd.push_back(QrcFile_);
+    // We're done here if the process fails to start
+    return !StartProcess(AutogenBuildDir_, cmd, true);
   }
 
+  return true;
+}
+
+void cmQtAutoGeneratorRcc::GenerateWrapper()
+{
   // Generate a wrapper source file on demand
-  if (success && (this->MultiConfig == cmQtAutoGen::WRAP)) {
+  if (MultiConfig_ == MultiConfigT::WRAPPER) {
     // Wrapper file name
-    std::string const& wrapperFileAbs = this->RccFile;
-    std::string const wrapperFileRel = cmSystemTools::RelativePath(
-      this->AutogenBuildDir.c_str(), wrapperFileAbs.c_str());
+    std::string const& wrapperAbs = RccFile_;
     // Wrapper file content
     std::string content = "// This is an autogenerated configuration "
                           "wrapper file. Changes will be overwritten.\n"
                           "#include \"";
-    content += cmSystemTools::GetFilenameName(rccFileRel);
+    content += cmSystemTools::GetFilenameName(RccFileBuild_);
     content += "\"\n";
     // Write content to file
-    if (this->FileDiffers(wrapperFileAbs, content)) {
+    if (FileSys().FileDiffers(wrapperAbs, content)) {
       // Write new wrapper file
-      if (this->GetVerbose()) {
-        this->LogBold("Generating RCC wrapper " + wrapperFileRel);
+      if (Log().Verbose()) {
+        Log().Info(GeneratorT::RCC, "Generating RCC wrapper " + wrapperAbs);
       }
-      if (!this->FileWrite(cmQtAutoGen::RCC, wrapperFileAbs, content)) {
-        this->LogFileError(cmQtAutoGen::RCC, wrapperFileAbs,
-                           "rcc wrapper file writing failed");
-        success = false;
+      if (!FileSys().FileWrite(GeneratorT::RCC, wrapperAbs, content)) {
+        Log().ErrorFile(GeneratorT::RCC, wrapperAbs,
+                        "RCC wrapper file writing failed");
+        Error_ = true;
       }
-    } else if (rccGenerated) {
+    } else if (BuildFileChanged_) {
       // Just touch the wrapper file
-      if (this->GetVerbose()) {
-        this->LogInfo(cmQtAutoGen::RCC,
-                      "Touching RCC wrapper " + wrapperFileRel);
+      if (Log().Verbose()) {
+        Log().Info(GeneratorT::RCC, "Touching RCC wrapper " + wrapperAbs);
       }
-      cmSystemTools::Touch(wrapperFileAbs, false);
+      FileSys().Touch(wrapperAbs);
     }
   }
+}
+
+bool cmQtAutoGeneratorRcc::StartProcess(
+  std::string const& workingDirectory, std::vector<std::string> const& command,
+  bool mergedOutput)
+{
+  // Log command
+  if (Log().Verbose()) {
+    std::string msg = "Running command:\n";
+    msg += QuotedCommand(command);
+    msg += '\n';
+    Log().Info(GeneratorT::RCC, msg);
+  }
 
-  return success;
+  // Create process handler
+  Process_ = cm::make_unique<ReadOnlyProcessT>();
+  Process_->setup(&ProcessResult_, mergedOutput, command, workingDirectory);
+  // Start process
+  if (!Process_->start(UVLoop(),
+                       std::bind(&cm::uv_async_ptr::send, &UVRequest()))) {
+    Log().ErrorFile(GeneratorT::RCC, QrcFile_, ProcessResult_.ErrorMessage);
+    Error_ = true;
+    // Clean up
+    Process_.reset();
+    ProcessResult_.reset();
+    return false;
+  }
+  return true;
 }
diff --git a/Source/cmQtAutoGeneratorRcc.h b/Source/cmQtAutoGeneratorRcc.h
index 0e3f690..8a69c6c 100644
--- a/Source/cmQtAutoGeneratorRcc.h
+++ b/Source/cmQtAutoGeneratorRcc.h
@@ -5,52 +5,97 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include "cmFilePathChecksum.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenerator.h"
+#include "cm_uv.h"
 
 #include <string>
 #include <vector>
 
 class cmMakefile;
 
+// @brief AUTORCC generator
 class cmQtAutoGeneratorRcc : public cmQtAutoGenerator
 {
   CM_DISABLE_COPY(cmQtAutoGeneratorRcc)
 public:
   cmQtAutoGeneratorRcc();
+  ~cmQtAutoGeneratorRcc() override;
 
 private:
-  // -- Initialization & settings
-  bool InfoFileRead(cmMakefile* makefile);
-  void SettingsFileRead(cmMakefile* makefile);
-  bool SettingsFileWrite();
-  // -- Central processing
-  bool Process(cmMakefile* makefile) override;
-  bool RccGenerate();
+  // -- Types
 
+  /// @brief Processing stage
+  enum class StageT
+  {
+    SETTINGS_READ,
+    TEST_QRC_RCC_FILES,
+    TEST_RESOURCES_READ,
+    TEST_RESOURCES,
+    TEST_INFO_FILE,
+    GENERATE,
+    GENERATE_RCC,
+    GENERATE_WRAPPER,
+    SETTINGS_WRITE,
+    FINISH,
+    END
+  };
+
+  // -- Abstract processing interface
+  bool Init(cmMakefile* makefile) override;
+  bool Process() override;
+  // -- Process stage
+  static void UVPollStage(uv_async_t* handle);
+  void PollStage();
+  void SetStage(StageT stage);
+  // -- Settings file
+  void SettingsFileRead();
+  void SettingsFileWrite();
+  // -- Tests
+  bool TestQrcRccFiles();
+  bool TestResourcesRead();
+  bool TestResources();
+  void TestInfoFile();
+  // -- Generation
+  void GenerateParentDir();
+  bool GenerateRcc();
+  void GenerateWrapper();
+
+  // -- Utility
+  bool StartProcess(std::string const& workingDirectory,
+                    std::vector<std::string> const& command,
+                    bool mergedOutput);
+
+private:
   // -- Config settings
-  std::string ConfigSuffix;
-  cmQtAutoGen::MultiConfig MultiConfig;
-  // -- Settings
-  bool SettingsChanged;
-  std::string SettingsFile;
-  std::string SettingsString;
+  bool SettingsChanged_;
+  std::string ConfigSuffix_;
+  MultiConfigT MultiConfig_;
   // -- Directories
-  std::string ProjectSourceDir;
-  std::string ProjectBinaryDir;
-  std::string CurrentSourceDir;
-  std::string CurrentBinaryDir;
-  std::string AutogenBuildDir;
-  cmFilePathChecksum FilePathChecksum;
+  std::string AutogenBuildDir_;
   // -- Qt environment
-  std::string RccExecutable;
-  std::vector<std::string> RccListOptions;
+  std::string RccExecutable_;
+  std::vector<std::string> RccListOptions_;
   // -- Job
-  std::string QrcFile;
-  std::string RccFile;
-  std::vector<std::string> Options;
-  std::vector<std::string> Inputs;
+  std::string QrcFile_;
+  std::string QrcFileName_;
+  std::string QrcFileDir_;
+  std::string RccFile_;
+  std::string RccFileWrapper_;
+  std::string RccFileBuild_;
+  std::vector<std::string> Options_;
+  std::vector<std::string> Inputs_;
+  // -- Subprocess
+  ProcessResultT ProcessResult_;
+  std::unique_ptr<ReadOnlyProcessT> Process_;
+  // -- Settings file
+  std::string SettingsFile_;
+  std::string SettingsString_;
+  // -- libuv loop
+  StageT Stage_;
+  bool Error_;
+  bool Generate_;
+  bool BuildFileChanged_;
 };
 
 #endif
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 1974be3..663a4c9 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -247,6 +247,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     this->SetPropertyDefault("AUTOMOC", nullptr);
     this->SetPropertyDefault("AUTOUIC", nullptr);
     this->SetPropertyDefault("AUTORCC", nullptr);
+    this->SetPropertyDefault("AUTOGEN_PARALLEL", nullptr);
     this->SetPropertyDefault("AUTOMOC_COMPILER_PREDEFINES", nullptr);
     this->SetPropertyDefault("AUTOMOC_DEPEND_FILTERS", nullptr);
     this->SetPropertyDefault("AUTOMOC_MACRO_NAMES", nullptr);
-- 
cgit v0.12