[Cmake-commits] CMake branch, master, updated. v3.14.2-729-g9aecda5

Kitware Robot kwrobot at kitware.com
Tue Apr 16 13:43:05 EDT 2019


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "CMake".

The branch, master has been updated
       via  9aecda56ba960267b392e54e5c28388a6cfa92ed (commit)
       via  3205561b8a04f16d0004f73d0dc5eeffd0c7dd02 (commit)
       via  34975815a8b07879ea10b23e8cb630bb1930a3ee (commit)
       via  68a0b51ef882456affb852e33317cc58aa1e3692 (commit)
       via  7f9e93aa0599e039f7673fb55143701754f33bb9 (commit)
       via  fc4324a27cb534080fa981a77391e0224df2ef67 (commit)
       via  09fba6146fa726a83cbbccc3d5fb288f7f14f111 (commit)
       via  0b54f72e940503adfbd978d7f1dedc3da4799f52 (commit)
       via  60ec2922585d3710b45212fb5671676ff726965e (commit)
       via  536cca60ea1c037d751b03bf5da8385783856000 (commit)
       via  cce342a5b966c57d933951053757090fc860a067 (commit)
       via  a0b6448c855053301cba575c226abecef173f2c3 (commit)
       via  58f04b6ecf853e4ee5cce2bfb258fa7d4cc75b79 (commit)
       via  a3f062091f488237c0151f3f4753e0668f37c60d (commit)
       via  8cb26a0a2ad57ca9012f97c7437711ee94f1a9db (commit)
      from  5ec9d16b5a955de40d1ba904d08479ab204c0cb7 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=9aecda56ba960267b392e54e5c28388a6cfa92ed
commit 9aecda56ba960267b392e54e5c28388a6cfa92ed
Merge: 3205561 60ec292
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 17:42:43 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Tue Apr 16 13:42:53 2019 -0400

    Merge topic 'genex-output_name'
    
    60ec292258 Genex: Rename $<TARGET_*_OUTPUT_NAME:...> in $<TARGET_*_FILE_BASE_NAME:...>
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3228


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=3205561b8a04f16d0004f73d0dc5eeffd0c7dd02
commit 3205561b8a04f16d0004f73d0dc5eeffd0c7dd02
Merge: 3497581 536cca6
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 17:39:51 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Tue Apr 16 13:40:02 2019 -0400

    Merge topic 'string-repeat'
    
    536cca60ea string: introduce `REPEAT` sub-command
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3239


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=34975815a8b07879ea10b23e8cb630bb1930a3ee
commit 34975815a8b07879ea10b23e8cb630bb1930a3ee
Merge: 68a0b51 58f04b6
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 17:38:08 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Tue Apr 16 13:38:21 2019 -0400

    Merge topic 'autogen_moc_uic_single_job_queue'
    
    58f04b6ecf Autogen: Add ManySources test
    a3f062091f Autogen: Rename `cmQtAutoGeneratorMocUic` class to `cmQtAutoMocUic`
    8cb26a0a2a Autogen: Factor out concurrency framework to cmWorkerPool class
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3224


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=68a0b51ef882456affb852e33317cc58aa1e3692
commit 68a0b51ef882456affb852e33317cc58aa1e3692
Merge: 7f9e93a 09fba61
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 13:36:54 2019 -0400
Commit:     Brad King <brad.king at kitware.com>
CommitDate: Tue Apr 16 13:36:54 2019 -0400

    Merge branch 'release-3.14'


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=7f9e93aa0599e039f7673fb55143701754f33bb9
commit 7f9e93aa0599e039f7673fb55143701754f33bb9
Merge: fc4324a a0b6448
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 17:35:27 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Tue Apr 16 13:35:39 2019 -0400

    Merge topic 'vs2019-redist'
    
    a0b6448c85 IRSL: Update redist directory for VS 2019 update 1
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3233


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=fc4324a27cb534080fa981a77391e0224df2ef67
commit fc4324a27cb534080fa981a77391e0224df2ef67
Merge: 5ec9d16 0b54f72
Author:     Brad King <brad.king at kitware.com>
AuthorDate: Tue Apr 16 17:34:33 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Tue Apr 16 13:34:41 2019 -0400

    Merge topic 'Boost-Gentoo'
    
    0b54f72e94 FindBoost: Fix detection with version suffixes on Gentoo
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3237


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=60ec2922585d3710b45212fb5671676ff726965e
commit 60ec2922585d3710b45212fb5671676ff726965e
Author:     Marc Chevrier <marc.chevrier at gmail.com>
AuthorDate: Sun Apr 14 19:48:39 2019 +0200
Commit:     Marc Chevrier <marc.chevrier at gmail.com>
CommitDate: Mon Apr 15 18:22:14 2019 +0200

    Genex: Rename $<TARGET_*_OUTPUT_NAME:...> in $<TARGET_*_FILE_BASE_NAME:...>

diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index e9b3f4c..7f4761f 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -387,14 +387,25 @@ Target-Dependent Queries
 ``$<TARGET_NAME_IF_EXISTS:tgt>``
   Expands to the ``tgt`` if the given target exists, an empty string
   otherwise.
-``$<TARGET_OUTPUT_NAME:tgt>``
+``$<TARGET_FILE:tgt>``
+  Full path to main file (.exe, .so.1.2, .a) where ``tgt`` is the name of a
+  target.
+``$<TARGET_FILE_BASE_NAME:tgt>``
   Base name of main file where ``tgt`` is the name of a target.
 
+  The base name corresponds to the target file name (see
+  ``$<TARGET_FILE_NAME:tgt>``) without prefix and suffix. For example, if
+  target file name is ``libbase.so``, the base name is ``base``.
+
+  See also the :prop_tgt:`OUTPUT_NAME`, :prop_tgt:`ARCHIVE_OUTPUT_NAME`,
+  :prop_tgt:`LIBRARY_OUTPUT_NAME` and :prop_tgt:`RUNTIME_OUTPUT_NAME`
+  target properties and their configuration specific variants
+  :prop_tgt:`OUTPUT_NAME_<CONFIG>`, :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>`,
+  :prop_tgt:`LIBRARY_OUTPUT_NAME_<CONFIG>` and
+  :prop_tgt:`RUNTIME_OUTPUT_NAME_<CONFIG>`.
+
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on.
-``$<TARGET_FILE:tgt>``
-  Full path to main file (.exe, .so.1.2, .a) where ``tgt`` is the name of a
-  target.
 ``$<TARGET_FILE_PREFIX:tgt>``
   Prefix of main file where ``tgt`` is the name of a target.
 
@@ -409,13 +420,23 @@ Target-Dependent Queries
   Name of main file (.exe, .so.1.2, .a).
 ``$<TARGET_FILE_DIR:tgt>``
   Directory of main file (.exe, .so.1.2, .a).
-``$<TARGET_LINKER_OUTPUT_NAME:tgt>``
+``$<TARGET_LINKER_FILE:tgt>``
+  File used to link (.a, .lib, .so) where ``tgt`` is the name of a target.
+``$<TARGET_LINKER_FILE_BASE_NAME:tgt>``
   Base name of file used to link where ``tgt`` is the name of a target.
 
+  The base name corresponds to the target linker file name (see
+  ``$<TARGET_LINKER_FILE_NAME:tgt>``) without prefix and suffix. For example,
+  if target file name is ``libbase.a``, the base name is ``base``.
+
+  See also the :prop_tgt:`OUTPUT_NAME`, :prop_tgt:`ARCHIVE_OUTPUT_NAME`,
+  and :prop_tgt:`LIBRARY_OUTPUT_NAME` target properties and their configuration
+  specific variants :prop_tgt:`OUTPUT_NAME_<CONFIG>`,
+  :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>` and
+  :prop_tgt:`LIBRARY_OUTPUT_NAME_<CONFIG>`.
+
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on.
-``$<TARGET_LINKER_FILE:tgt>``
-  File used to link (.a, .lib, .so) where ``tgt`` is the name of a target.
 ``$<TARGET_LINKER_FILE_PREFIX:tgt>``
   Prefix of file used to link where ``tgt`` is the name of a target.
 
@@ -436,22 +457,26 @@ Target-Dependent Queries
   Name of file with soname (.so.3).
 ``$<TARGET_SONAME_FILE_DIR:tgt>``
   Directory of with soname (.so.3).
-``$<TARGET_PDB_OUTPUT_NAME:tgt>``
+``$<TARGET_PDB_FILE:tgt>``
+  Full path to the linker generated program database file (.pdb)
+  where ``tgt`` is the name of a target.
+
+  See also the :prop_tgt:`PDB_NAME` and :prop_tgt:`PDB_OUTPUT_DIRECTORY`
+  target properties and their configuration specific variants
+  :prop_tgt:`PDB_NAME_<CONFIG>` and :prop_tgt:`PDB_OUTPUT_DIRECTORY_<CONFIG>`.
+``$<TARGET_PDB_FILE_BASE_NAME:tgt>``
   Base name of the linker generated program database file (.pdb)
   where ``tgt`` is the name of a target.
 
+  The base name corresponds to the target PDB file name (see
+  ``$<TARGET_PDB_FILE_NAME:tgt>``) without prefix and suffix. For example,
+  if target file name is ``base.pdb``, the base name is ``base``.
+
   See also the :prop_tgt:`PDB_NAME` target property and its configuration
   specific variant :prop_tgt:`PDB_NAME_<CONFIG>`.
 
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on.
-``$<TARGET_PDB_FILE:tgt>``
-  Full path to the linker generated program database file (.pdb)
-  where ``tgt`` is the name of a target.
-
-  See also the :prop_tgt:`PDB_NAME` and :prop_tgt:`PDB_OUTPUT_DIRECTORY`
-  target properties and their configuration specific variants
-  :prop_tgt:`PDB_NAME_<CONFIG>` and :prop_tgt:`PDB_OUTPUT_DIRECTORY_<CONFIG>`.
 ``$<TARGET_PDB_FILE_NAME:tgt>``
   Name of the linker generated program database file (.pdb).
 ``$<TARGET_PDB_FILE_DIR:tgt>``
diff --git a/Help/release/dev/genex-TARGET_FILE_BASE_NAME.rst b/Help/release/dev/genex-TARGET_FILE_BASE_NAME.rst
new file mode 100644
index 0000000..d8b2b21
--- /dev/null
+++ b/Help/release/dev/genex-TARGET_FILE_BASE_NAME.rst
@@ -0,0 +1,7 @@
+genex-TARGET_FILE_BASE_NAME
+---------------------------
+
+* New ``$<TARGET_FILE_BASE_NAME:...>``, ``$<TARGET_LINKER_FILE_BASE_NAME:...>``
+  and ``$<TARGET_PDB_FILE_BASE_NAME:...>``
+  :manual:`generator expressions <cmake-generator-expressions(7)>` have been
+  added to retrieve the base name of various artifacts.
diff --git a/Help/release/dev/genex-TARGET_OUTPUT_NAME.rst b/Help/release/dev/genex-TARGET_OUTPUT_NAME.rst
deleted file mode 100644
index e3ffe57..0000000
--- a/Help/release/dev/genex-TARGET_OUTPUT_NAME.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-genex-TARGET_OUTPUT_NAME
-------------------------
-
-* New ``$<TARGET_OUTPUT_NAME:...>``, ``$<TARGET_LINKER_OUTPUT_NAME:...>`` and
-  ``$<TARGET_PDB_OUTPUT_NAME:...>``
-  :manual:`generator expressions <cmake-generator-expressions(7)>` have been
-  added to retrieve the base name of various artifacts.
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index af409e4..8b3d9d6 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -2210,8 +2210,8 @@ struct TargetOutputNameArtifactResultGetter<ArtifactLinkerTag>
     // The file used to link to the target (.so, .lib, .a).
     if (!target->IsLinkable()) {
       ::reportError(context, content->GetOriginalExpression(),
-                    "TARGET_LINKER_OUTPUT_NAME is allowed only for libraries "
-                    "and executables with ENABLE_EXPORTS.");
+                    "TARGET_LINKER_FILE_BASE_NAME is allowed only for "
+                    "libraries and executables with ENABLE_EXPORTS.");
       return std::string();
     }
     cmStateEnums::ArtifactType artifact =
@@ -2232,7 +2232,7 @@ struct TargetOutputNameArtifactResultGetter<ArtifactPdbTag>
     if (target->IsImported()) {
       ::reportError(
         context, content->GetOriginalExpression(),
-        "TARGET_PDB_OUTPUT_NAME not allowed for IMPORTED targets.");
+        "TARGET_PDB_FILE_BASE_NAME not allowed for IMPORTED targets.");
       return std::string();
     }
 
@@ -2243,7 +2243,7 @@ struct TargetOutputNameArtifactResultGetter<ArtifactPdbTag>
     if (!context->LG->GetMakefile()->IsOn(pdbSupportVar)) {
       ::reportError(
         context, content->GetOriginalExpression(),
-        "TARGET_PDB_OUTPUT_NAME is not supported by the target linker.");
+        "TARGET_PDB_FILE_BASE_NAME is not supported by the target linker.");
       return std::string();
     }
 
@@ -2253,7 +2253,7 @@ struct TargetOutputNameArtifactResultGetter<ArtifactPdbTag>
         targetType != cmStateEnums::MODULE_LIBRARY &&
         targetType != cmStateEnums::EXECUTABLE) {
       ::reportError(context, content->GetOriginalExpression(),
-                    "TARGET_PDB_OUTPUT_NAME is allowed only for "
+                    "TARGET_PDB_FILE_BASE_NAME is allowed only for "
                     "targets with linker created artifacts.");
       return std::string();
     }
@@ -2263,9 +2263,9 @@ struct TargetOutputNameArtifactResultGetter<ArtifactPdbTag>
 };
 
 template <typename ArtifactT>
-struct TargetOutputNameArtifact : public TargetArtifactBase
+struct TargetFileBaseNameArtifact : public TargetArtifactBase
 {
-  TargetOutputNameArtifact() {} // NOLINT(modernize-use-equals-default)
+  TargetFileBaseNameArtifact() {} // NOLINT(modernize-use-equals-default)
 
   int NumExpectedParameters() const override { return 1; }
 
@@ -2290,12 +2290,12 @@ struct TargetOutputNameArtifact : public TargetArtifactBase
   }
 };
 
-static const TargetOutputNameArtifact<ArtifactNameTag> targetOutputNameNode;
-
-static const TargetOutputNameArtifact<ArtifactLinkerTag>
-  targetLinkerOutputNameNode;
-
-static const TargetOutputNameArtifact<ArtifactPdbTag> targetPdbOutputNameNode;
+static const TargetFileBaseNameArtifact<ArtifactNameTag>
+  targetFileBaseNameNode;
+static const TargetFileBaseNameArtifact<ArtifactLinkerTag>
+  targetLinkerFileBaseNameNode;
+static const TargetFileBaseNameArtifact<ArtifactPdbTag>
+  targetPdbFileBaseNameNode;
 
 class ArtifactFilePrefixTag;
 class ArtifactLinkerFilePrefixTag;
@@ -2474,6 +2474,9 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
     { "TARGET_LINKER_FILE", &targetLinkerNodeGroup.File },
     { "TARGET_SONAME_FILE", &targetSoNameNodeGroup.File },
     { "TARGET_PDB_FILE", &targetPdbNodeGroup.File },
+    { "TARGET_FILE_BASE_NAME", &targetFileBaseNameNode },
+    { "TARGET_LINKER_FILE_BASE_NAME", &targetLinkerFileBaseNameNode },
+    { "TARGET_PDB_FILE_BASE_NAME", &targetPdbFileBaseNameNode },
     { "TARGET_FILE_PREFIX", &targetFilePrefixNode },
     { "TARGET_LINKER_FILE_PREFIX", &targetLinkerFilePrefixNode },
     { "TARGET_FILE_SUFFIX", &targetFileSuffixNode },
@@ -2488,9 +2491,6 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
     { "TARGET_PDB_FILE_DIR", &targetPdbNodeGroup.FileDir },
     { "TARGET_BUNDLE_DIR", &targetBundleDirNode },
     { "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode },
-    { "TARGET_OUTPUT_NAME", &targetOutputNameNode },
-    { "TARGET_LINKER_OUTPUT_NAME", &targetLinkerOutputNameNode },
-    { "TARGET_PDB_OUTPUT_NAME", &targetPdbOutputNameNode },
     { "STREQUAL", &strEqualNode },
     { "EQUAL", &equalNode },
     { "IN_LIST", &inListNode },
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-result.txt
similarity index 100%
rename from Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-result.txt
rename to Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-result.txt
diff --git a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
new file mode 100644
index 0000000..3b2a814
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake:2 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<TARGET_PDB_FILE_BASE_NAME:empty>
+
+  TARGET_PDB_FILE_BASE_NAME not allowed for IMPORTED targets.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake
new file mode 100644
index 0000000..489d8e6
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake
@@ -0,0 +1,2 @@
+add_library(empty UNKNOWN IMPORTED)
+add_custom_target(custom COMMAND echo $<TARGET_PDB_FILE_BASE_NAME:empty>)
diff --git a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
deleted file mode 100644
index 783bfb3..0000000
--- a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-CMake Error at ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake:2 \(add_custom_target\):
-  Error evaluating generator expression:
-
-    \$<TARGET_PDB_OUTPUT_NAME:empty>
-
-  TARGET_PDB_OUTPUT_NAME not allowed for IMPORTED targets.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake b/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake
deleted file mode 100644
index 010b38e..0000000
--- a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake
+++ /dev/null
@@ -1,2 +0,0 @@
-add_library(empty UNKNOWN IMPORTED)
-add_custom_target(custom COMMAND echo $<TARGET_PDB_OUTPUT_NAME:empty>)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-result.txt
similarity index 100%
rename from Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-result.txt
rename to Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-result.txt
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-stderr.txt
new file mode 100644
index 0000000..b061ce3
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at NonValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake:6 \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_PDB_FILE_BASE_NAME:empty>
+
+  TARGET_PDB_FILE_BASE_NAME is not supported by the target linker.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME.cmake b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake
similarity index 71%
rename from Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME.cmake
rename to Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake
index 07951de..811c3f7 100644
--- a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME.cmake
+++ b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake
@@ -5,5 +5,5 @@ add_library(empty STATIC empty.c)
 
 file(GENERATE
   OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
-  CONTENT "[$<TARGET_PDB_OUTPUT_NAME:empty>]"
+  CONTENT "[$<TARGET_PDB_FILE_BASE_NAME:empty>]"
 )
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-stderr.txt
deleted file mode 100644
index 00ec496..0000000
--- a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-stderr.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-CMake Error at NonValidCompiler-TARGET_PDB_OUTPUT_NAME.cmake:6 \(file\):
-  Error evaluating generator expression:
-
-    \$<TARGET_PDB_OUTPUT_NAME:empty>
-
-  TARGET_PDB_OUTPUT_NAME is not supported by the target linker.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-result.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-result.txt
similarity index 100%
rename from Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-result.txt
rename to Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-result.txt
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
new file mode 100644
index 0000000..c7d245c
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake:6 \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_PDB_FILE_BASE_NAME:empty>
+
+  TARGET_PDB_FILE_BASE_NAME is allowed only for targets with linker created
+  artifacts.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME.cmake b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
similarity index 71%
rename from Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME.cmake
rename to Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
index 07951de..811c3f7 100644
--- a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME.cmake
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
@@ -5,5 +5,5 @@ add_library(empty STATIC empty.c)
 
 file(GENERATE
   OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
-  CONTENT "[$<TARGET_PDB_OUTPUT_NAME:empty>]"
+  CONTENT "[$<TARGET_PDB_FILE_BASE_NAME:empty>]"
 )
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
deleted file mode 100644
index 8ac349e..0000000
--- a/Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-CMake Error at NonValidTarget-TARGET_PDB_OUTPUT_NAME.cmake:6 \(file\):
-  Error evaluating generator expression:
-
-    \$<TARGET_PDB_OUTPUT_NAME:empty>
-
-  TARGET_PDB_OUTPUT_NAME is allowed only for targets with linker created
-  artifacts.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/OUTPUT_NAME-recursion.cmake b/Tests/RunCMake/GeneratorExpression/OUTPUT_NAME-recursion.cmake
index 775f68a..006b0da 100644
--- a/Tests/RunCMake/GeneratorExpression/OUTPUT_NAME-recursion.cmake
+++ b/Tests/RunCMake/GeneratorExpression/OUTPUT_NAME-recursion.cmake
@@ -3,4 +3,4 @@ add_executable(empty1 empty.c)
 set_property(TARGET empty1 PROPERTY OUTPUT_NAME $<TARGET_FILE_NAME:empty1>)
 
 add_executable(empty2 empty.c)
-set_property(TARGET empty2 PROPERTY OUTPUT_NAME $<TARGET_OUTPUT_NAME:empty2>)
+set_property(TARGET empty2 PROPERTY OUTPUT_NAME $<TARGET_FILE_BASE_NAME:empty2>)
diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
index 0b0fb78..477b593 100644
--- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
@@ -41,10 +41,10 @@ run_cmake(TARGET_FILE_SUFFIX)
 run_cmake(TARGET_FILE_SUFFIX-imported-target)
 run_cmake(TARGET_FILE_SUFFIX-non-valid-target)
 run_cmake(TARGET_LINKER_FILE_SUFFIX-non-valid-target)
-run_cmake(TARGET_OUTPUT_NAME)
-run_cmake(TARGET_OUTPUT_NAME-imported-target)
-run_cmake(TARGET_OUTPUT_NAME-non-valid-target)
-run_cmake(TARGET_LINKER_OUTPUT_NAME-non-valid-target)
+run_cmake(TARGET_FILE_BASE_NAME)
+run_cmake(TARGET_FILE_BASE_NAME-imported-target)
+run_cmake(TARGET_FILE_BASE_NAME-non-valid-target)
+run_cmake(TARGET_LINKER_FILE_BASE_NAME-non-valid-target)
 run_cmake(TARGET_PROPERTY-LOCATION)
 run_cmake(TARGET_PROPERTY-SOURCES)
 run_cmake(LINK_ONLY-not-linking)
@@ -78,15 +78,15 @@ run_cmake(FILTER-Include)
 run_cmake(ImportedTarget-TARGET_BUNDLE_DIR)
 run_cmake(ImportedTarget-TARGET_BUNDLE_CONTENT_DIR)
 run_cmake(ImportedTarget-TARGET_PDB_FILE)
-run_cmake(ImportedTarget-TARGET_PDB_OUTPUT_NAME)
+run_cmake(ImportedTarget-TARGET_PDB_FILE_BASE_NAME)
 if(LINKER_SUPPORTS_PDB)
   run_cmake(NonValidTarget-TARGET_PDB_FILE)
   run_cmake(ValidTarget-TARGET_PDB_FILE)
-  run_cmake(NonValidTarget-TARGET_PDB_OUTPUT_NAME)
-  run_cmake(ValidTarget-TARGET_PDB_OUTPUT_NAME)
+  run_cmake(NonValidTarget-TARGET_PDB_FILE_BASE_NAME)
+  run_cmake(ValidTarget-TARGET_PDB_FILE_BASE_NAME)
 else()
   run_cmake(NonValidCompiler-TARGET_PDB_FILE)
-  run_cmake(NonValidCompiler-TARGET_PDB_OUTPUT_NAME)
+  run_cmake(NonValidCompiler-TARGET_PDB_FILE_BASE_NAME)
 endif()
 
 set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0085:STRING=OLD)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-check.cmake
new file mode 100644
index 0000000..793edb1
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_BASE_NAME-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target-check.cmake
new file mode 100644
index 0000000..793edb1
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_BASE_NAME-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target.cmake
similarity index 51%
rename from Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target.cmake
rename to Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target.cmake
index 548a2d7..aa54b31 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target.cmake
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target.cmake
@@ -17,11 +17,11 @@ add_library (static1 STATIC IMPORTED)
 
 string (APPEND GENERATE_CONTENT [[
 
-check_value ("TARGET_OUTPUT_NAME executable default" "$<TARGET_OUTPUT_NAME:exec1>" "exec1")
-check_value ("TARGET_OUTPUT_NAME shared default" "$<TARGET_OUTPUT_NAME:shared1>" "shared1")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker default" "$<TARGET_LINKER_OUTPUT_NAME:shared1>" "shared1")
-check_value ("TARGET_OUTPUT_NAME static default" "$<TARGET_OUTPUT_NAME:static1>" "static1")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker default" "$<TARGET_LINKER_OUTPUT_NAME:static1>" "static1")
+check_value ("TARGET_FILE_BASE_NAME executable default" "$<TARGET_FILE_BASE_NAME:exec1>" "exec1")
+check_value ("TARGET_FILE_BASE_NAME shared default" "$<TARGET_FILE_BASE_NAME:shared1>" "shared1")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker default" "$<TARGET_LINKER_FILE_BASE_NAME:shared1>" "shared1")
+check_value ("TARGET_FILE_BASE_NAME static default" "$<TARGET_FILE_BASE_NAME:static1>" "static1")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker default" "$<TARGET_LINKER_FILE_BASE_NAME:static1>" "static1")
 ]])
 
 
@@ -34,11 +34,11 @@ set_property (TARGET static2 PROPERTY OUTPUT_NAME static2_custom)
 
 string (APPEND GENERATE_CONTENT [[
 
-check_value ("TARGET_OUTPUT_NAME executable custom" "$<TARGET_OUTPUT_NAME:exec2>" "exec2_custom")
-check_value ("TARGET_OUTPUT_NAME shared custom" "$<TARGET_OUTPUT_NAME:shared2>" "shared2_custom")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker custom" "$<TARGET_LINKER_OUTPUT_NAME:shared2>" "shared2_custom")
-check_value ("TARGET_OUTPUT_NAME static custom" "$<TARGET_OUTPUT_NAME:static2>" "static2_custom")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker custom" "$<TARGET_LINKER_OUTPUT_NAME:static2>" "static2_custom")
+check_value ("TARGET_FILE_BASE_NAME executable custom" "$<TARGET_FILE_BASE_NAME:exec2>" "exec2_custom")
+check_value ("TARGET_FILE_BASE_NAME shared custom" "$<TARGET_FILE_BASE_NAME:shared2>" "shared2_custom")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker custom" "$<TARGET_LINKER_FILE_BASE_NAME:shared2>" "shared2_custom")
+check_value ("TARGET_FILE_BASE_NAME static custom" "$<TARGET_FILE_BASE_NAME:static2>" "static2_custom")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker custom" "$<TARGET_LINKER_FILE_BASE_NAME:static2>" "static2_custom")
 ]])
 
 
@@ -60,11 +60,11 @@ set_property (TARGET static3 PROPERTY PDB_NAME static3_pdb)
 
 string (APPEND GENERATE_CONTENT [[
 
-check_value ("TARGET_OUTPUT_NAME executable all properties" "$<TARGET_OUTPUT_NAME:exec3>" "exec3_runtime")
-check_value ("TARGET_OUTPUT_NAME shared all properties" "$<TARGET_OUTPUT_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_runtime,shared3_library>")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker all properties" "$<TARGET_LINKER_OUTPUT_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_archive,shared3_library>")
-check_value ("TARGET_OUTPUT_NAME static all properties" "$<TARGET_OUTPUT_NAME:static3>" "static3_archive")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker all properties" "$<TARGET_LINKER_OUTPUT_NAME:static3>" "static3_archive")
+check_value ("TARGET_FILE_BASE_NAME executable all properties" "$<TARGET_FILE_BASE_NAME:exec3>" "exec3_runtime")
+check_value ("TARGET_FILE_BASE_NAME shared all properties" "$<TARGET_FILE_BASE_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_runtime,shared3_library>")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker all properties" "$<TARGET_LINKER_FILE_BASE_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_archive,shared3_library>")
+check_value ("TARGET_FILE_BASE_NAME static all properties" "$<TARGET_FILE_BASE_NAME:static3>" "static3_archive")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker all properties" "$<TARGET_LINKER_FILE_BASE_NAME:static3>" "static3_archive")
 ]])
 
 
@@ -75,5 +75,5 @@ if(_isMultiConfig)
   set(GENERATE_CONDITION CONDITION $<CONFIG:${FIRST_CONFIG}>)
 endif()
 
-file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_OUTPUT_NAME-generated.cmake"
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_BASE_NAME-generated.cmake"
   CONTENT "${GENERATE_CONTENT}" ${GENERATE_CONDITION})
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-result.txt
similarity index 100%
rename from Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-result.txt
rename to Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-result.txt
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-stderr.txt
new file mode 100644
index 0000000..ecb9e5d
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_FILE_BASE_NAME-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_FILE_BASE_NAME:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target.cmake
similarity index 66%
rename from Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake
rename to Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target.cmake
index 5248dfa..8622b7d 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target.cmake
@@ -3,5 +3,5 @@ add_custom_target(empty)
 
 file(GENERATE
   OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
-  CONTENT "[$<TARGET_OUTPUT_NAME:empty>]"
+  CONTENT "[$<TARGET_FILE_BASE_NAME:empty>]"
 )
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME.cmake
new file mode 100644
index 0000000..5ea53a0
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME.cmake
@@ -0,0 +1,96 @@
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_language (C)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_executable (exec1 empty.c)
+add_library (shared1 SHARED empty.c)
+add_library (static1 STATIC empty.c)
+
+string (APPEND GENERATE_CONTENT [[
+
+check_value ("TARGET_FILE_BASE_NAME executable default" "$<TARGET_FILE_BASE_NAME:exec1>" "exec1")
+check_value ("TARGET_FILE_BASE_NAME shared default" "$<TARGET_FILE_BASE_NAME:shared1>" "shared1")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker default" "$<TARGET_LINKER_FILE_BASE_NAME:shared1>" "shared1")
+check_value ("TARGET_FILE_BASE_NAME static default" "$<TARGET_FILE_BASE_NAME:static1>" "static1")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker default" "$<TARGET_LINKER_FILE_BASE_NAME:static1>" "static1")
+]])
+if (CMAKE_C_LINKER_SUPPORTS_PDB)
+  string(APPEND GENERATE_CONTENT [[
+check_value ("TARGET_PDB_FILE_BASE_NAME executable PDB default" "$<TARGET_PDB_FILE_BASE_NAME:exec1>" "exec1")
+check_value ("TARGET_PDB_FILE_BASE_NAME shared PDB default" "$<TARGET_PDB_FILE_BASE_NAME:shared1>" "shared1")
+]])
+endif()
+
+
+add_executable (exec2 empty.c)
+set_property (TARGET exec2 PROPERTY OUTPUT_NAME exec2_custom)
+add_library (shared2 SHARED empty.c)
+set_property (TARGET shared2 PROPERTY OUTPUT_NAME shared2_custom)
+add_library (static2 STATIC empty.c)
+set_property (TARGET static2 PROPERTY OUTPUT_NAME static2_custom)
+
+string (APPEND GENERATE_CONTENT [[
+
+check_value ("TARGET_FILE_BASE_NAME executable custom" "$<TARGET_FILE_BASE_NAME:exec2>" "exec2_custom")
+check_value ("TARGET_FILE_BASE_NAME shared custom" "$<TARGET_FILE_BASE_NAME:shared2>" "shared2_custom")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker custom" "$<TARGET_LINKER_FILE_BASE_NAME:shared2>" "shared2_custom")
+check_value ("TARGET_FILE_BASE_NAME static custom" "$<TARGET_FILE_BASE_NAME:static2>" "static2_custom")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker custom" "$<TARGET_LINKER_FILE_BASE_NAME:static2>" "static2_custom")
+]])
+if (CMAKE_C_LINKER_SUPPORTS_PDB)
+  string (APPEND GENERATE_CONTENT [[
+check_value ("TARGET_PDB_FILE_BASE_NAME executable PDB custom" "$<TARGET_PDB_FILE_BASE_NAME:exec2>" "exec2_custom")
+check_value ("TARGET_PDB_FILE_BASE_NAME shared PDB custom" "$<TARGET_PDB_FILE_BASE_NAME:shared2>" "shared2_custom")
+  ]])
+endif()
+
+add_executable (exec3 empty.c)
+set_property (TARGET exec3 PROPERTY RUNTIME_OUTPUT_NAME exec3_runtime)
+set_property (TARGET exec3 PROPERTY LIBRARY_OUTPUT_NAME exec3_library)
+set_property (TARGET exec3 PROPERTY ARCHIVE_OUTPUT_NAME exec3_archive)
+set_property (TARGET exec3 PROPERTY PDB_NAME exec3_pdb)
+add_library (shared3 SHARED empty.c)
+set_property (TARGET shared3 PROPERTY RUNTIME_OUTPUT_NAME shared3_runtime)
+set_property (TARGET shared3 PROPERTY LIBRARY_OUTPUT_NAME shared3_library)
+set_property (TARGET shared3 PROPERTY ARCHIVE_OUTPUT_NAME shared3_archive)
+set_property (TARGET shared3 PROPERTY PDB_NAME shared3_pdb)
+add_library (static3 STATIC empty.c)
+set_property (TARGET static3 PROPERTY RUNTIME_OUTPUT_NAME static3_runtime)
+set_property (TARGET static3 PROPERTY LIBRARY_OUTPUT_NAME static3_library)
+set_property (TARGET static3 PROPERTY ARCHIVE_OUTPUT_NAME static3_archive)
+set_property (TARGET static3 PROPERTY PDB_NAME static3_pdb)
+
+string (APPEND GENERATE_CONTENT [[
+
+check_value ("TARGET_FILE_BASE_NAME executable all properties" "$<TARGET_FILE_BASE_NAME:exec3>" "exec3_runtime")
+check_value ("TARGET_FILE_BASE_NAME shared all properties" "$<TARGET_FILE_BASE_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_runtime,shared3_library>")
+check_value ("TARGET_LINKER_FILE_BASE_NAME shared linker all properties" "$<TARGET_LINKER_FILE_BASE_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_archive,shared3_library>")
+check_value ("TARGET_FILE_BASE_NAME static all properties" "$<TARGET_FILE_BASE_NAME:static3>" "static3_archive")
+check_value ("TARGET_LINKER_FILE_BASE_NAME static linker all properties" "$<TARGET_LINKER_FILE_BASE_NAME:static3>" "static3_archive")
+]])
+if (CMAKE_C_LINKER_SUPPORTS_PDB)
+  string (APPEND GENERATE_CONTENT [[
+check_value ("TARGET_PDB_FILE_BASE_NAME executable PDB all properties" "$<TARGET_PDB_FILE_BASE_NAME:exec3>" "exec3_pdb")
+check_value ("TARGET_PDB_FILE_BASE_NAME shared PDB all properties" "$<TARGET_PDB_FILE_BASE_NAME:shared3>" "shared3_pdb")
+]])
+endif()
+
+
+unset(GENERATE_CONDITION)
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  list(GET CMAKE_CONFIGURATION_TYPES 0 FIRST_CONFIG)
+  set(GENERATE_CONDITION CONDITION $<CONFIG:${FIRST_CONFIG}>)
+endif()
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_BASE_NAME-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}" ${GENERATE_CONDITION})
diff --git a/Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-result.txt
similarity index 100%
rename from Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-result.txt
rename to Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-result.txt
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-stderr.txt
new file mode 100644
index 0000000..1ae2f2c
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_LINKER_FILE_BASE_NAME-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_LINKER_FILE_BASE_NAME:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target.cmake
similarity index 63%
rename from Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake
rename to Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target.cmake
index c439535..776fb4b 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target.cmake
@@ -3,5 +3,5 @@ add_custom_target(empty)
 
 file(GENERATE
   OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
-  CONTENT "[$<TARGET_LINKER_OUTPUT_NAME:empty>]"
+  CONTENT "[$<TARGET_LINKER_FILE_BASE_NAME:empty>]"
 )
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
deleted file mode 100644
index 0e09469..0000000
--- a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-CMake Error at TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake:[0-9]+ \(file\):
-  Error evaluating generator expression:
-
-    \$<TARGET_LINKER_OUTPUT_NAME:empty>
-
-  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-check.cmake
deleted file mode 100644
index fa4f2b9..0000000
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-check.cmake
+++ /dev/null
@@ -1,2 +0,0 @@
-
-include ("${RunCMake_TEST_BINARY_DIR}/TARGET_OUTPUT_NAME-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target-check.cmake
deleted file mode 100644
index fa4f2b9..0000000
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target-check.cmake
+++ /dev/null
@@ -1,2 +0,0 @@
-
-include ("${RunCMake_TEST_BINARY_DIR}/TARGET_OUTPUT_NAME-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
deleted file mode 100644
index 9672a99..0000000
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-CMake Error at TARGET_OUTPUT_NAME-non-valid-target.cmake:[0-9]+ \(file\):
-  Error evaluating generator expression:
-
-    \$<TARGET_OUTPUT_NAME:empty>
-
-  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME.cmake
deleted file mode 100644
index b7bae15..0000000
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME.cmake
+++ /dev/null
@@ -1,96 +0,0 @@
-
-cmake_minimum_required(VERSION 3.14)
-
-enable_language (C)
-
-set (GENERATE_CONTENT [[
-macro (CHECK_VALUE test_msg value expected)
-  if (NOT "${value}" STREQUAL "${expected}")
-    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
-  endif()
-endmacro()
-]])
-
-add_executable (exec1 empty.c)
-add_library (shared1 SHARED empty.c)
-add_library (static1 STATIC empty.c)
-
-string (APPEND GENERATE_CONTENT [[
-
-check_value ("TARGET_OUTPUT_NAME executable default" "$<TARGET_OUTPUT_NAME:exec1>" "exec1")
-check_value ("TARGET_OUTPUT_NAME shared default" "$<TARGET_OUTPUT_NAME:shared1>" "shared1")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker default" "$<TARGET_LINKER_OUTPUT_NAME:shared1>" "shared1")
-check_value ("TARGET_OUTPUT_NAME static default" "$<TARGET_OUTPUT_NAME:static1>" "static1")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker default" "$<TARGET_LINKER_OUTPUT_NAME:static1>" "static1")
-]])
-if (CMAKE_C_LINKER_SUPPORTS_PDB)
-  string(APPEND GENERATE_CONTENT [[
-check_value ("TARGET_PDB_OUTPUT_NAME executable PDB default" "$<TARGET_PDB_OUTPUT_NAME:exec1>" "exec1")
-check_value ("TARGET_PDB_OUTPUT_NAME shared PDB default" "$<TARGET_PDB_OUTPUT_NAME:shared1>" "shared1")
-]])
-endif()
-
-
-add_executable (exec2 empty.c)
-set_property (TARGET exec2 PROPERTY OUTPUT_NAME exec2_custom)
-add_library (shared2 SHARED empty.c)
-set_property (TARGET shared2 PROPERTY OUTPUT_NAME shared2_custom)
-add_library (static2 STATIC empty.c)
-set_property (TARGET static2 PROPERTY OUTPUT_NAME static2_custom)
-
-string (APPEND GENERATE_CONTENT [[
-
-check_value ("TARGET_OUTPUT_NAME executable custom" "$<TARGET_OUTPUT_NAME:exec2>" "exec2_custom")
-check_value ("TARGET_OUTPUT_NAME shared custom" "$<TARGET_OUTPUT_NAME:shared2>" "shared2_custom")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker custom" "$<TARGET_LINKER_OUTPUT_NAME:shared2>" "shared2_custom")
-check_value ("TARGET_OUTPUT_NAME static custom" "$<TARGET_OUTPUT_NAME:static2>" "static2_custom")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker custom" "$<TARGET_LINKER_OUTPUT_NAME:static2>" "static2_custom")
-]])
-if (CMAKE_C_LINKER_SUPPORTS_PDB)
-  string (APPEND GENERATE_CONTENT [[
-check_value ("TARGET_PDB_OUTPUT_NAME executable PDB custom" "$<TARGET_PDB_OUTPUT_NAME:exec2>" "exec2_custom")
-check_value ("TARGET_PDB_OUTPUT_NAME shared PDB custom" "$<TARGET_PDB_OUTPUT_NAME:shared2>" "shared2_custom")
-  ]])
-endif()
-
-add_executable (exec3 empty.c)
-set_property (TARGET exec3 PROPERTY RUNTIME_OUTPUT_NAME exec3_runtime)
-set_property (TARGET exec3 PROPERTY LIBRARY_OUTPUT_NAME exec3_library)
-set_property (TARGET exec3 PROPERTY ARCHIVE_OUTPUT_NAME exec3_archive)
-set_property (TARGET exec3 PROPERTY PDB_NAME exec3_pdb)
-add_library (shared3 SHARED empty.c)
-set_property (TARGET shared3 PROPERTY RUNTIME_OUTPUT_NAME shared3_runtime)
-set_property (TARGET shared3 PROPERTY LIBRARY_OUTPUT_NAME shared3_library)
-set_property (TARGET shared3 PROPERTY ARCHIVE_OUTPUT_NAME shared3_archive)
-set_property (TARGET shared3 PROPERTY PDB_NAME shared3_pdb)
-add_library (static3 STATIC empty.c)
-set_property (TARGET static3 PROPERTY RUNTIME_OUTPUT_NAME static3_runtime)
-set_property (TARGET static3 PROPERTY LIBRARY_OUTPUT_NAME static3_library)
-set_property (TARGET static3 PROPERTY ARCHIVE_OUTPUT_NAME static3_archive)
-set_property (TARGET static3 PROPERTY PDB_NAME static3_pdb)
-
-string (APPEND GENERATE_CONTENT [[
-
-check_value ("TARGET_OUTPUT_NAME executable all properties" "$<TARGET_OUTPUT_NAME:exec3>" "exec3_runtime")
-check_value ("TARGET_OUTPUT_NAME shared all properties" "$<TARGET_OUTPUT_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_runtime,shared3_library>")
-check_value ("TARGET_LINKER_OUTPUT_NAME shared linker all properties" "$<TARGET_LINKER_OUTPUT_NAME:shared3>" "$<IF:$<IN_LIST:$<PLATFORM_ID>,Windows$<SEMICOLON>CYGWIN>,shared3_archive,shared3_library>")
-check_value ("TARGET_OUTPUT_NAME static all properties" "$<TARGET_OUTPUT_NAME:static3>" "static3_archive")
-check_value ("TARGET_LINKER_OUTPUT_NAME static linker all properties" "$<TARGET_LINKER_OUTPUT_NAME:static3>" "static3_archive")
-]])
-if (CMAKE_C_LINKER_SUPPORTS_PDB)
-  string (APPEND GENERATE_CONTENT [[
-check_value ("TARGET_PDB_OUTPUT_NAME executable PDB all properties" "$<TARGET_PDB_OUTPUT_NAME:exec3>" "exec3_pdb")
-check_value ("TARGET_PDB_OUTPUT_NAME shared PDB all properties" "$<TARGET_PDB_OUTPUT_NAME:shared3>" "shared3_pdb")
-]])
-endif()
-
-
-unset(GENERATE_CONDITION)
-get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
-if(_isMultiConfig)
-  list(GET CMAKE_CONFIGURATION_TYPES 0 FIRST_CONFIG)
-  set(GENERATE_CONDITION CONDITION $<CONFIG:${FIRST_CONFIG}>)
-endif()
-
-file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_OUTPUT_NAME-generated.cmake"
-  CONTENT "${GENERATE_CONTENT}" ${GENERATE_CONDITION})
diff --git a/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME-check.cmake b/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME-check.cmake
new file mode 100644
index 0000000..996d2d4
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME-check.cmake
@@ -0,0 +1,7 @@
+file(STRINGS ${RunCMake_TEST_BINARY_DIR}/test.txt TEST_TXT ENCODING UTF-8)
+
+list(GET TEST_TXT 0 PDB_FILE_BASE_NAME)
+
+if(NOT PDB_FILE_BASE_NAME MATCHES "empty")
+  set(RunCMake_TEST_FAILED "unexpected PDB_FILE_BASE_NAME [${PDB_FILE_BASE_NAME}]")
+endif()
diff --git a/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME.cmake b/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
similarity index 88%
rename from Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME.cmake
rename to Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
index ba70b43..cc53bdf 100644
--- a/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME.cmake
+++ b/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake
@@ -11,6 +11,6 @@ endif()
 
 file(GENERATE
   OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
-  CONTENT "$<TARGET_PDB_OUTPUT_NAME:empty>"
+  CONTENT "$<TARGET_PDB_FILE_BASE_NAME:empty>"
   ${GENERATE_CONDITION}
 )
diff --git a/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME-check.cmake b/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME-check.cmake
deleted file mode 100644
index 8d1103e..0000000
--- a/Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME-check.cmake
+++ /dev/null
@@ -1,7 +0,0 @@
-file(STRINGS ${RunCMake_TEST_BINARY_DIR}/test.txt TEST_TXT ENCODING UTF-8)
-
-list(GET TEST_TXT 0 PDB_OUTPUT_NAME)
-
-if(NOT PDB_OUTPUT_NAME MATCHES "empty")
-  set(RunCMake_TEST_FAILED "unexpected PDB_OUTPUT_NAME [${PDB_OUTPUT_NAME}]")
-endif()

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=536cca60ea1c037d751b03bf5da8385783856000
commit 536cca60ea1c037d751b03bf5da8385783856000
Author:     Alex Turbov <i.zaufi at gmail.com>
AuthorDate: Sat Apr 6 17:53:58 2019 +0800
Commit:     Brad King <brad.king at kitware.com>
CommitDate: Mon Apr 15 11:06:06 2019 -0400

    string: introduce `REPEAT` sub-command

diff --git a/Help/command/string.rst b/Help/command/string.rst
index 893fb43..2e89d7b 100644
--- a/Help/command/string.rst
+++ b/Help/command/string.rst
@@ -28,6 +28,7 @@ Synopsis
     string(`SUBSTRING`_ <string> <begin> <length> <out-var>)
     string(`STRIP`_ <string> <out-var>)
     string(`GENEX_STRIP`_ <string> <out-var>)
+    string(`REPEAT`_ <string> <count> <out-var>)
 
   `Comparison`_
     string(`COMPARE`_ <op> <string1> <string2> <out-var>)
@@ -269,6 +270,14 @@ trailing spaces removed.
 Strip any :manual:`generator expressions <cmake-generator-expressions(7)>`
 from the ``input string`` and store the result in the ``output variable``.
 
+.. _REPEAT:
+
+.. code-block:: cmake
+
+  string(REPEAT <input string> <count> <output variable>)
+
+Produce the output string as repetion of ``input string`` ``count`` times.
+
 Comparison
 ^^^^^^^^^^
 
diff --git a/Help/release/dev/string-repeat.rst b/Help/release/dev/string-repeat.rst
new file mode 100644
index 0000000..4be0d5c
--- /dev/null
+++ b/Help/release/dev/string-repeat.rst
@@ -0,0 +1,4 @@
+string-repeat
+--------------
+
+* The :command:`string` learned a new sub-command ``REPEAT``.
diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx
index 252d985..998f904 100644
--- a/Source/cmStringCommand.cxx
+++ b/Source/cmStringCommand.cxx
@@ -1,9 +1,13 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
+#define _SCL_SECURE_NO_WARNINGS
+
 #include "cmStringCommand.h"
 
 #include "cmsys/RegularExpression.hxx"
+#include <algorithm>
 #include <ctype.h>
+#include <iterator>
 #include <memory> // IWYU pragma: keep
 #include <sstream>
 #include <stdio.h>
@@ -13,6 +17,7 @@
 #include "cmCryptoHash.h"
 #include "cmGeneratorExpression.h"
 #include "cmMakefile.h"
+#include "cmMessageType.h"
 #include "cmRange.h"
 #include "cmStringReplaceHelper.h"
 #include "cmSystemTools.h"
@@ -79,6 +84,9 @@ bool cmStringCommand::InitialPass(std::vector<std::string> const& args,
   if (subCommand == "STRIP") {
     return this->HandleStripCommand(args);
   }
+  if (subCommand == "REPEAT") {
+    return this->HandleRepeatCommand(args);
+  }
   if (subCommand == "RANDOM") {
     return this->HandleRandomCommand(args);
   }
@@ -709,6 +717,59 @@ bool cmStringCommand::HandleStripCommand(std::vector<std::string> const& args)
   return true;
 }
 
+bool cmStringCommand::HandleRepeatCommand(std::vector<std::string> const& args)
+{
+  // `string(REPEAT "<str>" <times> OUTPUT_VARIABLE)`
+  enum ArgPos : std::size_t
+  {
+    SUB_COMMAND,
+    VALUE,
+    TIMES,
+    OUTPUT_VARIABLE,
+    TOTAL_ARGS
+  };
+
+  if (args.size() != ArgPos::TOTAL_ARGS) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      "sub-command REPEAT requires three arguments.");
+    return true;
+  }
+
+  unsigned long times;
+  if (!cmSystemTools::StringToULong(args[ArgPos::TIMES].c_str(), &times)) {
+    this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
+                                 "repeat count is not a positive number.");
+    return true;
+  }
+
+  const auto& stringValue = args[ArgPos::VALUE];
+  const auto& variableName = args[ArgPos::OUTPUT_VARIABLE];
+  const auto inStringLength = stringValue.size();
+
+  std::string result;
+  switch (inStringLength) {
+    case 0u:
+      // Nothing to do for zero length input strings
+      break;
+    case 1u:
+      // NOTE If the string to repeat consists of the only character,
+      // use the appropriate constructor.
+      result = std::string(times, stringValue[0]);
+      break;
+    default:
+      result = std::string(inStringLength * times, char{});
+      for (auto i = 0u; i < times; ++i) {
+        std::copy(cm::cbegin(stringValue), cm::cend(stringValue),
+                  &result[i * inStringLength]);
+      }
+      break;
+  }
+
+  this->Makefile->AddDefinition(variableName, result.c_str());
+  return true;
+}
+
 bool cmStringCommand::HandleRandomCommand(std::vector<std::string> const& args)
 {
   if (args.size() < 2 || args.size() == 3 || args.size() == 5) {
diff --git a/Source/cmStringCommand.h b/Source/cmStringCommand.h
index cbff73e..acde605 100644
--- a/Source/cmStringCommand.h
+++ b/Source/cmStringCommand.h
@@ -51,6 +51,7 @@ protected:
   bool HandleConcatCommand(std::vector<std::string> const& args);
   bool HandleJoinCommand(std::vector<std::string> const& args);
   bool HandleStripCommand(std::vector<std::string> const& args);
+  bool HandleRepeatCommand(std::vector<std::string> const& args);
   bool HandleRandomCommand(std::vector<std::string> const& args);
   bool HandleFindCommand(std::vector<std::string> const& args);
   bool HandleTimestampCommand(std::vector<std::string> const& args);
diff --git a/Tests/RunCMake/string/Repeat.cmake b/Tests/RunCMake/string/Repeat.cmake
new file mode 100644
index 0000000..fc390aa
--- /dev/null
+++ b/Tests/RunCMake/string/Repeat.cmake
@@ -0,0 +1,45 @@
+string(REPEAT "q" 4 q_out)
+
+if(NOT DEFINED q_out)
+  message(FATAL_ERROR "q_out is not defined")
+endif()
+
+if(NOT q_out STREQUAL "qqqq")
+  message(FATAL_ERROR "unexpected result")
+endif()
+
+string(REPEAT "1234" 0 zero_out)
+
+if(NOT DEFINED zero_out)
+  message(FATAL_ERROR "zero_out is not defined")
+endif()
+
+if(NOT zero_out STREQUAL "")
+  message(FATAL_ERROR "unexpected result")
+endif()
+
+unset(zero_out)
+
+string(REPEAT "" 100 zero_out)
+
+if(NOT DEFINED zero_out)
+  message(FATAL_ERROR "zero_out is not defined")
+endif()
+
+if(NOT zero_out STREQUAL "")
+  message(FATAL_ERROR "unexpected result")
+endif()
+
+string(REPEAT "1" 1 one_out)
+
+if(NOT one_out STREQUAL "1")
+  message(FATAL_ERROR "unexpected result")
+endif()
+
+unset(one_out)
+
+string(REPEAT "one" 1 one_out)
+
+if(NOT one_out STREQUAL "one")
+  message(FATAL_ERROR "unexpected result")
+endif()
diff --git a/Tests/RunCMake/string/RepeatNegativeCount-result.txt b/Tests/RunCMake/string/RepeatNegativeCount-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNegativeCount-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/string/RepeatNegativeCount-stderr.txt b/Tests/RunCMake/string/RepeatNegativeCount-stderr.txt
new file mode 100644
index 0000000..bbd498e
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNegativeCount-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at RepeatNegativeCount.cmake:[0-9]+ \(string\):
+  repeat count is not a positive number.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/string/RepeatNegativeCount.cmake b/Tests/RunCMake/string/RepeatNegativeCount.cmake
new file mode 100644
index 0000000..769e7c0
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNegativeCount.cmake
@@ -0,0 +1 @@
+string(REPEAT "blah" -1 out)
diff --git a/Tests/RunCMake/string/RepeatNoArgs-result.txt b/Tests/RunCMake/string/RepeatNoArgs-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNoArgs-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/string/RepeatNoArgs-stderr.txt b/Tests/RunCMake/string/RepeatNoArgs-stderr.txt
new file mode 100644
index 0000000..5abcb3b
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNoArgs-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at RepeatNoArgs.cmake:[0-9]+ \(string\):
+  sub-command REPEAT requires three arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/string/RepeatNoArgs.cmake b/Tests/RunCMake/string/RepeatNoArgs.cmake
new file mode 100644
index 0000000..e327e99
--- /dev/null
+++ b/Tests/RunCMake/string/RepeatNoArgs.cmake
@@ -0,0 +1 @@
+string(REPEAT)
diff --git a/Tests/RunCMake/string/RunCMakeTest.cmake b/Tests/RunCMake/string/RunCMakeTest.cmake
index 211337a..c432b4e 100644
--- a/Tests/RunCMake/string/RunCMakeTest.cmake
+++ b/Tests/RunCMake/string/RunCMakeTest.cmake
@@ -33,3 +33,7 @@ run_cmake(UTF-16BE)
 run_cmake(UTF-16LE)
 run_cmake(UTF-32BE)
 run_cmake(UTF-32LE)
+
+run_cmake(Repeat)
+run_cmake(RepeatNoArgs)
+run_cmake(RepeatNegativeCount)

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=58f04b6ecf853e4ee5cce2bfb258fa7d4cc75b79
commit 58f04b6ecf853e4ee5cce2bfb258fa7d4cc75b79
Author:     Sebastian Holtermann <sebholt at xwmw.org>
AuthorDate: Fri Apr 12 13:01:09 2019 +0200
Commit:     Sebastian Holtermann <sebholt at xwmw.org>
CommitDate: Mon Apr 15 16:07:14 2019 +0200

    Autogen: Add ManySources test
    
    The QtAutogen/ManySources test generates a number of source, header, .ui and
    .qrc files that get AUTOMOC, AUTOUIC and AUTORCC processed.  This stresses the
    concurrency framework in `cmQtAutoMocUic` and should reveal any issues
    with that.

diff --git a/Tests/QtAutogen/ManySources/CMakeLists.txt b/Tests/QtAutogen/ManySources/CMakeLists.txt
new file mode 100644
index 0000000..df8a2a6
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.10)
+project(ManySources)
+include("../AutogenGuiTest.cmake")
+
+# Test AUTOMOC and AUTOUIC on many source files to stress the concurrent
+# parsing and processing framework.
+
+set(CSD ${CMAKE_CURRENT_SOURCE_DIR})
+set(CBD ${CMAKE_CURRENT_BINARY_DIR})
+
+set(SRCS main.cpp)
+set(MAIN_INCLUDES "\n// Item includes\n")
+set(MAIN_ITEMS "\n// Items\n")
+
+set(NUM 24)
+foreach(III RANGE 1 ${NUM})
+  configure_file(${CSD}/object.h.in ${CBD}/object_${III}.h)
+  configure_file(${CSD}/item.h.in ${CBD}/item_${III}.h)
+  configure_file(${CSD}/item.cpp.in ${CBD}/item_${III}.cpp)
+  configure_file(${CSD}/view.ui.in ${CBD}/view_${III}.ui)
+  configure_file(${CSD}/data.qrc.in ${CBD}/data_${III}.qrc)
+
+  list(APPEND SRCS ${CBD}/item_${III}.cpp)
+  list(APPEND SRCS ${CBD}/data_${III}.qrc)
+
+  string(APPEND MAIN_INCLUDES "#include \"item_${III}.h\"\n")
+  string(APPEND MAIN_ITEMS "Item_${III} item_${III};\n")
+  string(APPEND MAIN_ITEMS "item_${III}.TheSlot();\n")
+endforeach()
+
+configure_file(${CSD}/main.cpp.in ${CBD}/main.cpp)
+
+add_executable(manySources ${SRCS} ${CBD}/main.cpp)
+target_link_libraries(manySources ${QT_LIBRARIES})
+set_target_properties(manySources PROPERTIES AUTOMOC 1 AUTOUIC 1 AUTORCC 1)
diff --git a/Tests/QtAutogen/ManySources/data.qrc.in b/Tests/QtAutogen/ManySources/data.qrc.in
new file mode 100644
index 0000000..870d486
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/data.qrc.in
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+  <file>object_ at III@.h</file>
+  <file>item_ at III@.h</file>
+  <file>item_ at III@.cpp</file>
+</qresource>
+</RCC>
diff --git a/Tests/QtAutogen/ManySources/item.cpp.in b/Tests/QtAutogen/ManySources/item.cpp.in
new file mode 100644
index 0000000..c34ad16
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/item.cpp.in
@@ -0,0 +1,27 @@
+#include "item_ at III@.h"
+#include "object_ at III@.h"
+// AUTOUIC include
+#include <ui_view_ at III@.h>
+
+class LocalObject_ at III@ : public QObject
+{
+  Q_OBJECT;
+
+public:
+  LocalObject_ at III@() = default;
+  ~LocalObject_ at III@() = default;
+};
+
+void Item_ at III@ ::TheSlot()
+{
+  LocalObject_ at III@ localObject;
+  Object_ at III@ obj;
+  obj.ObjectSlot();
+
+  Ui_View_ at III@ ui_view;
+}
+
+// AUTOMOC includes
+#include "item_ at III@.moc"
+#include "moc_item_ at III@.cpp"
+#include "moc_object_ at III@.cpp"
diff --git a/Tests/QtAutogen/ManySources/item.h.in b/Tests/QtAutogen/ManySources/item.h.in
new file mode 100644
index 0000000..67ad794
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/item.h.in
@@ -0,0 +1,15 @@
+#ifndef ITEM_ at III@HPP
+#define ITEM_ at III@HPP
+
+#include <QObject>
+
+class Item_ at III@ : public QObject
+{
+  Q_OBJECT
+
+public:
+  Q_SLOT
+  void TheSlot();
+};
+
+#endif
diff --git a/Tests/QtAutogen/ManySources/main.cpp.in b/Tests/QtAutogen/ManySources/main.cpp.in
new file mode 100644
index 0000000..e1dda40
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/main.cpp.in
@@ -0,0 +1,7 @@
+ at MAIN_INCLUDES@
+
+int main(int argv, char** args)
+{
+  @MAIN_ITEMS@
+  return 0;
+}
diff --git a/Tests/QtAutogen/ManySources/object.h.in b/Tests/QtAutogen/ManySources/object.h.in
new file mode 100644
index 0000000..a747cbc
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/object.h.in
@@ -0,0 +1,15 @@
+#ifndef OBJECT_ at III@H
+#define OBJECT_ at III@H
+
+#include <QObject>
+
+class Object_ at III@ : public QObject
+{
+  Q_OBJECT
+
+public:
+  Q_SLOT
+  void ObjectSlot(){};
+};
+
+#endif
diff --git a/Tests/QtAutogen/ManySources/view.ui.in b/Tests/QtAutogen/ManySources/view.ui.in
new file mode 100644
index 0000000..6901fe3
--- /dev/null
+++ b/Tests/QtAutogen/ManySources/view.ui.in
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>View_ at III@</class>
+ <widget class="QWidget" name="Base">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QTreeView" name="treeView"/>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Tests/QtAutogen/Tests.cmake b/Tests/QtAutogen/Tests.cmake
index 096d5e3..6771828 100644
--- a/Tests/QtAutogen/Tests.cmake
+++ b/Tests/QtAutogen/Tests.cmake
@@ -5,6 +5,7 @@ ADD_AUTOGEN_TEST(AutogenTargetDepends)
 ADD_AUTOGEN_TEST(Complex QtAutogen)
 ADD_AUTOGEN_TEST(GlobalAutogenTarget)
 ADD_AUTOGEN_TEST(LowMinimumVersion lowMinimumVersion)
+ADD_AUTOGEN_TEST(ManySources manySources)
 ADD_AUTOGEN_TEST(MocOnly mocOnly)
 ADD_AUTOGEN_TEST(MocOptions mocOptions)
 ADD_AUTOGEN_TEST(ObjectLibrary someProgram)

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=a3f062091f488237c0151f3f4753e0668f37c60d
commit a3f062091f488237c0151f3f4753e0668f37c60d
Author:     Sebastian Holtermann <sebholt at xwmw.org>
AuthorDate: Fri Apr 12 10:56:08 2019 +0200
Commit:     Sebastian Holtermann <sebholt at xwmw.org>
CommitDate: Mon Apr 15 16:07:13 2019 +0200

    Autogen: Rename `cmQtAutoGeneratorMocUic` class to `cmQtAutoMocUic`
    
    The class name `cmQtAutoGeneratorMocUic` is long and cumbersome.  This renames
    it to `cmQtAutoMocUic`.

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index fcea2e3..49f237f 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -350,8 +350,8 @@ set(SRCS
   cmQtAutoGenGlobalInitializer.h
   cmQtAutoGenInitializer.cxx
   cmQtAutoGenInitializer.h
-  cmQtAutoGeneratorMocUic.cxx
-  cmQtAutoGeneratorMocUic.h
+  cmQtAutoMocUic.cxx
+  cmQtAutoMocUic.h
   cmQtAutoRcc.cxx
   cmQtAutoRcc.h
   cmRST.cxx
diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoMocUic.cxx
similarity index 94%
rename from Source/cmQtAutoGeneratorMocUic.cxx
rename to Source/cmQtAutoMocUic.cxx
index 80684b6..75c5d8a 100644
--- a/Source/cmQtAutoGeneratorMocUic.cxx
+++ b/Source/cmQtAutoMocUic.cxx
@@ -1,6 +1,6 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmQtAutoGeneratorMocUic.h"
+#include "cmQtAutoMocUic.h"
 
 #include <algorithm>
 #include <array>
@@ -24,7 +24,7 @@
 
 // -- Class methods
 
-std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath(
+std::string cmQtAutoMocUic::BaseSettingsT::AbsoluteBuildPath(
   std::string const& relativePath) const
 {
   return FileSys->CollapseFullPath(relativePath, AutogenBuildDir);
@@ -35,7 +35,7 @@ std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath(
  * appending different header extensions
  * @return True on success
  */
-bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader(
+bool cmQtAutoMocUic::BaseSettingsT::FindHeader(
   std::string& header, std::string const& testBasePath) const
 {
   for (std::string const& ext : HeaderExtensions) {
@@ -50,8 +50,7 @@ bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader(
   return false;
 }
 
-bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped(
-  std::string const& fileName) const
+bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const
 {
   return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
 }
@@ -60,7 +59,7 @@ bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped(
  * @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 cmQtAutoMocUic::MocSettingsT::FindMacro(
   std::string const& content) const
 {
   for (KeyExpT const& filter : MacroFilters) {
@@ -77,7 +76,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro(
   return std::string();
 }
 
-std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const
+std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const
 {
   std::string res;
   const auto itB = MacroFilters.cbegin();
@@ -99,7 +98,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const
   return res;
 }
 
-std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile(
+std::string cmQtAutoMocUic::MocSettingsT::FindIncludedFile(
   std::string const& sourcePath, std::string const& includeString) const
 {
   // Search in vicinity of the source
@@ -123,7 +122,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile(
   return std::string();
 }
 
-void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies(
+void cmQtAutoMocUic::MocSettingsT::FindDependencies(
   std::string const& content, std::set<std::string>& depends) const
 {
   if (!DependFilters.empty() && !content.empty()) {
@@ -147,27 +146,27 @@ void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies(
   }
 }
 
-bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped(
-  std::string const& fileName) const
+bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const
 {
   return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
 }
 
-void cmQtAutoGeneratorMocUic::JobT::LogError(GenT genType,
-                                             std::string const& message) const
+void cmQtAutoMocUic::JobT::LogError(GenT genType,
+                                    std::string const& message) const
 {
   Gen()->AbortError();
   Gen()->Log().Error(genType, message);
 }
 
-void cmQtAutoGeneratorMocUic::JobT::LogFileError(
-  GenT genType, std::string const& filename, std::string const& message) const
+void cmQtAutoMocUic::JobT::LogFileError(GenT genType,
+                                        std::string const& filename,
+                                        std::string const& message) const
 {
   Gen()->AbortError();
   Gen()->Log().ErrorFile(genType, filename, message);
 }
 
-void cmQtAutoGeneratorMocUic::JobT::LogCommandError(
+void cmQtAutoMocUic::JobT::LogCommandError(
   GenT genType, std::string const& message,
   std::vector<std::string> const& command, std::string const& output) const
 {
@@ -175,9 +174,9 @@ void cmQtAutoGeneratorMocUic::JobT::LogCommandError(
   Gen()->Log().ErrorCommand(genType, message, command, output);
 }
 
-bool cmQtAutoGeneratorMocUic::JobT::RunProcess(
-  GenT genType, cmWorkerPool::ProcessResultT& result,
-  std::vector<std::string> const& command)
+bool cmQtAutoMocUic::JobT::RunProcess(GenT genType,
+                                      cmWorkerPool::ProcessResultT& result,
+                                      std::vector<std::string> const& command)
 {
   // Log command
   if (Log().Verbose()) {
@@ -190,7 +189,7 @@ bool cmQtAutoGeneratorMocUic::JobT::RunProcess(
                                         Gen()->Base().AutogenBuildDir);
 }
 
-void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process()
+void cmQtAutoMocUic::JobMocPredefsT::Process()
 {
   // (Re)generate moc_predefs.h on demand
   bool generate(false);
@@ -260,7 +259,7 @@ void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process()
   }
 }
 
-void cmQtAutoGeneratorMocUic::JobParseT::Process()
+void cmQtAutoMocUic::JobParseT::Process()
 {
   if (AutoMoc && Header) {
     // Don't parse header for moc if the file is included by a source already
@@ -297,7 +296,7 @@ void cmQtAutoGeneratorMocUic::JobParseT::Process()
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(MetaT const& meta)
+bool cmQtAutoMocUic::JobParseT::ParseMocSource(MetaT const& meta)
 {
   struct JobPre
   {
@@ -550,7 +549,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(MetaT const& meta)
   return true;
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(MetaT const& meta)
+bool cmQtAutoMocUic::JobParseT::ParseMocHeader(MetaT const& meta)
 {
   bool success = true;
   std::string const macroName = Gen()->Moc().FindMacro(meta.Content);
@@ -568,7 +567,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(MetaT const& meta)
   return success;
 }
 
-std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
+std::string cmQtAutoMocUic::JobParseT::MocStringHeaders(
   std::string const& fileBase) const
 {
   std::string res = fileBase;
@@ -578,7 +577,7 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
   return res;
 }
 
-std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
+std::string cmQtAutoMocUic::JobParseT::MocFindIncludedHeader(
   std::string const& includerDir, std::string const& includeBase)
 {
   std::string header;
@@ -601,7 +600,7 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
   return header;
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(MetaT const& meta)
+bool cmQtAutoMocUic::JobParseT::ParseUic(MetaT const& meta)
 {
   bool success = true;
   if (meta.Content.find("ui_") != std::string::npos) {
@@ -618,8 +617,8 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(MetaT const& meta)
   return success;
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
-  MetaT const& meta, std::string&& includeString)
+bool cmQtAutoMocUic::JobParseT::ParseUicInclude(MetaT const& meta,
+                                                std::string&& includeString)
 {
   bool success = false;
   std::string uiInputFile = UicFindIncludedFile(meta, includeString);
@@ -636,7 +635,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
   return success;
 }
 
-std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
+std::string cmQtAutoMocUic::JobParseT::UicFindIncludedFile(
   MetaT const& meta, std::string const& includeString)
 {
   std::string res;
@@ -698,7 +697,7 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
   return res;
 }
 
-void cmQtAutoGeneratorMocUic::JobPostParseT::Process()
+void cmQtAutoMocUic::JobPostParseT::Process()
 {
   if (Gen()->Moc().Enabled) {
     // Add mocs compilations fence job
@@ -708,7 +707,7 @@ void cmQtAutoGeneratorMocUic::JobPostParseT::Process()
   Gen()->WorkerPool().EmplaceJob<JobFinishT>();
 }
 
-void cmQtAutoGeneratorMocUic::JobMocsCompilationT::Process()
+void cmQtAutoMocUic::JobMocsCompilationT::Process()
 {
   // Compose mocs compilation file content
   std::string content =
@@ -750,14 +749,13 @@ void cmQtAutoGeneratorMocUic::JobMocsCompilationT::Process()
   }
 }
 
-void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies(
-  std::string const& content)
+void cmQtAutoMocUic::JobMocT::FindDependencies(std::string const& content)
 {
   Gen()->Moc().FindDependencies(content, Depends);
   DependsValid = true;
 }
 
-void cmQtAutoGeneratorMocUic::JobMocT::Process()
+void cmQtAutoMocUic::JobMocT::Process()
 {
   // Compute build file name
   if (!IncludeString.empty()) {
@@ -788,7 +786,7 @@ void cmQtAutoGeneratorMocUic::JobMocT::Process()
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired()
+bool cmQtAutoMocUic::JobMocT::UpdateRequired()
 {
   bool const verbose = Log().Verbose();
 
@@ -921,7 +919,7 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired()
   return false;
 }
 
-void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc()
+void cmQtAutoMocUic::JobMocT::GenerateMoc()
 {
   // Make sure the parent directory exists
   if (!FileSys().MakeParentDirectory(BuildFile)) {
@@ -972,7 +970,7 @@ void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc()
   }
 }
 
-void cmQtAutoGeneratorMocUic::JobUicT::Process()
+void cmQtAutoMocUic::JobUicT::Process()
 {
   // Compute build file name
   BuildFile = Gen()->Base().AutogenIncludeDir;
@@ -984,7 +982,7 @@ void cmQtAutoGeneratorMocUic::JobUicT::Process()
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired()
+bool cmQtAutoMocUic::JobUicT::UpdateRequired()
 {
   bool const verbose = Log().Verbose();
 
@@ -1040,7 +1038,7 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired()
   return false;
 }
 
-void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic()
+void cmQtAutoMocUic::JobUicT::GenerateUic()
 {
   // Make sure the parent directory exists
   if (!FileSys().MakeParentDirectory(BuildFile)) {
@@ -1089,12 +1087,12 @@ void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic()
   }
 }
 
-void cmQtAutoGeneratorMocUic::JobFinishT::Process()
+void cmQtAutoMocUic::JobFinishT::Process()
 {
   Gen()->AbortSuccess();
 }
 
-cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
+cmQtAutoMocUic::cmQtAutoMocUic()
   : Base_(&FileSys())
   , Moc_(&FileSys())
 {
@@ -1106,9 +1104,9 @@ cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
                              "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
 }
 
-cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() = default;
+cmQtAutoMocUic::~cmQtAutoMocUic() = default;
 
-bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
+bool cmQtAutoMocUic::Init(cmMakefile* makefile)
 {
   // -- Meta
   Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions();
@@ -1478,7 +1476,7 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
   return true;
 }
 
-bool cmQtAutoGeneratorMocUic::Process()
+bool cmQtAutoMocUic::Process()
 {
   SettingsFileRead();
   if (!CreateDirectories()) {
@@ -1496,7 +1494,7 @@ bool cmQtAutoGeneratorMocUic::Process()
   return SettingsFileWrite();
 }
 
-void cmQtAutoGeneratorMocUic::SettingsFileRead()
+void cmQtAutoMocUic::SettingsFileRead()
 {
   // Compose current settings strings
   {
@@ -1562,7 +1560,7 @@ void cmQtAutoGeneratorMocUic::SettingsFileRead()
   }
 }
 
-bool cmQtAutoGeneratorMocUic::SettingsFileWrite()
+bool cmQtAutoMocUic::SettingsFileWrite()
 {
   // Only write if any setting changed
   if (Moc().SettingsChanged || Uic().SettingsChanged) {
@@ -1597,7 +1595,7 @@ bool cmQtAutoGeneratorMocUic::SettingsFileWrite()
   return true;
 }
 
-bool cmQtAutoGeneratorMocUic::CreateDirectories()
+bool cmQtAutoMocUic::CreateDirectories()
 {
   // Create AUTOGEN include directory
   if (!FileSys().MakeDirectory(Base().AutogenIncludeDir)) {
@@ -1608,9 +1606,9 @@ bool cmQtAutoGeneratorMocUic::CreateDirectories()
   return true;
 }
 
-// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be
+// Private method that requires cmQtAutoMocUic::JobsMutex_ to be
 // locked
-void cmQtAutoGeneratorMocUic::Abort(bool error)
+void cmQtAutoMocUic::Abort(bool error)
 {
   if (error) {
     JobError_.store(true);
@@ -1618,8 +1616,7 @@ void cmQtAutoGeneratorMocUic::Abort(bool error)
   WorkerPool_.Abort();
 }
 
-bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(
-  cmWorkerPool::JobHandleT&& jobHandle)
+bool cmQtAutoMocUic::ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle)
 {
   JobMocT const& mocJob(static_cast<JobMocT&>(*jobHandle));
   // Do additional tests if this is an included moc job
@@ -1669,8 +1666,7 @@ bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(
   return WorkerPool_.PushJob(std::move(jobHandle));
 }
 
-bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(
-  cmWorkerPool::JobHandleT&& jobHandle)
+bool cmQtAutoMocUic::ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle)
 {
   const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle));
   {
@@ -1717,14 +1713,13 @@ bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(
   return WorkerPool_.PushJob(std::move(jobHandle));
 }
 
-bool cmQtAutoGeneratorMocUic::ParallelMocIncluded(
-  std::string const& sourceFile)
+bool cmQtAutoMocUic::ParallelMocIncluded(std::string const& sourceFile)
 {
   std::lock_guard<std::mutex> guard(MocMetaMutex_);
   return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end());
 }
 
-std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister(
+std::string cmQtAutoMocUic::ParallelMocAutoRegister(
   std::string const& baseName)
 {
   std::string res;
diff --git a/Source/cmQtAutoGeneratorMocUic.h b/Source/cmQtAutoMocUic.h
similarity index 95%
rename from Source/cmQtAutoGeneratorMocUic.h
rename to Source/cmQtAutoMocUic.h
index 4efc2c6..3902abb 100644
--- a/Source/cmQtAutoGeneratorMocUic.h
+++ b/Source/cmQtAutoMocUic.h
@@ -1,7 +1,7 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
-#ifndef cmQtAutoGeneratorMocUic_h
-#define cmQtAutoGeneratorMocUic_h
+#ifndef cmQtAutoMocUic_h
+#define cmQtAutoMocUic_h
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
@@ -24,14 +24,14 @@
 class cmMakefile;
 
 // @brief AUTOMOC and AUTOUIC generator
-class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator
+class cmQtAutoMocUic : public cmQtAutoGenerator
 {
 public:
-  cmQtAutoGeneratorMocUic();
-  ~cmQtAutoGeneratorMocUic() override;
+  cmQtAutoMocUic();
+  ~cmQtAutoMocUic() override;
 
-  cmQtAutoGeneratorMocUic(cmQtAutoGeneratorMocUic const&) = delete;
-  cmQtAutoGeneratorMocUic& operator=(cmQtAutoGeneratorMocUic const&) = delete;
+  cmQtAutoMocUic(cmQtAutoMocUic const&) = delete;
+  cmQtAutoMocUic& operator=(cmQtAutoMocUic const&) = delete;
 
 public:
   // -- Types
@@ -183,9 +183,9 @@ public:
     }
 
     //! Get the generator. Only valid during Process() call!
-    cmQtAutoGeneratorMocUic* Gen() const
+    cmQtAutoMocUic* Gen() const
     {
-      return static_cast<cmQtAutoGeneratorMocUic*>(UserData());
+      return static_cast<cmQtAutoMocUic*>(UserData());
     };
 
     //! Get the file system interface. Only valid during Process() call!
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index c18c256..3c75957 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -7,7 +7,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
-#include "cmQtAutoGeneratorMocUic.h"
+#include "cmQtAutoMocUic.h"
 #include "cmQtAutoRcc.h"
 #include "cmRange.h"
 #include "cmState.h"
@@ -1018,7 +1018,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args)
 
 #ifdef CMAKE_BUILD_WITH_CMAKE
     if ((args[1] == "cmake_autogen") && (args.size() >= 4)) {
-      cmQtAutoGeneratorMocUic autoGen;
+      cmQtAutoMocUic autoGen;
       std::string const& infoDir = args[2];
       std::string const& config = args[3];
       return autoGen.Run(infoDir, config) ? 0 : 1;

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=8cb26a0a2ad57ca9012f97c7437711ee94f1a9db
commit 8cb26a0a2ad57ca9012f97c7437711ee94f1a9db
Author:     Sebastian Holtermann <sebholt at xwmw.org>
AuthorDate: Fri Apr 5 12:19:14 2019 +0200
Commit:     Sebastian Holtermann <sebholt at xwmw.org>
CommitDate: Mon Apr 15 16:07:13 2019 +0200

    Autogen: Factor out concurrency framework to cmWorkerPool class
    
    This factors out the concurrency framework in `cmQtAutoGeneratorMocUic` to a
    dedicated class `cmWorkerPool` which might be reused in other places.
    
    `cmWorkerPool` supports fence jobs that require that
    - all other jobs before in the queue have been processed before the fence
      job processing gets started,
    - no jobs later in the queue will be processed before the fence job processing
      has been completed.
    Fence jobs are needed where the completion of all previous jobs in the queue
    is a requirement for further processing.  E.g. in `cmQtAutoGeneratorMocUic`
    the generation of `mocs_compilation.cpp` requires that all previous
    source file parse jobs have been completed.

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 924d997..fcea2e3 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -391,6 +391,8 @@ set(SRCS
   cmVariableWatch.h
   cmVersion.cxx
   cmVersion.h
+  cmWorkerPool.cxx
+  cmWorkerPool.h
   cmWorkingDirectory.cxx
   cmWorkingDirectory.h
   cmXMLParser.cxx
diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx
index f115016..6fbea82 100644
--- a/Source/cmQtAutoGenerator.cxx
+++ b/Source/cmQtAutoGenerator.cxx
@@ -14,12 +14,6 @@
 #include "cmSystemTools.h"
 #include "cmake.h"
 
-#include <algorithm>
-#include <sstream>
-#include <utility>
-
-// -- Class methods
-
 cmQtAutoGenerator::Logger::Logger()
 {
   // Initialize logger
@@ -431,232 +425,6 @@ bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
   return cmQtAutoGenerator::MakeParentDirectory(filename);
 }
 
-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_.data();
-  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;
-}
-
-static std::string getUVError(const char* prefixString, int uvErrorCode)
-{
-  std::ostringstream ost;
-  ost << prefixString << ": " << uv_strerror(uvErrorCode);
-  return ost.str();
-}
-
-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_.data());
-    UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
-    UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
-    UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
-    UVOptions_.stdio = UVOptionsStdIO_.data();
-
-    // -- Spawn process
-    int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
-    if (uvErrorCode != 0) {
-      Result()->ErrorMessage =
-        getUVError("libuv process spawn failed ", uvErrorCode);
-    }
-  }
-  // -- 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() = default;
 
 cmQtAutoGenerator::~cmQtAutoGenerator() = default;
diff --git a/Source/cmQtAutoGenerator.h b/Source/cmQtAutoGenerator.h
index 479d357..437fa20 100644
--- a/Source/cmQtAutoGenerator.h
+++ b/Source/cmQtAutoGenerator.h
@@ -7,14 +7,8 @@
 
 #include "cmFilePathChecksum.h"
 #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>
 
@@ -137,102 +131,6 @@ public:
     cmFilePathChecksum FilePathChecksum_;
   };
 
-  /// @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;
-    };
-
-    // -- 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_;
-  };
-
 public:
   // -- Constructors
   cmQtAutoGenerator();
diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoGeneratorMocUic.cxx
index ec1a1aa..80684b6 100644
--- a/Source/cmQtAutoGeneratorMocUic.cxx
+++ b/Source/cmQtAutoGeneratorMocUic.cxx
@@ -4,7 +4,7 @@
 
 #include <algorithm>
 #include <array>
-#include <cstddef>
+#include <deque>
 #include <list>
 #include <memory>
 #include <set>
@@ -153,11 +153,118 @@ bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped(
   return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
 }
 
-void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobT::LogError(GenT genType,
+                                             std::string const& message) const
+{
+  Gen()->AbortError();
+  Gen()->Log().Error(genType, message);
+}
+
+void cmQtAutoGeneratorMocUic::JobT::LogFileError(
+  GenT genType, std::string const& filename, std::string const& message) const
+{
+  Gen()->AbortError();
+  Gen()->Log().ErrorFile(genType, filename, message);
+}
+
+void cmQtAutoGeneratorMocUic::JobT::LogCommandError(
+  GenT genType, std::string const& message,
+  std::vector<std::string> const& command, std::string const& output) const
+{
+  Gen()->AbortError();
+  Gen()->Log().ErrorCommand(genType, message, command, output);
+}
+
+bool cmQtAutoGeneratorMocUic::JobT::RunProcess(
+  GenT genType, cmWorkerPool::ProcessResultT& result,
+  std::vector<std::string> const& command)
+{
+  // Log command
+  if (Log().Verbose()) {
+    std::string msg = "Running command:\n";
+    msg += QuotedCommand(command);
+    msg += '\n';
+    Log().Info(genType, msg);
+  }
+  return cmWorkerPool::JobT::RunProcess(result, command,
+                                        Gen()->Base().AutogenBuildDir);
+}
+
+void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process()
+{
+  // (Re)generate moc_predefs.h on demand
+  bool generate(false);
+  bool fileExists(FileSys().FileExists(Gen()->Moc().PredefsFileAbs));
+  if (!fileExists) {
+    if (Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(Gen()->Moc().PredefsFileRel);
+      reason += " because it doesn't exist";
+      Log().Info(GenT::MOC, reason);
+    }
+    generate = true;
+  } else if (Gen()->Moc().SettingsChanged) {
+    if (Log().Verbose()) {
+      std::string reason = "Generating ";
+      reason += Quoted(Gen()->Moc().PredefsFileRel);
+      reason += " because the settings changed.";
+      Log().Info(GenT::MOC, reason);
+    }
+    generate = true;
+  }
+  if (generate) {
+    cmWorkerPool::ProcessResultT result;
+    {
+      // Compose command
+      std::vector<std::string> cmd = Gen()->Moc().PredefsCmd;
+      // Add includes
+      cmd.insert(cmd.end(), Gen()->Moc().Includes.begin(),
+                 Gen()->Moc().Includes.end());
+      // Add definitions
+      for (std::string const& def : Gen()->Moc().Definitions) {
+        cmd.push_back("-D" + def);
+      }
+      // Execute command
+      if (!RunProcess(GenT::MOC, result, cmd)) {
+        std::string emsg = "The content generation command for ";
+        emsg += Quoted(Gen()->Moc().PredefsFileRel);
+        emsg += " failed.\n";
+        emsg += result.ErrorMessage;
+        LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
+      }
+    }
+
+    // (Re)write predefs file only on demand
+    if (!result.error()) {
+      if (!fileExists ||
+          FileSys().FileDiffers(Gen()->Moc().PredefsFileAbs, result.StdOut)) {
+        if (FileSys().FileWrite(Gen()->Moc().PredefsFileAbs, result.StdOut)) {
+          // Success
+        } else {
+          std::string emsg = "Writing ";
+          emsg += Quoted(Gen()->Moc().PredefsFileRel);
+          emsg += " failed.";
+          LogFileError(GenT::MOC, Gen()->Moc().PredefsFileAbs, emsg);
+        }
+      } else {
+        // Touch to update the time stamp
+        if (Log().Verbose()) {
+          std::string msg = "Touching ";
+          msg += Quoted(Gen()->Moc().PredefsFileRel);
+          msg += ".";
+          Log().Info(GenT::MOC, msg);
+        }
+        FileSys().Touch(Gen()->Moc().PredefsFileAbs);
+      }
+    }
+  }
+}
+
+void cmQtAutoGeneratorMocUic::JobParseT::Process()
 {
   if (AutoMoc && Header) {
     // Don't parse header for moc if the file is included by a source already
-    if (wrk.Gen().ParallelMocIncluded(FileName)) {
+    if (Gen()->ParallelMocIncluded(FileName)) {
       AutoMoc = false;
     }
   }
@@ -165,35 +272,32 @@ void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk)
   if (AutoMoc || AutoUic) {
     std::string error;
     MetaT meta;
-    if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) {
+    if (FileSys().FileRead(meta.Content, FileName, &error)) {
       if (!meta.Content.empty()) {
-        meta.FileDir = wrk.FileSys().SubDirPrefix(FileName);
-        meta.FileBase =
-          wrk.FileSys().GetFilenameWithoutLastExtension(FileName);
+        meta.FileDir = FileSys().SubDirPrefix(FileName);
+        meta.FileBase = FileSys().GetFilenameWithoutLastExtension(FileName);
 
         bool success = true;
         if (AutoMoc) {
           if (Header) {
-            success = ParseMocHeader(wrk, meta);
+            success = ParseMocHeader(meta);
           } else {
-            success = ParseMocSource(wrk, meta);
+            success = ParseMocSource(meta);
           }
         }
         if (AutoUic && success) {
-          ParseUic(wrk, meta);
+          ParseUic(meta);
         }
       } else {
-        wrk.LogFileWarning(GenT::GEN, FileName, "The source file is empty");
+        Log().WarningFile(GenT::GEN, FileName, "The source file is empty");
       }
     } else {
-      wrk.LogFileError(GenT::GEN, FileName,
-                       "Could not read the file: " + error);
+      LogFileError(GenT::GEN, FileName, "Could not read the file: " + error);
     }
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
-                                                        MetaT const& meta)
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(MetaT const& meta)
 {
   struct JobPre
   {
@@ -211,7 +315,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
   };
 
   // Check if this source file contains a relevant macro
-  std::string const ownMacro = wrk.Moc().FindMacro(meta.Content);
+  std::string const ownMacro = Gen()->Moc().FindMacro(meta.Content);
 
   // Extract moc includes from file
   std::deque<MocInclude> mocIncsUsc;
@@ -220,11 +324,11 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
     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)) {
+      while (Gen()->Moc().RegExpInclude.find(contentChars, match)) {
         std::string incString = match.match(2);
-        std::string incDir(wrk.FileSys().SubDirPrefix(incString));
+        std::string incDir(FileSys().SubDirPrefix(incString));
         std::string incBase =
-          wrk.FileSys().GetFilenameWithoutLastExtension(incString);
+          FileSys().GetFilenameWithoutLastExtension(incString);
         if (cmHasLiteralPrefix(incBase, "moc_")) {
           // moc_<BASE>.cxx
           // Remove the moc_ part from the base name
@@ -253,10 +357,10 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
   // Process moc_<BASE>.cxx includes
   for (const MocInclude& mocInc : mocIncsUsc) {
     std::string const header =
-      MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
+      MocFindIncludedHeader(meta.FileDir, mocInc.Dir + mocInc.Base);
     if (!header.empty()) {
       // Check if header is skipped
-      if (wrk.Moc().skipped(header)) {
+      if (Gen()->Moc().skipped(header)) {
         continue;
       }
       // Register moc job
@@ -271,9 +375,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
         std::string emsg = "The file includes the moc file ";
         emsg += Quoted(mocInc.Inc);
         emsg += ", but the header ";
-        emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
+        emsg += Quoted(MocStringHeaders(mocInc.Base));
         emsg += " could not be found.";
-        wrk.LogFileError(GenT::MOC, FileName, emsg);
+        LogFileError(GenT::MOC, FileName, emsg);
       }
       return false;
     }
@@ -282,7 +386,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
   // Process <BASE>.moc includes
   for (const MocInclude& mocInc : mocIncsDot) {
     const bool ownMoc = (mocInc.Base == meta.FileBase);
-    if (wrk.Moc().RelaxedMode) {
+    if (Gen()->Moc().RelaxedMode) {
       // Relaxed mode
       if (!ownMacro.empty() && ownMoc) {
         // Add self
@@ -292,10 +396,10 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
         // 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);
+          MocFindIncludedHeader(meta.FileDir, mocInc.Dir + mocInc.Base);
         if (!header.empty()) {
           // Check if header is skipped
-          if (wrk.Moc().skipped(header)) {
+          if (Gen()->Moc().skipped(header)) {
             continue;
           }
           // Register moc job
@@ -305,14 +409,14 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
               std::string emsg = "The file includes the moc file ";
               emsg += Quoted(mocInc.Inc);
               emsg += ", but does not contain a ";
-              emsg += wrk.Moc().MacrosString();
+              emsg += Gen()->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(GenT::MOC, FileName, emsg);
+              Log().WarningFile(GenT::MOC, FileName, emsg);
             } else {
               std::string emsg = "The file includes the moc file ";
               emsg += Quoted(mocInc.Inc);
@@ -324,7 +428,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
               emsg += Quoted("moc_" + mocInc.Base + ".cpp");
               emsg += " for compatibility with strict mode.\n"
                       "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
-              wrk.LogFileWarning(GenT::MOC, FileName, emsg);
+              Log().WarningFile(GenT::MOC, FileName, emsg);
             }
           }
         } else {
@@ -334,9 +438,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
             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 += Quoted(MocStringHeaders(mocInc.Base));
             emsg += " could not be found.";
-            wrk.LogFileError(GenT::MOC, FileName, emsg);
+            LogFileError(GenT::MOC, FileName, emsg);
           }
           return false;
         }
@@ -352,9 +456,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
           std::string emsg = "The file includes the moc file ";
           emsg += Quoted(mocInc.Inc);
           emsg += ", but does not contain a ";
-          emsg += wrk.Moc().MacrosString();
+          emsg += Gen()->Moc().MacrosString();
           emsg += " macro.";
-          wrk.LogFileWarning(GenT::MOC, FileName, emsg);
+          Log().WarningFile(GenT::MOC, FileName, emsg);
         }
       } else {
         // Don't allow <BASE>.moc include other than self in strict mode
@@ -365,7 +469,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
                   "source file.\nThis is not supported. Include ";
           emsg += Quoted(meta.FileBase + ".moc");
           emsg += " to run moc on this source file.";
-          wrk.LogFileError(GenT::MOC, FileName, emsg);
+          LogFileError(GenT::MOC, FileName, emsg);
         }
         return false;
       }
@@ -379,7 +483,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
     // 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) {
+    if (Gen()->Moc().RelaxedMode && ownMocUscIncluded) {
       JobPre uscJobPre;
       // Remove underscore job request
       {
@@ -408,7 +512,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
         emsg += Quoted(meta.FileBase + ".moc");
         emsg += " for compatibility with strict mode.\n"
                 "(CMAKE_AUTOMOC_RELAXED_MODE warning)";
-        wrk.LogFileWarning(GenT::MOC, FileName, emsg);
+        Log().WarningFile(GenT::MOC, FileName, emsg);
       }
       // Add own source job
       jobs.emplace_back(
@@ -423,7 +527,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
         emsg += "!\nConsider to\n - add #include \"";
         emsg += meta.FileBase;
         emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
-        wrk.LogFileError(GenT::MOC, FileName, emsg);
+        LogFileError(GenT::MOC, FileName, emsg);
       }
       return false;
     }
@@ -431,76 +535,80 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
 
   // Convert pre jobs to actual jobs
   for (JobPre& jobPre : jobs) {
-    JobHandleT jobHandle = cm::make_unique<JobMocT>(
+    cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>(
       std::move(jobPre.SourceFile), FileName, std::move(jobPre.IncludeString));
     if (jobPre.self) {
       // Read dependencies from this source
-      static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
+      JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle);
+      Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends);
+      jobMoc.DependsValid = true;
     }
-    if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) {
+    if (!Gen()->ParallelJobPushMoc(std::move(jobHandle))) {
       return false;
     }
   }
   return true;
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk,
-                                                        MetaT const& meta)
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(MetaT const& meta)
 {
   bool success = true;
-  std::string const macroName = wrk.Moc().FindMacro(meta.Content);
+  std::string const macroName = Gen()->Moc().FindMacro(meta.Content);
   if (!macroName.empty()) {
-    JobHandleT jobHandle = cm::make_unique<JobMocT>(
+    cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>(
       std::string(FileName), std::string(), std::string());
     // Read dependencies from this source
-    static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
-    success = wrk.Gen().ParallelJobPushMoc(jobHandle);
+    {
+      JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle);
+      Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends);
+      jobMoc.DependsValid = true;
+    }
+    success = Gen()->ParallelJobPushMoc(std::move(jobHandle));
   }
   return success;
 }
 
 std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
-  WorkerT& wrk, std::string const& fileBase) const
+  std::string const& fileBase) const
 {
   std::string res = fileBase;
   res += ".{";
-  res += cmJoin(wrk.Base().HeaderExtensions, ",");
+  res += cmJoin(Gen()->Base().HeaderExtensions, ",");
   res += "}";
   return res;
 }
 
 std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
-  WorkerT& wrk, std::string const& includerDir, std::string const& includeBase)
+  std::string const& includerDir, std::string const& includeBase)
 {
   std::string header;
   // Search in vicinity of the source
-  if (!wrk.Base().FindHeader(header, includerDir + includeBase)) {
+  if (!Gen()->Base().FindHeader(header, includerDir + includeBase)) {
     // Search in include directories
-    for (std::string const& path : wrk.Moc().IncludePaths) {
+    for (std::string const& path : Gen()->Moc().IncludePaths) {
       std::string fullPath = path;
       fullPath.push_back('/');
       fullPath += includeBase;
-      if (wrk.Base().FindHeader(header, fullPath)) {
+      if (Gen()->Base().FindHeader(header, fullPath)) {
         break;
       }
     }
   }
   // Sanitize
   if (!header.empty()) {
-    header = wrk.FileSys().GetRealPath(header);
+    header = FileSys().GetRealPath(header);
   }
   return header;
 }
 
-bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk,
-                                                  MetaT const& meta)
+bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(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(2))) {
+    while (Gen()->Uic().RegExpInclude.find(contentChars, match)) {
+      if (!ParseUicInclude(meta, match.match(2))) {
         success = false;
         break;
       }
@@ -511,15 +619,15 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk,
 }
 
 bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
-  WorkerT& wrk, MetaT const& meta, std::string&& includeString)
+  MetaT const& meta, std::string&& includeString)
 {
   bool success = false;
-  std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString);
+  std::string uiInputFile = UicFindIncludedFile(meta, includeString);
   if (!uiInputFile.empty()) {
-    if (!wrk.Uic().skipped(uiInputFile)) {
-      JobHandleT jobHandle = cm::make_unique<JobUicT>(
+    if (!Gen()->Uic().skipped(uiInputFile)) {
+      cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobUicT>(
         std::move(uiInputFile), FileName, std::move(includeString));
-      success = wrk.Gen().ParallelJobPushUic(jobHandle);
+      success = Gen()->ParallelJobPushUic(std::move(jobHandle));
     } else {
       // A skipped file is successful
       success = true;
@@ -529,16 +637,16 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
 }
 
 std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
-  WorkerT& wrk, MetaT const& meta, std::string const& includeString)
+  MetaT const& meta, std::string const& includeString)
 {
   std::string res;
   std::string searchFile =
-    wrk.FileSys().GetFilenameWithoutLastExtension(includeString).substr(3);
+    FileSys().GetFilenameWithoutLastExtension(includeString).substr(3);
   searchFile += ".ui";
   // Collect search paths list
   std::deque<std::string> testFiles;
   {
-    std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString);
+    std::string const searchPath = FileSys().SubDirPrefix(includeString);
 
     std::string searchFileFull;
     if (!searchPath.empty()) {
@@ -554,12 +662,12 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
       }
     }
     // AUTOUIC search paths
-    if (!wrk.Uic().SearchPaths.empty()) {
-      for (std::string const& sPath : wrk.Uic().SearchPaths) {
+    if (!Gen()->Uic().SearchPaths.empty()) {
+      for (std::string const& sPath : Gen()->Uic().SearchPaths) {
         testFiles.push_back((sPath + "/").append(searchFile));
       }
       if (!searchPath.empty()) {
-        for (std::string const& sPath : wrk.Uic().SearchPaths) {
+        for (std::string const& sPath : Gen()->Uic().SearchPaths) {
           testFiles.push_back((sPath + "/").append(searchFileFull));
         }
       }
@@ -568,8 +676,8 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
 
   // Search for the .ui file!
   for (std::string const& testFile : testFiles) {
-    if (wrk.FileSys().FileExists(testFile)) {
-      res = wrk.FileSys().GetRealPath(testFile);
+    if (FileSys().FileExists(testFile)) {
+      res = FileSys().GetRealPath(testFile);
       break;
     }
   }
@@ -584,162 +692,141 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
       emsg += Quoted(testFile);
       emsg += "\n";
     }
-    wrk.LogFileError(GenT::UIC, FileName, emsg);
+    LogFileError(GenT::UIC, FileName, emsg);
   }
 
   return res;
 }
 
-void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobPostParseT::Process()
 {
-  // (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(GenT::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(GenT::MOC, reason);
-    }
-    generate = true;
+  if (Gen()->Moc().Enabled) {
+    // Add mocs compilations fence job
+    Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
   }
-  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(GenT::MOC, result, cmd)) {
-        std::string emsg = "The content generation command for ";
-        emsg += Quoted(wrk.Moc().PredefsFileRel);
-        emsg += " failed.\n";
-        emsg += result.ErrorMessage;
-        wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
-      }
+  // Add finish job
+  Gen()->WorkerPool().EmplaceJob<JobFinishT>();
+}
+
+void cmQtAutoGeneratorMocUic::JobMocsCompilationT::Process()
+{
+  // Compose mocs compilation file content
+  std::string content =
+    "// This file is autogenerated. Changes will be overwritten.\n";
+  if (Gen()->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
+    char const sbeg = Gen()->Base().MultiConfig ? '<' : '"';
+    char const send = Gen()->Base().MultiConfig ? '>' : '"';
+    for (std::string const& mocfile : Gen()->MocAutoFiles()) {
+      content += "#include ";
+      content += sbeg;
+      content += mocfile;
+      content += send;
+      content += '\n';
     }
+  }
 
-    // (Re)write predefs file only on demand
-    if (!result.error()) {
-      if (!fileExists ||
-          wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) {
-        std::string error;
-        if (wrk.FileSys().FileWrite(wrk.Moc().PredefsFileAbs, result.StdOut,
-                                    &error)) {
-          // Success
-        } else {
-          std::string emsg = "Writing ";
-          emsg += Quoted(wrk.Moc().PredefsFileRel);
-          emsg += " failed. ";
-          emsg += error;
-          wrk.LogFileError(GenT::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(GenT::MOC, msg);
-        }
-        wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs);
-      }
+  std::string const& compAbs = Gen()->Moc().CompFileAbs;
+  if (FileSys().FileDiffers(compAbs, content)) {
+    // Actually write mocs compilation file
+    if (Log().Verbose()) {
+      Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs);
     }
+    if (!FileSys().FileWrite(compAbs, content)) {
+      LogFileError(GenT::MOC, compAbs,
+                   "mocs compilation file writing failed.");
+    }
+  } else if (Gen()->MocAutoFileUpdated()) {
+    // Only touch mocs compilation file
+    if (Log().Verbose()) {
+      Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
+    }
+    FileSys().Touch(compAbs);
   }
 }
 
 void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies(
-  WorkerT& wrk, std::string const& content)
+  std::string const& content)
 {
-  wrk.Moc().FindDependencies(content, Depends);
+  Gen()->Moc().FindDependencies(content, Depends);
   DependsValid = true;
 }
 
-void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobMocT::Process()
 {
   // Compute build file name
   if (!IncludeString.empty()) {
-    BuildFile = wrk.Base().AutogenIncludeDir;
+    BuildFile = Gen()->Base().AutogenIncludeDir;
     BuildFile += '/';
     BuildFile += IncludeString;
   } else {
     // Relative build path
-    std::string relPath = wrk.FileSys().GetFilePathChecksum(SourceFile);
+    std::string relPath = FileSys().GetFilePathChecksum(SourceFile);
     relPath += "/moc_";
-    relPath += wrk.FileSys().GetFilenameWithoutLastExtension(SourceFile);
+    relPath += FileSys().GetFilenameWithoutLastExtension(SourceFile);
 
     // Register relative file path with duplication check
-    relPath = wrk.Gen().ParallelMocAutoRegister(relPath);
+    relPath = Gen()->ParallelMocAutoRegister(relPath);
 
     // Absolute build path
-    if (wrk.Base().MultiConfig) {
-      BuildFile = wrk.Base().AutogenIncludeDir;
+    if (Gen()->Base().MultiConfig) {
+      BuildFile = Gen()->Base().AutogenIncludeDir;
       BuildFile += '/';
       BuildFile += relPath;
     } else {
-      BuildFile = wrk.Base().AbsoluteBuildPath(relPath);
+      BuildFile = Gen()->Base().AbsoluteBuildPath(relPath);
     }
   }
 
-  if (UpdateRequired(wrk)) {
-    GenerateMoc(wrk);
+  if (UpdateRequired()) {
+    GenerateMoc();
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
+bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired()
 {
-  bool const verbose = wrk.Gen().Log().Verbose();
+  bool const verbose = Log().Verbose();
 
   // Test if the build file exists
-  if (!wrk.FileSys().FileExists(BuildFile)) {
+  if (!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(GenT::MOC, reason);
+      Log().Info(GenT::MOC, reason);
     }
     return true;
   }
 
   // Test if any setting changed
-  if (wrk.Moc().SettingsChanged) {
+  if (Gen()->Moc().SettingsChanged) {
     if (verbose) {
       std::string reason = "Generating ";
       reason += Quoted(BuildFile);
       reason += " from ";
       reason += Quoted(SourceFile);
       reason += " because the MOC settings changed";
-      wrk.LogInfo(GenT::MOC, reason);
+      Log().Info(GenT::MOC, reason);
     }
     return true;
   }
 
   // Test if the moc_predefs file is newer
-  if (!wrk.Moc().PredefsFileAbs.empty()) {
+  if (!Gen()->Moc().PredefsFileAbs.empty()) {
     bool isOlder = false;
     {
       std::string error;
-      isOlder = wrk.FileSys().FileIsOlderThan(
-        BuildFile, wrk.Moc().PredefsFileAbs, &error);
+      isOlder = FileSys().FileIsOlderThan(BuildFile,
+                                          Gen()->Moc().PredefsFileAbs, &error);
       if (!isOlder && !error.empty()) {
-        wrk.LogError(GenT::MOC, error);
+        LogError(GenT::MOC, error);
         return false;
       }
     }
@@ -748,8 +835,8 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
         std::string reason = "Generating ";
         reason += Quoted(BuildFile);
         reason += " because it's older than: ";
-        reason += Quoted(wrk.Moc().PredefsFileAbs);
-        wrk.LogInfo(GenT::MOC, reason);
+        reason += Quoted(Gen()->Moc().PredefsFileAbs);
+        Log().Info(GenT::MOC, reason);
       }
       return true;
     }
@@ -760,9 +847,9 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
     bool isOlder = false;
     {
       std::string error;
-      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
+      isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
       if (!isOlder && !error.empty()) {
-        wrk.LogError(GenT::MOC, error);
+        LogError(GenT::MOC, error);
         return false;
       }
     }
@@ -772,7 +859,7 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
         reason += Quoted(BuildFile);
         reason += " because it's older than its source file ";
         reason += Quoted(SourceFile);
-        wrk.LogInfo(GenT::MOC, reason);
+        Log().Info(GenT::MOC, reason);
       }
       return true;
     }
@@ -785,7 +872,7 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
       std::string content;
       {
         std::string error;
-        if (!wrk.FileSys().FileRead(content, SourceFile, &error)) {
+        if (!FileSys().FileRead(content, SourceFile, &error)) {
           std::string emsg = "Could not read file\n  ";
           emsg += Quoted(SourceFile);
           emsg += "\nrequired by moc include ";
@@ -794,20 +881,20 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
           emsg += Quoted(IncluderFile);
           emsg += ".\n";
           emsg += error;
-          wrk.LogError(GenT::MOC, emsg);
+          LogError(GenT::MOC, emsg);
           return false;
         }
       }
-      FindDependencies(wrk, content);
+      FindDependencies(content);
     }
     // Check dependency timestamps
     std::string error;
-    std::string sourceDir = wrk.FileSys().SubDirPrefix(SourceFile);
+    std::string sourceDir = FileSys().SubDirPrefix(SourceFile);
     for (std::string const& depFileRel : Depends) {
       std::string depFileAbs =
-        wrk.Moc().FindIncludedFile(sourceDir, depFileRel);
+        Gen()->Moc().FindIncludedFile(sourceDir, depFileRel);
       if (!depFileAbs.empty()) {
-        if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) {
+        if (FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) {
           if (verbose) {
             std::string reason = "Generating ";
             reason += Quoted(BuildFile);
@@ -815,18 +902,18 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
             reason += Quoted(SourceFile);
             reason += " because it is older than it's dependency file ";
             reason += Quoted(depFileAbs);
-            wrk.LogInfo(GenT::MOC, reason);
+            Log().Info(GenT::MOC, reason);
           }
           return true;
         }
         if (!error.empty()) {
-          wrk.LogError(GenT::MOC, error);
+          LogError(GenT::MOC, error);
           return false;
         }
       } else {
         std::string message = "Could not find dependency file ";
         message += Quoted(depFileRel);
-        wrk.LogFileWarning(GenT::MOC, SourceFile, message);
+        Log().WarningFile(GenT::MOC, SourceFile, message);
       }
     }
   }
@@ -834,41 +921,40 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
   return false;
 }
 
-void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc()
 {
   // Make sure the parent directory exists
-  if (!wrk.FileSys().MakeParentDirectory(BuildFile)) {
-    wrk.LogFileError(GenT::MOC, BuildFile,
-                     "Could not create parent directory.");
+  if (!FileSys().MakeParentDirectory(BuildFile)) {
+    LogFileError(GenT::MOC, BuildFile, "Could not create parent directory.");
     return;
   }
   {
     // Compose moc command
     std::vector<std::string> cmd;
-    cmd.push_back(wrk.Moc().Executable);
+    cmd.push_back(Gen()->Moc().Executable);
     // Add options
-    cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(),
-               wrk.Moc().AllOptions.end());
+    cmd.insert(cmd.end(), Gen()->Moc().AllOptions.begin(),
+               Gen()->Moc().AllOptions.end());
     // Add predefs include
-    if (!wrk.Moc().PredefsFileAbs.empty()) {
+    if (!Gen()->Moc().PredefsFileAbs.empty()) {
       cmd.emplace_back("--include");
-      cmd.push_back(wrk.Moc().PredefsFileAbs);
+      cmd.push_back(Gen()->Moc().PredefsFileAbs);
     }
     cmd.emplace_back("-o");
     cmd.push_back(BuildFile);
     cmd.push_back(SourceFile);
 
     // Execute moc command
-    ProcessResultT result;
-    if (wrk.RunProcess(GenT::MOC, result, cmd)) {
+    cmWorkerPool::ProcessResultT result;
+    if (RunProcess(GenT::MOC, result, cmd)) {
       // Moc command success
       // Print moc output
       if (!result.StdOut.empty()) {
-        wrk.LogInfo(GenT::MOC, result.StdOut);
+        Log().Info(GenT::MOC, result.StdOut);
       }
       // Notify the generator that a not included file changed (on demand)
       if (IncludeString.empty()) {
-        wrk.Gen().ParallelMocAutoUpdated();
+        Gen()->ParallelMocAutoUpdated();
       }
     } else {
       // Moc command failed
@@ -879,51 +965,51 @@ void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk)
         emsg += Quoted(BuildFile);
         emsg += ".\n";
         emsg += result.ErrorMessage;
-        wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
+        LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
       }
-      wrk.FileSys().FileRemove(BuildFile);
+      FileSys().FileRemove(BuildFile);
     }
   }
 }
 
-void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobUicT::Process()
 {
   // Compute build file name
-  BuildFile = wrk.Base().AutogenIncludeDir;
+  BuildFile = Gen()->Base().AutogenIncludeDir;
   BuildFile += '/';
   BuildFile += IncludeString;
 
-  if (UpdateRequired(wrk)) {
-    GenerateUic(wrk);
+  if (UpdateRequired()) {
+    GenerateUic();
   }
 }
 
-bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
+bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired()
 {
-  bool const verbose = wrk.Gen().Log().Verbose();
+  bool const verbose = Log().Verbose();
 
   // Test if the build file exists
-  if (!wrk.FileSys().FileExists(BuildFile)) {
+  if (!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(GenT::UIC, reason);
+      Log().Info(GenT::UIC, reason);
     }
     return true;
   }
 
   // Test if the uic settings changed
-  if (wrk.Uic().SettingsChanged) {
+  if (Gen()->Uic().SettingsChanged) {
     if (verbose) {
       std::string reason = "Generating ";
       reason += Quoted(BuildFile);
       reason += " from ";
       reason += Quoted(SourceFile);
       reason += " because the UIC settings changed";
-      wrk.LogInfo(GenT::UIC, reason);
+      Log().Info(GenT::UIC, reason);
     }
     return true;
   }
@@ -933,9 +1019,9 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
     bool isOlder = false;
     {
       std::string error;
-      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
+      isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
       if (!isOlder && !error.empty()) {
-        wrk.LogError(GenT::UIC, error);
+        LogError(GenT::UIC, error);
         return false;
       }
     }
@@ -945,7 +1031,7 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
         reason += Quoted(BuildFile);
         reason += " because it's older than its source file ";
         reason += Quoted(SourceFile);
-        wrk.LogInfo(GenT::UIC, reason);
+        Log().Info(GenT::UIC, reason);
       }
       return true;
     }
@@ -954,37 +1040,36 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
   return false;
 }
 
-void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk)
+void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic()
 {
   // Make sure the parent directory exists
-  if (!wrk.FileSys().MakeParentDirectory(BuildFile)) {
-    wrk.LogFileError(GenT::UIC, BuildFile,
-                     "Could not create parent directory.");
+  if (!FileSys().MakeParentDirectory(BuildFile)) {
+    LogFileError(GenT::UIC, BuildFile, "Could not create parent directory.");
     return;
   }
   {
     // Compose uic command
     std::vector<std::string> cmd;
-    cmd.push_back(wrk.Uic().Executable);
+    cmd.push_back(Gen()->Uic().Executable);
     {
-      std::vector<std::string> allOpts = wrk.Uic().TargetOptions;
-      auto optionIt = wrk.Uic().Options.find(SourceFile);
-      if (optionIt != wrk.Uic().Options.end()) {
+      std::vector<std::string> allOpts = Gen()->Uic().TargetOptions;
+      auto optionIt = Gen()->Uic().Options.find(SourceFile);
+      if (optionIt != Gen()->Uic().Options.end()) {
         UicMergeOptions(allOpts, optionIt->second,
-                        (wrk.Base().QtVersionMajor == 5));
+                        (Gen()->Base().QtVersionMajor == 5));
       }
       cmd.insert(cmd.end(), allOpts.begin(), allOpts.end());
     }
     cmd.emplace_back("-o");
-    cmd.push_back(BuildFile);
-    cmd.push_back(SourceFile);
+    cmd.emplace_back(BuildFile);
+    cmd.emplace_back(SourceFile);
 
-    ProcessResultT result;
-    if (wrk.RunProcess(GenT::UIC, result, cmd)) {
+    cmWorkerPool::ProcessResultT result;
+    if (RunProcess(GenT::UIC, result, cmd)) {
       // Uic command success
       // Print uic output
       if (!result.StdOut.empty()) {
-        wrk.LogInfo(GenT::UIC, result.StdOut);
+        Log().Info(GenT::UIC, result.StdOut);
       }
     } else {
       // Uic command failed
@@ -997,144 +1082,16 @@ void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk)
         emsg += Quoted(IncluderFile);
         emsg += ".\n";
         emsg += result.ErrorMessage;
-        wrk.LogCommandError(GenT::UIC, emsg, cmd, result.StdOut);
+        LogCommandError(GenT::UIC, emsg, cmd, result.StdOut);
       }
-      wrk.FileSys().FileRemove(BuildFile);
-    }
-  }
-}
-
-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();
-  }
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogInfo(
-  GenT genType, std::string const& message) const
-{
-  Log().Info(genType, message);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogWarning(
-  GenT genType, std::string const& message) const
-{
-  Log().Warning(genType, message);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning(
-  GenT genType, std::string const& filename, std::string const& message) const
-{
-  Log().WarningFile(genType, filename, message);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogError(
-  GenT genType, std::string const& message) const
-{
-  Gen().ParallelRegisterJobError();
-  Log().Error(genType, message);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogFileError(
-  GenT genType, std::string const& filename, std::string const& message) const
-{
-  Gen().ParallelRegisterJobError();
-  Log().ErrorFile(genType, filename, message);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError(
-  GenT 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(
-  GenT 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);
+      FileSys().FileRemove(BuildFile);
     }
   }
-  return !result.error();
 }
 
-void cmQtAutoGeneratorMocUic::WorkerT::Loop()
+void cmQtAutoGeneratorMocUic::JobFinishT::Process()
 {
-  while (true) {
-    Gen().WorkerSwapJob(JobHandle_);
-    if (JobHandle_) {
-      JobHandle_->Process(*this);
-    } else {
-      break;
-    }
-  }
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::UVProcessStart(uv_async_t* handle)
-{
-  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, [&wrk] { wrk.UVProcessFinished(); });
-    }
-  }
-
-  if (!wrk.Process_->IsStarted()) {
-    wrk.UVProcessFinished();
-  }
-}
-
-void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished()
-{
-  {
-    std::lock_guard<std::mutex> lock(ProcessMutex_);
-    if (Process_ && (Process_->IsFinished() || !Process_->IsStarted())) {
-      Process_.reset();
-    }
-  }
-  // Notify idling thread
-  ProcessCondition_.notify_one();
+  Gen()->AbortSuccess();
 }
 
 cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
@@ -1147,24 +1104,9 @@ cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
     "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
   Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
                              "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
-
-  // 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());
-
-  // Initialize libuv asynchronous iteration request
-  UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this);
 }
 
-cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic()
-{
-  // Close libuv loop
-  uv_loop_close(UVLoop());
-}
+cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() = default;
 
 bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
 {
@@ -1364,7 +1306,7 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
     Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
     // Install moc predefs job
     if (!Moc().PredefsCmd.empty()) {
-      JobQueues_.MocPredefs.emplace_back(cm::make_unique<JobMocPredefsT>());
+      WorkerPool().EmplaceJob<JobMocPredefsT>();
     }
   }
 
@@ -1400,46 +1342,48 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
   }
 
   // - Headers and sources
+  // Add sources
   {
-    auto addHeader = [this](std::string&& hdr, bool moc, bool uic) {
-      this->JobQueues_.Headers.emplace_back(
-        cm::make_unique<JobParseT>(std::move(hdr), moc, uic, true));
-    };
     auto addSource = [this](std::string&& src, bool moc, bool uic) {
-      this->JobQueues_.Sources.emplace_back(
-        cm::make_unique<JobParseT>(std::move(src), moc, uic, false));
+      WorkerPool().EmplaceJob<JobParseT>(std::move(src), moc, uic, false);
     };
-
-    // Add headers
-    for (std::string& hdr : InfoGetList("AM_HEADERS")) {
-      addHeader(std::move(hdr), true, true);
+    for (std::string& src : InfoGetList("AM_SOURCES")) {
+      addSource(std::move(src), true, true);
     }
     if (Moc().Enabled) {
-      for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) {
-        addHeader(std::move(hdr), true, false);
+      for (std::string& src : InfoGetList("AM_MOC_SOURCES")) {
+        addSource(std::move(src), true, false);
       }
     }
     if (Uic().Enabled) {
-      for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) {
-        addHeader(std::move(hdr), false, true);
+      for (std::string& src : InfoGetList("AM_UIC_SOURCES")) {
+        addSource(std::move(src), false, true);
       }
     }
-
-    // Add sources
-    for (std::string& src : InfoGetList("AM_SOURCES")) {
-      addSource(std::move(src), true, true);
+  }
+  // Add Fence job
+  WorkerPool().EmplaceJob<JobFenceT>();
+  // Add headers
+  {
+    auto addHeader = [this](std::string&& hdr, bool moc, bool uic) {
+      WorkerPool().EmplaceJob<JobParseT>(std::move(hdr), moc, uic, true);
+    };
+    for (std::string& hdr : InfoGetList("AM_HEADERS")) {
+      addHeader(std::move(hdr), true, true);
     }
     if (Moc().Enabled) {
-      for (std::string& src : InfoGetList("AM_MOC_SOURCES")) {
-        addSource(std::move(src), true, false);
+      for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) {
+        addHeader(std::move(hdr), true, false);
       }
     }
     if (Uic().Enabled) {
-      for (std::string& src : InfoGetList("AM_UIC_SOURCES")) {
-        addSource(std::move(src), false, true);
+      for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) {
+        addHeader(std::move(hdr), false, true);
       }
     }
   }
+  // Addpost parse fence job
+  WorkerPool().EmplaceJob<JobPostParseT>();
 
   // Init derived information
   // ------------------------
@@ -1536,93 +1480,20 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
 
 bool cmQtAutoGeneratorMocUic::Process()
 {
-  // Run libuv event loop
-  UVRequest().send();
-  if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
-    if (JobError_) {
-      return false;
-    }
-  } else {
+  SettingsFileRead();
+  if (!CreateDirectories()) {
     return false;
   }
-  return true;
-}
-
-void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle)
-{
-  reinterpret_cast<cmQtAutoGeneratorMocUic*>(handle->data)->PollStage();
-}
 
-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);
-      }
-      break;
-    case StageT::PARSE_HEADERS:
-      if (ThreadsStartJobs(JobQueues_.Headers)) {
-        SetStage(StageT::MOC_PREDEFS);
-      }
-      break;
-    case StageT::MOC_PREDEFS:
-      if (ThreadsStartJobs(JobQueues_.MocPredefs)) {
-        SetStage(StageT::MOC_PROCESS);
-      }
-      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;
+  if (!WorkerPool_.Process(Base().NumThreads, this)) {
+    return false;
   }
-}
 
-void cmQtAutoGeneratorMocUic::SetStage(StageT stage)
-{
   if (JobError_) {
-    stage = StageT::FINISH;
-  }
-  // Only allow to increase the stage
-  if (Stage_ < stage) {
-    Stage_ = stage;
-    UVRequest().send();
+    return false;
   }
+
+  return SettingsFileWrite();
 }
 
 void cmQtAutoGeneratorMocUic::SettingsFileRead()
@@ -1691,11 +1562,10 @@ void cmQtAutoGeneratorMocUic::SettingsFileRead()
   }
 }
 
-void cmQtAutoGeneratorMocUic::SettingsFileWrite()
+bool cmQtAutoGeneratorMocUic::SettingsFileWrite()
 {
-  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
   // Only write if any setting changed
-  if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) {
+  if (Moc().SettingsChanged || Uic().SettingsChanged) {
     if (Log().Verbose()) {
       Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_));
     }
@@ -1721,246 +1591,136 @@ void cmQtAutoGeneratorMocUic::SettingsFileWrite()
                       "Settings file writing failed. " + error);
       // Remove old settings file to trigger a full rebuild on the next run
       FileSys().FileRemove(SettingsFile_);
-      RegisterJobError();
+      return false;
     }
   }
+  return true;
 }
 
-void cmQtAutoGeneratorMocUic::CreateDirectories()
+bool cmQtAutoGeneratorMocUic::CreateDirectories()
 {
   // Create AUTOGEN include directory
   if (!FileSys().MakeDirectory(Base().AutogenIncludeDir)) {
     Log().ErrorFile(GenT::GEN, Base().AutogenIncludeDir,
                     "Could not create directory.");
-    RegisterJobError();
+    return false;
   }
+  return true;
 }
 
-bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue)
+// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be
+// locked
+void cmQtAutoGeneratorMocUic::Abort(bool error)
 {
-  bool done = false;
-  std::size_t queueSize = queue.size();
-
-  // Change the active queue
-  {
-    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;
-    }
+  if (error) {
+    JobError_.store(true);
   }
+  WorkerPool_.Abort();
+}
 
-  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());
+bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(
+  cmWorkerPool::JobHandleT&& jobHandle)
+{
+  JobMocT const& mocJob(static_cast<JobMocT&>(*jobHandle));
+  // Do additional tests if this is an included moc job
+  if (!mocJob.IncludeString.empty()) {
+    std::lock_guard<std::mutex> guard(MocMetaMutex_);
+    // Register included moc file
+    MocIncludedFiles_.emplace(mocJob.SourceFile);
+
+    // Check if the same moc file would be generated from a different
+    // source file.
+    auto const range = MocIncludes_.equal_range(mocJob.IncludeString);
+    for (auto it = range.first; it != range.second; ++it) {
+      if (it->second[0] == mocJob.SourceFile) {
+        // The output file already gets generated
+        return true;
       }
-    } else {
-      // Notify threads
-      if (queueSize == 1) {
-        JobsConditionRead_.notify_one();
-      } else {
-        JobsConditionRead_.notify_all();
+      {
+        // The output file already gets generated - from a different source
+        // file!
+        std::string error = "The two source files\n  ";
+        error += Quoted(mocJob.IncluderFile);
+        error += " and\n  ";
+        error += Quoted(it->second[1]);
+        error += "\ncontain 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(it->second[0]);
+        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(GenT::MOC, error);
+        AbortError();
+        return false;
       }
     }
-  }
-
-  return done;
-}
 
-void cmQtAutoGeneratorMocUic::ThreadsStop()
-{
-  if (!Workers_.empty()) {
-    // Clear all jobs
-    {
-      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();
+    // We're still here so register this job
+    MocIncludes_.emplace_hint(range.first, mocJob.IncludeString,
+                              std::array<std::string, 2>{
+                                { mocJob.SourceFile, mocJob.IncluderFile } });
   }
+  return WorkerPool_.PushJob(std::move(jobHandle));
 }
 
-bool cmQtAutoGeneratorMocUic::ThreadsJobsDone()
+bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(
+  cmWorkerPool::JobHandleT&& jobHandle)
 {
-  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
-  return (JobsRemain_ == 0);
-}
-
-void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle)
-{
-  bool const jobProcessed(jobHandle);
-  if (jobProcessed) {
-    jobHandle.reset();
-  }
+  const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle));
   {
-    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();
+    std::lock_guard<std::mutex> guard(UicMetaMutex_);
+    // Check if the same uic file would be generated from a different
+    // source file.
+    auto const range = UicIncludes_.equal_range(uicJob.IncludeString);
+    for (auto it = range.first; it != range.second; ++it) {
+      if (it->second[0] == uicJob.SourceFile) {
+        // The output file already gets generated
+        return true;
       }
-    }
-    // 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();
-    }
-  }
-}
-
-void cmQtAutoGeneratorMocUic::ParallelRegisterJobError()
-{
-  std::lock_guard<std::mutex> jobsLock(JobsMutex_);
-  RegisterJobError();
-}
-
-// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be
-// locked
-void cmQtAutoGeneratorMocUic::RegisterJobError()
-{
-  JobError_ = true;
-  if (!JobThreadsAbort_) {
-    JobThreadsAbort_ = true;
-    // Clear remaining jobs
-    if (JobsRemain_ != 0) {
-      JobsRemain_ -= JobQueue_.size();
-      JobQueue_.clear();
-    }
-  }
-}
-
-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 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(GenT::MOC, error);
-              RegisterJobError();
-            }
-            // Do not push this job in since the included moc file already
-            // gets generated by an other job.
-            pushJobHandle = false;
-            break;
-          }
-        }
+      {
+        // The output file already gets generated - from a different .ui
+        // file!
+        std::string error = "The two source files\n  ";
+        error += Quoted(uicJob.IncluderFile);
+        error += " and\n  ";
+        error += Quoted(it->second[1]);
+        error += "\ncontain 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(it->second[0]);
+        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(GenT::UIC, error);
+        AbortError();
+        return false;
       }
     }
-    // Push job on demand
-    if (pushJobHandle) {
-      JobQueues_.Moc.emplace_back(std::move(jobHandle));
-    }
-  }
-  return !JobError_;
-}
 
-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 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(GenT::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));
-    }
+    // We're still here so register this job
+    UicIncludes_.emplace_hint(range.first, uicJob.IncludeString,
+                              std::array<std::string, 2>{
+                                { uicJob.SourceFile, uicJob.IncluderFile } });
   }
-  return !JobError_;
+  return WorkerPool_.PushJob(std::move(jobHandle));
 }
 
 bool cmQtAutoGeneratorMocUic::ParallelMocIncluded(
   std::string const& sourceFile)
 {
-  std::lock_guard<std::mutex> mocLock(JobsMutex_);
+  std::lock_guard<std::mutex> guard(MocMetaMutex_);
   return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end());
 }
 
@@ -1969,7 +1729,7 @@ std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister(
 {
   std::string res;
   {
-    std::lock_guard<std::mutex> mocLock(JobsMutex_);
+    std::lock_guard<std::mutex> mocLock(MocMetaMutex_);
     res = baseName;
     res += ".cpp";
     if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) {
@@ -1990,63 +1750,3 @@ std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister(
   }
   return res;
 }
-
-void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated()
-{
-  std::lock_guard<std::mutex> mocLock(JobsMutex_);
-  MocAutoFileUpdated_ = true;
-}
-
-void cmQtAutoGeneratorMocUic::MocGenerateCompilation()
-{
-  std::lock_guard<std::mutex> mocLock(JobsMutex_);
-  if (!JobError_ && Moc().Enabled) {
-    // Write mocs compilation build file
-    {
-      // 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
-        char const sbeg = Base().MultiConfig ? '<' : '"';
-        char const send = Base().MultiConfig ? '>' : '"';
-        for (std::string const& mocfile : MocAutoFiles_) {
-          content += "#include ";
-          content += sbeg;
-          content += mocfile;
-          content += send;
-          content += '\n';
-        }
-      }
-
-      std::string const& compAbs = Moc().CompFileAbs;
-      if (FileSys().FileDiffers(compAbs, content)) {
-        // Actually write mocs compilation file
-        if (Log().Verbose()) {
-          Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs);
-        }
-        std::string error;
-        if (!FileSys().FileWrite(compAbs, content, &error)) {
-          Log().ErrorFile(GenT::MOC, compAbs,
-                          "mocs compilation file writing failed. " + error);
-          RegisterJobError();
-          return;
-        }
-      } else if (MocAutoFileUpdated_) {
-        // Only touch mocs compilation file
-        if (Log().Verbose()) {
-          Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
-        }
-        FileSys().Touch(compAbs);
-      }
-    }
-    // Write mocs compilation wrapper file
-    if (Base().MultiConfig) {
-    }
-  }
-}
diff --git a/Source/cmQtAutoGeneratorMocUic.h b/Source/cmQtAutoGeneratorMocUic.h
index 27d73a7..4efc2c6 100644
--- a/Source/cmQtAutoGeneratorMocUic.h
+++ b/Source/cmQtAutoGeneratorMocUic.h
@@ -7,20 +7,16 @@
 
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenerator.h"
-#include "cmUVHandlePtr.h"
-#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
-#include "cm_uv.h"
+#include "cmWorkerPool.h"
 #include "cmsys/RegularExpression.hxx"
 
-#include <condition_variable>
-#include <cstddef>
-#include <deque>
+#include <array>
+#include <atomic>
 #include <map>
 #include <memory> // IWYU pragma: keep
 #include <mutex>
 #include <set>
 #include <string>
-#include <thread>
 #include <unordered_set>
 #include <utility>
 #include <vector>
@@ -39,7 +35,7 @@ public:
 
 public:
   // -- Types
-  class WorkerT;
+  typedef std::multimap<std::string, std::array<std::string, 2>> IncludesMap;
 
   /// @brief Search key plus regular expression pair
   ///
@@ -173,31 +169,71 @@ public:
     cmsys::RegularExpression RegExpInclude;
   };
 
-  /// @brief Abstract job class for threaded processing
+  /// @brief Abstract job class for concurrent job processing
   ///
-  class JobT
+  class JobT : public cmWorkerPool::JobT
   {
-  public:
-    JobT() = default;
-    virtual ~JobT() = default;
+  protected:
+    /**
+     * @brief Protected default constructor
+     */
+    JobT(bool fence = false)
+      : cmWorkerPool::JobT(fence)
+    {
+    }
+
+    //! Get the generator. Only valid during Process() call!
+    cmQtAutoGeneratorMocUic* Gen() const
+    {
+      return static_cast<cmQtAutoGeneratorMocUic*>(UserData());
+    };
+
+    //! Get the file system interface. Only valid during Process() call!
+    FileSystem& FileSys() { return Gen()->FileSys(); }
+    //! Get the logger. Only valid during Process() call!
+    Logger& Log() { return Gen()->Log(); }
+
+    // -- Error logging with automatic abort
+    void LogError(GenT genType, std::string const& message) const;
+    void LogFileError(GenT genType, std::string const& filename,
+                      std::string const& message) const;
+    void LogCommandError(GenT genType, std::string const& message,
+                         std::vector<std::string> const& command,
+                         std::string const& output) const;
 
-    JobT(JobT const&) = delete;
-    JobT& operator=(JobT const&) = delete;
+    /**
+     * @brief Run an external process. Use only during Process() call!
+     */
+    bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
+                    std::vector<std::string> const& command);
+  };
 
-    // -- Abstract processing interface
-    virtual void Process(WorkerT& wrk) = 0;
+  /// @brief Fence job utility class
+  ///
+  class JobFenceT : public JobT
+  {
+  public:
+    JobFenceT()
+      : JobT(true)
+    {
+    }
+    void Process() override{};
   };
 
-  // Job management types
-  typedef std::unique_ptr<JobT> JobHandleT;
-  typedef std::deque<JobHandleT> JobQueueT;
+  /// @brief Generate moc_predefs.h
+  ///
+  class JobMocPredefsT : public JobT
+  {
+  private:
+    void Process() override;
+  };
 
-  /// @brief Parse source job
+  /// @brief Parses a source file
   ///
   class JobParseT : public JobT
   {
   public:
-    JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false)
+    JobParseT(std::string fileName, bool moc, bool uic, bool header = false)
       : FileName(std::move(fileName))
       , AutoMoc(moc)
       , AutoUic(uic)
@@ -213,18 +249,15 @@ public:
       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,
+    void Process() override;
+    bool ParseMocSource(MetaT const& meta);
+    bool ParseMocHeader(MetaT const& meta);
+    std::string MocStringHeaders(std::string const& fileBase) const;
+    std::string MocFindIncludedHeader(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,
+    bool ParseUic(MetaT const& meta);
+    bool ParseUicInclude(MetaT const& meta, std::string&& includeString);
+    std::string UicFindIncludedFile(MetaT const& meta,
                                     std::string const& includeString);
 
   private:
@@ -234,12 +267,20 @@ public:
     bool Header = false;
   };
 
-  /// @brief Generate moc_predefs
+  /// @brief Generates additional jobs after all files have been parsed
   ///
-  class JobMocPredefsT : public JobT
+  class JobPostParseT : public JobFenceT
   {
   private:
-    void Process(WorkerT& wrk) override;
+    void Process() override;
+  };
+
+  /// @brief Generate mocs_compilation.cpp
+  ///
+  class JobMocsCompilationT : public JobFenceT
+  {
+  private:
+    void Process() override;
   };
 
   /// @brief Moc a file job
@@ -247,20 +288,20 @@ public:
   class JobMocT : public JobT
   {
   public:
-    JobMocT(std::string&& sourceFile, std::string includerFile,
-            std::string&& includeString)
+    JobMocT(std::string sourceFile, std::string includerFile,
+            std::string includeString)
       : SourceFile(std::move(sourceFile))
       , IncluderFile(std::move(includerFile))
       , IncludeString(std::move(includeString))
     {
     }
 
-    void FindDependencies(WorkerT& wrk, std::string const& content);
+    void FindDependencies(std::string const& content);
 
   private:
-    void Process(WorkerT& wrk) override;
-    bool UpdateRequired(WorkerT& wrk);
-    void GenerateMoc(WorkerT& wrk);
+    void Process() override;
+    bool UpdateRequired();
+    void GenerateMoc();
 
   public:
     std::string SourceFile;
@@ -276,8 +317,8 @@ public:
   class JobUicT : public JobT
   {
   public:
-    JobUicT(std::string&& sourceFile, std::string includerFile,
-            std::string&& includeString)
+    JobUicT(std::string sourceFile, std::string includerFile,
+            std::string includeString)
       : SourceFile(std::move(sourceFile))
       , IncluderFile(std::move(includerFile))
       , IncludeString(std::move(includeString))
@@ -285,9 +326,9 @@ public:
     }
 
   private:
-    void Process(WorkerT& wrk) override;
-    bool UpdateRequired(WorkerT& wrk);
-    void GenerateUic(WorkerT& wrk);
+    void Process() override;
+    bool UpdateRequired();
+    void GenerateUic();
 
   public:
     std::string SourceFile;
@@ -296,80 +337,12 @@ public:
     std::string BuildFile;
   };
 
-  /// @brief Worker Thread
+  /// @brief The last job
   ///
-  class WorkerT
+  class JobFinishT : public JobFenceT
   {
-  public:
-    WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop);
-    ~WorkerT();
-
-    WorkerT(WorkerT const&) = delete;
-    WorkerT& operator=(WorkerT const&) = delete;
-
-    // -- 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(); }
-
-    // -- Log info
-    void LogInfo(GenT genType, std::string const& message) const;
-    // -- Log warning
-    void LogWarning(GenT genType, std::string const& message) const;
-    void LogFileWarning(GenT genType, std::string const& filename,
-                        std::string const& message) const;
-    // -- Log error
-    void LogError(GenT genType, std::string const& message) const;
-    void LogFileError(GenT genType, std::string const& filename,
-                      std::string const& message) const;
-    void LogCommandError(GenT genType, std::string const& message,
-                         std::vector<std::string> const& command,
-                         std::string const& output) const;
-
-    // -- External processes
-    /// @brief Verbose logging version
-    bool RunProcess(GenT 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
-  {
-    SETTINGS_READ,
-    CREATE_DIRECTORIES,
-    PARSE_SOURCES,
-    PARSE_HEADERS,
-    MOC_PREDEFS,
-    MOC_PROCESS,
-    MOCS_COMPILATION,
-    UIC_PROCESS,
-    SETTINGS_WRITE,
-    FINISH,
-    END
+    void Process() override;
   };
 
   // -- Const settings interface
@@ -377,41 +350,39 @@ public:
   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);
+  cmWorkerPool& WorkerPool() { return WorkerPool_; }
+  void AbortError() { Abort(true); }
+  void AbortSuccess() { Abort(false); }
+  bool ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle);
+  bool ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle);
+
+  // -- Mocs compilation include file updated flag
+  void ParallelMocAutoUpdated() { MocAutoFileUpdated_.store(true); }
+  bool MocAutoFileUpdated() const { return MocAutoFileUpdated_.load(); }
+
+  // -- Mocs compilation file register
   std::string ParallelMocAutoRegister(std::string const& baseName);
-  void ParallelMocAutoUpdated();
+  bool ParallelMocIncluded(std::string const& sourceFile);
+  std::set<std::string> const& MocAutoFiles() const
+  {
+    return this->MocAutoFiles_;
+  }
 
 private:
   // -- Utility accessors
   Logger& Log() { return Logger_; }
   FileSystem& FileSys() { return FileSys_; }
-  // -- libuv loop accessors
-  uv_loop_t* UVLoop() { return UVLoop_.get(); }
-  cm::uv_async_ptr& UVRequest() { return UVRequest_; }
   // -- 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();
+  bool SettingsFileWrite();
   // -- Thread processing
-  bool ThreadsStartJobs(JobQueueT& queue);
-  bool ThreadsJobsDone();
-  void ThreadsStop();
-  void RegisterJobError();
+  void Abort(bool error);
   // -- Generation
-  void CreateDirectories();
-  void MocGenerateCompilation();
+  bool CreateDirectories();
 
 private:
   // -- Utility
@@ -421,39 +392,22 @@ private:
   BaseSettingsT Base_;
   MocSettingsT Moc_;
   UicSettingsT Uic_;
-  // -- 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_;
-  StageT Stage_ = StageT::SETTINGS_READ;
-  // -- Job queues
-  std::mutex JobsMutex_;
-  struct
-  {
-    JobQueueT Sources;
-    JobQueueT Headers;
-    JobQueueT MocPredefs;
-    JobQueueT Moc;
-    JobQueueT Uic;
-  } JobQueues_;
-  JobQueueT JobQueue_;
-  std::size_t volatile JobsRemain_ = 0;
-  bool volatile JobError_ = false;
-  bool volatile JobThreadsAbort_ = false;
-  std::condition_variable JobsConditionRead_;
   // -- Moc meta
-  std::set<std::string> MocIncludedStrings_;
+  std::mutex MocMetaMutex_;
   std::set<std::string> MocIncludedFiles_;
+  IncludesMap MocIncludes_;
   std::set<std::string> MocAutoFiles_;
-  bool volatile MocAutoFileUpdated_ = false;
+  std::atomic<bool> MocAutoFileUpdated_ = ATOMIC_VAR_INIT(false);
+  // -- Uic meta
+  std::mutex UicMetaMutex_;
+  IncludesMap UicIncludes_;
   // -- Settings file
   std::string SettingsFile_;
   std::string SettingsStringMoc_;
   std::string SettingsStringUic_;
-  // -- Threads and loops
-  std::vector<std::unique_ptr<WorkerT>> Workers_;
+  // -- Thread pool and job queue
+  std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
+  cmWorkerPool WorkerPool_;
 };
 
 #endif
diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx
new file mode 100644
index 0000000..464182c
--- /dev/null
+++ b/Source/cmWorkerPool.cxx
@@ -0,0 +1,770 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmWorkerPool.h"
+
+#include "cmRange.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
+#include "cm_uv.h"
+
+#include <algorithm>
+#include <array>
+#include <condition_variable>
+#include <deque>
+#include <functional>
+#include <mutex>
+#include <stddef.h>
+#include <thread>
+
+/**
+ * @brief libuv pipe buffer class
+ */
+class cmUVPipeBuffer
+{
+public:
+  typedef cmRange<char const*> DataRange;
+  typedef std::function<void(DataRange)> DataFunction;
+  /// On error the ssize_t argument is a non zero libuv error code
+  typedef std::function<void(ssize_t)> EndFunction;
+
+public:
+  /**
+   * Reset to construction state
+   */
+  void reset();
+
+  /**
+   * Initializes uv_pipe(), uv_stream() and uv_handle()
+   * @return true on success
+   */
+  bool init(uv_loop_t* uv_loop);
+
+  /**
+   * Start reading
+   * @return true on success
+   */
+  bool startRead(DataFunction dataFunction, EndFunction endFunction);
+
+  //! libuv pipe
+  uv_pipe_t* uv_pipe() const { return UVPipe_.get(); }
+  //! uv_pipe() casted to libuv stream
+  uv_stream_t* uv_stream() const { return static_cast<uv_stream_t*>(UVPipe_); }
+  //! uv_pipe() casted to libuv handle
+  uv_handle_t* uv_handle() { return static_cast<uv_handle_t*>(UVPipe_); }
+
+private:
+  // -- 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:
+  cm::uv_pipe_ptr UVPipe_;
+  std::vector<char> Buffer_;
+  DataFunction DataFunction_;
+  EndFunction EndFunction_;
+};
+
+void cmUVPipeBuffer::reset()
+{
+  if (UVPipe_.get() != nullptr) {
+    EndFunction_ = nullptr;
+    DataFunction_ = nullptr;
+    Buffer_.clear();
+    Buffer_.shrink_to_fit();
+    UVPipe_.reset();
+  }
+}
+
+bool cmUVPipeBuffer::init(uv_loop_t* uv_loop)
+{
+  reset();
+  if (uv_loop == nullptr) {
+    return false;
+  }
+  int ret = UVPipe_.init(*uv_loop, 0, this);
+  return (ret == 0);
+}
+
+bool cmUVPipeBuffer::startRead(DataFunction dataFunction,
+                               EndFunction endFunction)
+{
+  if (UVPipe_.get() == nullptr) {
+    return false;
+  }
+  if (!dataFunction || !endFunction) {
+    return false;
+  }
+  DataFunction_ = std::move(dataFunction);
+  EndFunction_ = std::move(endFunction);
+  int ret = uv_read_start(uv_stream(), &cmUVPipeBuffer::UVAlloc,
+                          &cmUVPipeBuffer::UVData);
+  return (ret == 0);
+}
+
+void cmUVPipeBuffer::UVAlloc(uv_handle_t* handle, size_t suggestedSize,
+                             uv_buf_t* buf)
+{
+  auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(handle->data);
+  pipe.Buffer_.resize(suggestedSize);
+  buf->base = pipe.Buffer_.data();
+  buf->len = static_cast<unsigned long>(pipe.Buffer_.size());
+}
+
+void cmUVPipeBuffer::UVData(uv_stream_t* stream, ssize_t nread,
+                            const uv_buf_t* buf)
+{
+  auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(stream->data);
+  if (nread > 0) {
+    if (buf->base != nullptr) {
+      // Call data function
+      pipe.DataFunction_(DataRange(buf->base, buf->base + nread));
+    }
+  } else if (nread < 0) {
+    // Save the end function on the stack before resetting the pipe
+    EndFunction efunc;
+    efunc.swap(pipe.EndFunction_);
+    // Reset pipe before calling the end function
+    pipe.reset();
+    // Call end function
+    efunc((nread == UV_EOF) ? 0 : nread);
+  }
+}
+
+/**
+ * @brief External process management class
+ */
+class cmUVReadOnlyProcess
+{
+public:
+  // -- Types
+  //! @brief Process settings
+  struct SetupT
+  {
+    std::string WorkingDirectory;
+    std::vector<std::string> Command;
+    cmWorkerPool::ProcessResultT* Result = nullptr;
+    bool MergedOutput = false;
+  };
+
+public:
+  // -- Const accessors
+  SetupT const& Setup() const { return Setup_; }
+  cmWorkerPool::ProcessResultT* Result() const { return Setup_.Result; }
+  bool IsStarted() const { return IsStarted_; }
+  bool IsFinished() const { return IsFinished_; }
+
+  // -- Runtime
+  void setup(cmWorkerPool::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:
+  // -- Libuv callbacks
+  static void UVExit(uv_process_t* handle, int64_t exitStatus, int termSignal);
+  void UVPipeOutData(cmUVPipeBuffer::DataRange data);
+  void UVPipeOutEnd(ssize_t error);
+  void UVPipeErrData(cmUVPipeBuffer::DataRange data);
+  void UVPipeErrEnd(ssize_t error);
+  void UVTryFinish();
+
+private:
+  // -- 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_;
+  cmUVPipeBuffer UVPipeOut_;
+  cmUVPipeBuffer UVPipeErr_;
+};
+
+void cmUVReadOnlyProcess::setup(cmWorkerPool::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 cmUVReadOnlyProcess::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)) {
+      Result()->ErrorMessage = "libuv stdout pipe initialization failed";
+    }
+  }
+  if (!Result()->error()) {
+    if (!UVPipeErr_.init(uv_loop)) {
+      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 = &cmUVReadOnlyProcess::UVExit;
+    UVOptions_.file = CommandPtr_[0];
+    UVOptions_.args = const_cast<char**>(CommandPtr_.data());
+    UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
+    UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
+    UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
+    UVOptions_.stdio = UVOptionsStdIO_.data();
+
+    // -- Spawn process
+    int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
+    if (uvErrorCode != 0) {
+      Result()->ErrorMessage = "libuv process spawn failed";
+      if (const char* uvErr = uv_strerror(uvErrorCode)) {
+        Result()->ErrorMessage += ": ";
+        Result()->ErrorMessage += uvErr;
+      }
+    }
+  }
+  // -- Start reading from stdio streams
+  if (!Result()->error()) {
+    if (!UVPipeOut_.startRead(
+          [this](cmUVPipeBuffer::DataRange range) {
+            this->UVPipeOutData(range);
+          },
+          [this](ssize_t error) { this->UVPipeOutEnd(error); })) {
+      Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
+    }
+  }
+  if (!Result()->error()) {
+    if (!UVPipeErr_.startRead(
+          [this](cmUVPipeBuffer::DataRange range) {
+            this->UVPipeErrData(range);
+          },
+          [this](ssize_t error) { this->UVPipeErrEnd(error); })) {
+      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 cmUVReadOnlyProcess::UVExit(uv_process_t* handle, int64_t exitStatus,
+                                 int termSignal)
+{
+  auto& proc = *reinterpret_cast<cmUVReadOnlyProcess*>(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
+    proc.UVProcess_.reset();
+    // Try finish
+    proc.UVTryFinish();
+  }
+}
+
+void cmUVReadOnlyProcess::UVPipeOutData(cmUVPipeBuffer::DataRange data)
+{
+  Result()->StdOut.append(data.begin(), data.end());
+}
+
+void cmUVReadOnlyProcess::UVPipeOutEnd(ssize_t error)
+{
+  // Process pipe error
+  if ((error != 0) && !Result()->error()) {
+    Result()->ErrorMessage =
+      "Reading from stdout pipe failed with libuv error code ";
+    Result()->ErrorMessage += std::to_string(error);
+  }
+  // Try finish
+  UVTryFinish();
+}
+
+void cmUVReadOnlyProcess::UVPipeErrData(cmUVPipeBuffer::DataRange data)
+{
+  std::string* str =
+    Setup_.MergedOutput ? &Result()->StdOut : &Result()->StdErr;
+  str->append(data.begin(), data.end());
+}
+
+void cmUVReadOnlyProcess::UVPipeErrEnd(ssize_t error)
+{
+  // Process pipe error
+  if ((error != 0) && !Result()->error()) {
+    Result()->ErrorMessage =
+      "Reading from stderr pipe failed with libuv error code ";
+    Result()->ErrorMessage += std::to_string(error);
+  }
+  // Try finish
+  UVTryFinish();
+}
+
+void cmUVReadOnlyProcess::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) || (UVPipeOut_.uv_pipe() != nullptr) ||
+      (UVPipeErr_.uv_pipe() != nullptr)) {
+    return;
+  }
+  IsFinished_ = true;
+  FinishedCallback_();
+}
+
+/**
+ * @brief Private worker pool internals
+ */
+class cmWorkerPoolInternal
+{
+public:
+  // -- Types
+
+  /**
+   * @brief Worker thread
+   */
+  class WorkerT
+  {
+  public:
+    WorkerT(unsigned int index);
+    ~WorkerT();
+
+    WorkerT(WorkerT const&) = delete;
+    WorkerT& operator=(WorkerT const&) = delete;
+
+    /**
+     * Start the thread
+     */
+    void Start(cmWorkerPoolInternal* internal);
+
+    /**
+     * @brief Run an external process
+     */
+    bool RunProcess(cmWorkerPool::ProcessResultT& result,
+                    std::vector<std::string> const& command,
+                    std::string const& workingDirectory);
+
+    // -- Accessors
+    unsigned int Index() const { return Index_; }
+    cmWorkerPool::JobHandleT& JobHandle() { return JobHandle_; }
+
+  private:
+    // -- Libuv callbacks
+    static void UVProcessStart(uv_async_t* handle);
+    void UVProcessFinished();
+
+  private:
+    //! @brief Job handle
+    cmWorkerPool::JobHandleT JobHandle_;
+    //! @brief Worker index
+    unsigned int Index_;
+    // -- Process management
+    struct
+    {
+      std::mutex Mutex;
+      cm::uv_async_ptr Request;
+      std::condition_variable Condition;
+      std::unique_ptr<cmUVReadOnlyProcess> ROP;
+    } Proc_;
+    // -- System thread
+    std::thread Thread_;
+  };
+
+public:
+  // -- Constructors
+  cmWorkerPoolInternal(cmWorkerPool* pool);
+  ~cmWorkerPoolInternal();
+
+  /**
+   * @brief Runs the libuv loop
+   */
+  bool Process();
+
+  /**
+   * @brief Clear queue and abort threads
+   */
+  void Abort();
+
+  /**
+   * @brief Push a job to the queue and notify a worker
+   */
+  bool PushJob(cmWorkerPool::JobHandleT&& jobHandle);
+
+  /**
+   * @brief Worker thread main loop method
+   */
+  void Work(WorkerT* worker);
+
+  // -- Request slots
+  static void UVSlotBegin(uv_async_t* handle);
+  static void UVSlotEnd(uv_async_t* handle);
+
+public:
+  // -- UV loop
+#ifdef CMAKE_UV_SIGNAL_HACK
+  std::unique_ptr<cmUVSignalHackRAII> UVHackRAII;
+#endif
+  std::unique_ptr<uv_loop_t> UVLoop;
+  cm::uv_async_ptr UVRequestBegin;
+  cm::uv_async_ptr UVRequestEnd;
+
+  // -- Thread pool and job queue
+  std::mutex Mutex;
+  bool Aborting = false;
+  bool FenceProcessing = false;
+  unsigned int WorkersRunning = 0;
+  unsigned int WorkersIdle = 0;
+  unsigned int JobsProcessing = 0;
+  std::deque<cmWorkerPool::JobHandleT> Queue;
+  std::condition_variable Condition;
+  std::vector<std::unique_ptr<WorkerT>> Workers;
+
+  // -- References
+  cmWorkerPool* Pool = nullptr;
+};
+
+cmWorkerPoolInternal::WorkerT::WorkerT(unsigned int index)
+  : Index_(index)
+{
+}
+
+cmWorkerPoolInternal::WorkerT::~WorkerT()
+{
+  if (Thread_.joinable()) {
+    Thread_.join();
+  }
+}
+
+void cmWorkerPoolInternal::WorkerT::Start(cmWorkerPoolInternal* internal)
+{
+  Proc_.Request.init(*(internal->UVLoop), &WorkerT::UVProcessStart, this);
+  Thread_ = std::thread(&cmWorkerPoolInternal::Work, internal, this);
+}
+
+bool cmWorkerPoolInternal::WorkerT::RunProcess(
+  cmWorkerPool::ProcessResultT& result,
+  std::vector<std::string> const& command, std::string const& workingDirectory)
+{
+  if (command.empty()) {
+    return false;
+  }
+  // Create process instance
+  {
+    std::lock_guard<std::mutex> lock(Proc_.Mutex);
+    Proc_.ROP = cm::make_unique<cmUVReadOnlyProcess>();
+    Proc_.ROP->setup(&result, true, command, workingDirectory);
+  }
+  // Send asynchronous process start request to libuv loop
+  Proc_.Request.send();
+  // Wait until the process has been finished and destroyed
+  {
+    std::unique_lock<std::mutex> ulock(Proc_.Mutex);
+    while (Proc_.ROP) {
+      Proc_.Condition.wait(ulock);
+    }
+  }
+  return !result.error();
+}
+
+void cmWorkerPoolInternal::WorkerT::UVProcessStart(uv_async_t* handle)
+{
+  auto* wrk = reinterpret_cast<WorkerT*>(handle->data);
+  bool startFailed = false;
+  {
+    auto& Proc = wrk->Proc_;
+    std::lock_guard<std::mutex> lock(Proc.Mutex);
+    if (Proc.ROP && !Proc.ROP->IsStarted()) {
+      startFailed =
+        !Proc.ROP->start(handle->loop, [wrk] { wrk->UVProcessFinished(); });
+    }
+  }
+  // Clean up if starting of the process failed
+  if (startFailed) {
+    wrk->UVProcessFinished();
+  }
+}
+
+void cmWorkerPoolInternal::WorkerT::UVProcessFinished()
+{
+  {
+    std::lock_guard<std::mutex> lock(Proc_.Mutex);
+    if (Proc_.ROP && (Proc_.ROP->IsFinished() || !Proc_.ROP->IsStarted())) {
+      Proc_.ROP.reset();
+    }
+  }
+  // Notify idling thread
+  Proc_.Condition.notify_one();
+}
+
+void cmWorkerPool::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();
+  }
+}
+
+cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool)
+  : Pool(pool)
+{
+  // 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.get());
+}
+
+cmWorkerPoolInternal::~cmWorkerPoolInternal()
+{
+  uv_loop_close(UVLoop.get());
+}
+
+bool cmWorkerPoolInternal::Process()
+{
+  // Reset state
+  Aborting = false;
+  // Initialize libuv asynchronous request
+  UVRequestBegin.init(*UVLoop, &cmWorkerPoolInternal::UVSlotBegin, this);
+  UVRequestEnd.init(*UVLoop, &cmWorkerPoolInternal::UVSlotEnd, this);
+  // Send begin request
+  UVRequestBegin.send();
+  // Run libuv loop
+  return (uv_run(UVLoop.get(), UV_RUN_DEFAULT) == 0);
+}
+
+void cmWorkerPoolInternal::Abort()
+{
+  bool firstCall = false;
+  // Clear all jobs and set abort flag
+  {
+    std::lock_guard<std::mutex> guard(Mutex);
+    if (!Aborting) {
+      // Register abort and clear queue
+      Aborting = true;
+      Queue.clear();
+      firstCall = true;
+    }
+  }
+  if (firstCall) {
+    // Wake threads
+    Condition.notify_all();
+  }
+}
+
+inline bool cmWorkerPoolInternal::PushJob(cmWorkerPool::JobHandleT&& jobHandle)
+{
+  std::lock_guard<std::mutex> guard(Mutex);
+  if (Aborting) {
+    return false;
+  }
+
+  // Append the job to the queue
+  Queue.emplace_back(std::move(jobHandle));
+
+  // Notify an idle worker if there's one
+  if (WorkersIdle != 0) {
+    Condition.notify_one();
+  }
+
+  return true;
+}
+
+void cmWorkerPoolInternal::UVSlotBegin(uv_async_t* handle)
+{
+  auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
+  // Create worker threads
+  {
+    unsigned int const num = gint.Pool->ThreadCount();
+    // Create workers
+    gint.Workers.reserve(num);
+    for (unsigned int ii = 0; ii != num; ++ii) {
+      gint.Workers.emplace_back(cm::make_unique<WorkerT>(ii));
+    }
+    // Start workers
+    for (auto& wrk : gint.Workers) {
+      wrk->Start(&gint);
+    }
+  }
+  // Destroy begin request
+  gint.UVRequestBegin.reset();
+}
+
+void cmWorkerPoolInternal::UVSlotEnd(uv_async_t* handle)
+{
+  auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
+  // Join and destroy worker threads
+  gint.Workers.clear();
+  // Destroy end request
+  gint.UVRequestEnd.reset();
+}
+
+void cmWorkerPoolInternal::Work(WorkerT* worker)
+{
+  std::unique_lock<std::mutex> uLock(Mutex);
+  // Increment running workers count
+  ++WorkersRunning;
+  // Enter worker main loop
+  while (true) {
+    // Abort on request
+    if (Aborting) {
+      break;
+    }
+    // Wait for new jobs
+    if (Queue.empty()) {
+      ++WorkersIdle;
+      Condition.wait(uLock);
+      --WorkersIdle;
+      continue;
+    }
+
+    // Check for fence jobs
+    if (FenceProcessing || Queue.front()->IsFence()) {
+      if (JobsProcessing != 0) {
+        Condition.wait(uLock);
+        continue;
+      }
+      // No jobs get processed. Set the fence job processing flag.
+      FenceProcessing = true;
+    }
+
+    // Pop next job from queue
+    worker->JobHandle() = std::move(Queue.front());
+    Queue.pop_front();
+
+    // Unlocked scope for job processing
+    ++JobsProcessing;
+    {
+      uLock.unlock();
+      worker->JobHandle()->Work(Pool, worker->Index()); // Process job
+      worker->JobHandle().reset();                      // Destroy job
+      uLock.lock();
+    }
+    --JobsProcessing;
+
+    // Was this a fence job?
+    if (FenceProcessing) {
+      FenceProcessing = false;
+      Condition.notify_all();
+    }
+  }
+
+  // Decrement running workers count
+  if (--WorkersRunning == 0) {
+    // Last worker thread about to finish. Send libuv event.
+    UVRequestEnd.send();
+  }
+}
+
+cmWorkerPool::JobT::~JobT() = default;
+
+bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result,
+                                    std::vector<std::string> const& command,
+                                    std::string const& workingDirectory)
+{
+  // Get worker by index
+  auto* wrk = Pool_->Int_->Workers.at(WorkerIndex_).get();
+  return wrk->RunProcess(result, command, workingDirectory);
+}
+
+cmWorkerPool::cmWorkerPool()
+  : Int_(cm::make_unique<cmWorkerPoolInternal>(this))
+{
+}
+
+cmWorkerPool::~cmWorkerPool() = default;
+
+bool cmWorkerPool::Process(unsigned int threadCount, void* userData)
+{
+  // Setup user data
+  UserData_ = userData;
+  ThreadCount_ = (threadCount > 0) ? threadCount : 1u;
+
+  // Run libuv loop
+  bool success = Int_->Process();
+
+  // Clear user data
+  UserData_ = nullptr;
+  ThreadCount_ = 0;
+
+  return success;
+}
+
+bool cmWorkerPool::PushJob(JobHandleT&& jobHandle)
+{
+  return Int_->PushJob(std::move(jobHandle));
+}
+
+void cmWorkerPool::Abort()
+{
+  Int_->Abort();
+}
diff --git a/Source/cmWorkerPool.h b/Source/cmWorkerPool.h
new file mode 100644
index 0000000..71c7d84
--- /dev/null
+++ b/Source/cmWorkerPool.h
@@ -0,0 +1,219 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmWorkerPool_h
+#define cmWorkerPool_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmAlgorithms.h" // IWYU pragma: keep
+
+#include <memory> // IWYU pragma: keep
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+// -- Types
+class cmWorkerPoolInternal;
+
+/** @class cmWorkerPool
+ * @brief Thread pool with job queue
+ */
+class cmWorkerPool
+{
+public:
+  /**
+   * 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;
+  };
+
+  /**
+   * Abstract job class for concurrent job processing.
+   */
+  class JobT
+  {
+  public:
+    JobT(JobT const&) = delete;
+    JobT& operator=(JobT const&) = delete;
+
+    /**
+     * @brief Virtual destructor.
+     */
+    virtual ~JobT();
+
+    /**
+     * @brief Fence job flag
+     *
+     * Fence jobs require that:
+     * - all jobs before in the queue have been processed
+     * - no jobs later in the queue will be processed before this job was
+     *   processed
+     */
+    bool IsFence() const { return Fence_; }
+
+  protected:
+    /**
+     * @brief Protected default constructor
+     */
+    JobT(bool fence = false)
+      : Fence_(fence)
+    {
+    }
+
+    /**
+     * Abstract processing interface that must be implement in derived classes.
+     */
+    virtual void Process() = 0;
+
+    /**
+     * Get the worker pool.
+     * Only valid during the JobT::Process() call!
+     */
+    cmWorkerPool* Pool() const { return Pool_; }
+
+    /**
+     * Get the user data.
+     * Only valid during the JobT::Process() call!
+     */
+    void* UserData() const { return Pool_->UserData(); };
+
+    /**
+     * Get the worker index.
+     * This is the index of the thread processing this job and is in the range
+     * [0..ThreadCount).
+     * Concurrently processing jobs will never have the same WorkerIndex().
+     * Only valid during the JobT::Process() call!
+     */
+    unsigned int WorkerIndex() const { return WorkerIndex_; }
+
+    /**
+     * Run an external read only process.
+     * Use only during JobT::Process() call!
+     */
+    bool RunProcess(ProcessResultT& result,
+                    std::vector<std::string> const& command,
+                    std::string const& workingDirectory);
+
+  private:
+    //! Needs access to Work()
+    friend class cmWorkerPoolInternal;
+    //! Worker thread entry method.
+    void Work(cmWorkerPool* pool, unsigned int workerIndex)
+    {
+      Pool_ = pool;
+      WorkerIndex_ = workerIndex;
+      this->Process();
+    }
+
+  private:
+    cmWorkerPool* Pool_ = nullptr;
+    unsigned int WorkerIndex_ = 0;
+    bool Fence_ = false;
+  };
+
+  /**
+   * @brief Job handle type
+   */
+  typedef std::unique_ptr<JobT> JobHandleT;
+
+  /**
+   * @brief Fence job base class
+   */
+  class JobFenceT : public JobT
+  {
+  public:
+    JobFenceT()
+      : JobT(true)
+    {
+    }
+    //! Does nothing
+    void Process() override{};
+  };
+
+  /**
+   * @brief Fence job that aborts the worker pool.
+   * This class is useful as the last job in the job queue.
+   */
+  class JobEndT : JobFenceT
+  {
+  public:
+    //! Does nothing
+    void Process() override { Pool()->Abort(); }
+  };
+
+public:
+  // -- Methods
+  cmWorkerPool();
+  ~cmWorkerPool();
+
+  /**
+   * @brief Blocking function that starts threads to process all Jobs in
+   *        the queue.
+   *
+   * This method blocks until a job calls the Abort() method.
+   * @arg threadCount Number of threads to process jobs.
+   * @arg userData Common user data pointer available in all Jobs.
+   */
+  bool Process(unsigned int threadCount, void* userData = nullptr);
+
+  /**
+   * Number of worker threads passed to Process().
+   * Only valid during Process().
+   */
+  unsigned int ThreadCount() const { return ThreadCount_; }
+
+  /**
+   * User data reference passed to Process().
+   * Only valid during Process().
+   */
+  void* UserData() const { return UserData_; }
+
+  // -- Job processing interface
+
+  /**
+   * @brief Clears the job queue and aborts all worker threads.
+   *
+   * This method is thread safe and can be called from inside a job.
+   */
+  void Abort();
+
+  /**
+   * @brief Push job to the queue.
+   *
+   * This method is thread safe and can be called from inside a job or before
+   * Process().
+   */
+  bool PushJob(JobHandleT&& jobHandle);
+
+  /**
+   * @brief Push job to the queue
+   *
+   * This method is thread safe and can be called from inside a job or before
+   * Process().
+   */
+  template <class T, typename... Args>
+  bool EmplaceJob(Args&&... args)
+  {
+    return PushJob(cm::make_unique<T>(std::forward<Args>(args)...));
+  }
+
+private:
+  void* UserData_ = nullptr;
+  unsigned int ThreadCount_ = 0;
+  std::unique_ptr<cmWorkerPoolInternal> Int_;
+};
+
+#endif

-----------------------------------------------------------------------

Summary of changes:
 Help/command/string.rst                            |    9 +
 Help/manual/cmake-generator-expressions.7.rst      |   55 +-
 Help/release/dev/genex-TARGET_FILE_BASE_NAME.rst   |    7 +
 Help/release/dev/genex-TARGET_OUTPUT_NAME.rst      |    7 -
 Help/release/dev/string-repeat.rst                 |    4 +
 Modules/FindBoost.cmake                            |    2 +-
 Modules/InstallRequiredSystemLibraries.cmake       |    7 +-
 Source/CMakeLists.txt                              |    6 +-
 Source/cmGeneratorExpressionNode.cxx               |   32 +-
 Source/cmQtAutoGenerator.cxx                       |  232 ----
 Source/cmQtAutoGenerator.h                         |  102 --
 ...tAutoGeneratorMocUic.cxx => cmQtAutoMocUic.cxx} | 1211 ++++++++------------
 ...{cmQtAutoGeneratorMocUic.h => cmQtAutoMocUic.h} |  298 ++---
 Source/cmStringCommand.cxx                         |   61 +
 Source/cmStringCommand.h                           |    1 +
 Source/cmWorkerPool.cxx                            |  770 +++++++++++++
 Source/cmWorkerPool.h                              |  219 ++++
 Source/cmcmd.cxx                                   |    4 +-
 Tests/FindBoost/CMakeLists.txt                     |   13 +
 Tests/FindBoost/TestPython/CMakeLists.txt          |   17 +
 Tests/QtAutogen/ManySources/CMakeLists.txt         |   35 +
 Tests/QtAutogen/ManySources/data.qrc.in            |    7 +
 Tests/QtAutogen/ManySources/item.cpp.in            |   27 +
 Tests/QtAutogen/ManySources/item.h.in              |   15 +
 Tests/QtAutogen/ManySources/main.cpp.in            |    7 +
 Tests/QtAutogen/ManySources/object.h.in            |   15 +
 Tests/QtAutogen/ManySources/view.ui.in             |   24 +
 Tests/QtAutogen/Tests.cmake                        |    1 +
 ...tedTarget-TARGET_PDB_FILE_BASE_NAME-result.txt} |    0
 ...rtedTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt |    8 +
 .../ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake |    2 +
 ...mportedTarget-TARGET_PDB_OUTPUT_NAME-result.txt |    1 -
 ...mportedTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt |    8 -
 .../ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake    |    2 -
 ...dCompiler-TARGET_PDB_FILE_BASE_NAME-result.txt} |    0
 ...idCompiler-TARGET_PDB_FILE_BASE_NAME-stderr.txt |    8 +
 ...nValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake} |    2 +-
 ...ValidCompiler-TARGET_PDB_OUTPUT_NAME-result.txt |    1 -
 ...ValidCompiler-TARGET_PDB_OUTPUT_NAME-stderr.txt |    8 -
 ...lidTarget-TARGET_PDB_FILE_BASE_NAME-result.txt} |    0
 ...alidTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt |    9 +
 ...NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake} |    2 +-
 ...onValidTarget-TARGET_PDB_OUTPUT_NAME-result.txt |    1 -
 ...onValidTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt |    9 -
 .../OUTPUT_NAME-recursion.cmake                    |    2 +-
 .../GeneratorExpression/RunCMakeTest.cmake         |   16 +-
 .../TARGET_FILE_BASE_NAME-check.cmake              |    2 +
 ...RGET_FILE_BASE_NAME-imported-target-check.cmake |    2 +
 ...=> TARGET_FILE_BASE_NAME-imported-target.cmake} |   32 +-
 ...GET_FILE_BASE_NAME-non-valid-target-result.txt} |    0
 ...RGET_FILE_BASE_NAME-non-valid-target-stderr.txt |    6 +
 ...> TARGET_FILE_BASE_NAME-non-valid-target.cmake} |    2 +-
 .../TARGET_FILE_BASE_NAME.cmake                    |   96 ++
 ...KER_FILE_BASE_NAME-non-valid-target-result.txt} |    0
 ...NKER_FILE_BASE_NAME-non-valid-target-stderr.txt |    6 +
 ...T_LINKER_FILE_BASE_NAME-non-valid-target.cmake} |    2 +-
 ..._LINKER_OUTPUT_NAME-non-valid-target-result.txt |    1 -
 ..._LINKER_OUTPUT_NAME-non-valid-target-stderr.txt |    6 -
 .../TARGET_OUTPUT_NAME-check.cmake                 |    2 -
 .../TARGET_OUTPUT_NAME-imported-target-check.cmake |    2 -
 .../TARGET_OUTPUT_NAME-non-valid-target-result.txt |    1 -
 .../TARGET_OUTPUT_NAME-non-valid-target-stderr.txt |    6 -
 .../GeneratorExpression/TARGET_OUTPUT_NAME.cmake   |   96 --
 ...lidTarget-TARGET_PDB_FILE_BASE_NAME-check.cmake |    7 +
 ...=> ValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake} |    2 +-
 .../ValidTarget-TARGET_PDB_OUTPUT_NAME-check.cmake |    7 -
 Tests/RunCMake/string/Repeat.cmake                 |   45 +
 .../RepeatNegativeCount-result.txt}                |    0
 .../RunCMake/string/RepeatNegativeCount-stderr.txt |    4 +
 Tests/RunCMake/string/RepeatNegativeCount.cmake    |    1 +
 .../RepeatNoArgs-result.txt}                       |    0
 Tests/RunCMake/string/RepeatNoArgs-stderr.txt      |    4 +
 Tests/RunCMake/string/RepeatNoArgs.cmake           |    1 +
 Tests/RunCMake/string/RunCMakeTest.cmake           |    4 +
 74 files changed, 2114 insertions(+), 1490 deletions(-)
 create mode 100644 Help/release/dev/genex-TARGET_FILE_BASE_NAME.rst
 delete mode 100644 Help/release/dev/genex-TARGET_OUTPUT_NAME.rst
 create mode 100644 Help/release/dev/string-repeat.rst
 rename Source/{cmQtAutoGeneratorMocUic.cxx => cmQtAutoMocUic.cxx} (59%)
 rename Source/{cmQtAutoGeneratorMocUic.h => cmQtAutoMocUic.h} (56%)
 create mode 100644 Source/cmWorkerPool.cxx
 create mode 100644 Source/cmWorkerPool.h
 create mode 100644 Tests/FindBoost/TestPython/CMakeLists.txt
 create mode 100644 Tests/QtAutogen/ManySources/CMakeLists.txt
 create mode 100644 Tests/QtAutogen/ManySources/data.qrc.in
 create mode 100644 Tests/QtAutogen/ManySources/item.cpp.in
 create mode 100644 Tests/QtAutogen/ManySources/item.h.in
 create mode 100644 Tests/QtAutogen/ManySources/main.cpp.in
 create mode 100644 Tests/QtAutogen/ManySources/object.h.in
 create mode 100644 Tests/QtAutogen/ManySources/view.ui.in
 copy Tests/RunCMake/{while/MissingArgument-result.txt => GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-result.txt} (100%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_FILE_BASE_NAME.cmake
 delete mode 100644 Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-result.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/ImportedTarget-TARGET_PDB_OUTPUT_NAME.cmake
 copy Tests/RunCMake/{while/MissingArgument-result.txt => GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-result.txt} (100%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_FILE_BASE_NAME-stderr.txt
 rename Tests/RunCMake/GeneratorExpression/{NonValidTarget-TARGET_PDB_OUTPUT_NAME.cmake => NonValidCompiler-TARGET_PDB_FILE_BASE_NAME.cmake} (71%)
 delete mode 100644 Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-result.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/NonValidCompiler-TARGET_PDB_OUTPUT_NAME-stderr.txt
 copy Tests/RunCMake/{while/MissingArgument-result.txt => GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-result.txt} (100%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_FILE_BASE_NAME-stderr.txt
 rename Tests/RunCMake/GeneratorExpression/{NonValidCompiler-TARGET_PDB_OUTPUT_NAME.cmake => NonValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake} (71%)
 delete mode 100644 Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-result.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/NonValidTarget-TARGET_PDB_OUTPUT_NAME-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-check.cmake
 create mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-imported-target-check.cmake
 rename Tests/RunCMake/GeneratorExpression/{TARGET_OUTPUT_NAME-imported-target.cmake => TARGET_FILE_BASE_NAME-imported-target.cmake} (51%)
 copy Tests/RunCMake/{while/MissingArgument-result.txt => GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-result.txt} (100%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME-non-valid-target-stderr.txt
 rename Tests/RunCMake/GeneratorExpression/{TARGET_OUTPUT_NAME-non-valid-target.cmake => TARGET_FILE_BASE_NAME-non-valid-target.cmake} (66%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_FILE_BASE_NAME.cmake
 copy Tests/RunCMake/{while/MissingArgument-result.txt => GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-result.txt} (100%)
 create mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_BASE_NAME-non-valid-target-stderr.txt
 rename Tests/RunCMake/GeneratorExpression/{TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake => TARGET_LINKER_FILE_BASE_NAME-non-valid-target.cmake} (63%)
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-result.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-check.cmake
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-imported-target-check.cmake
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-result.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
 delete mode 100644 Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME.cmake
 create mode 100644 Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_FILE_BASE_NAME-check.cmake
 rename Tests/RunCMake/GeneratorExpression/{ValidTarget-TARGET_PDB_OUTPUT_NAME.cmake => ValidTarget-TARGET_PDB_FILE_BASE_NAME.cmake} (88%)
 delete mode 100644 Tests/RunCMake/GeneratorExpression/ValidTarget-TARGET_PDB_OUTPUT_NAME-check.cmake
 create mode 100644 Tests/RunCMake/string/Repeat.cmake
 copy Tests/RunCMake/{while/MissingArgument-result.txt => string/RepeatNegativeCount-result.txt} (100%)
 create mode 100644 Tests/RunCMake/string/RepeatNegativeCount-stderr.txt
 create mode 100644 Tests/RunCMake/string/RepeatNegativeCount.cmake
 copy Tests/RunCMake/{while/MissingArgument-result.txt => string/RepeatNoArgs-result.txt} (100%)
 create mode 100644 Tests/RunCMake/string/RepeatNoArgs-stderr.txt
 create mode 100644 Tests/RunCMake/string/RepeatNoArgs.cmake


hooks/post-receive
-- 
CMake


More information about the Cmake-commits mailing list