From 1ab3881ec9e809ac5f6cad5cd84048310b8683e2 Mon Sep 17 00:00:00 2001
From: Florian Maushart <FloriansGit@online.ms>
Date: Sat, 14 Apr 2018 22:50:19 +0200
Subject: cmake: Add options for parallel builds to --build mode

While we already support `cmake --build . -- -j`, the options after `--`
are specific to the native build tool.  Add new options `--parallel
[<N>]` and `-j [<N>]` to abstract this and map to the proper option
for the native build tool.
---
 Help/envvar/CMAKE_BUILD_PARALLEL_LEVEL.rst         |  9 ++++
 Help/manual/cmake-env-variables.7.rst              |  1 +
 Help/manual/cmake.1.rst                            |  7 +++
 Help/release/dev/parallel_build_option.rst         |  6 +++
 Source/CTest/cmCTestBuildAndTestHandler.cxx        |  6 +--
 Source/cmCoreTryCompile.cxx                        |  3 +-
 Source/cmGlobalBorlandMakefileGenerator.cxx        | 29 +++++++++++++
 Source/cmGlobalBorlandMakefileGenerator.h          | 24 ++++++++---
 Source/cmGlobalGenerator.cxx                       | 21 ++++++---
 Source/cmGlobalGenerator.h                         | 13 +++---
 Source/cmGlobalGhsMultiGenerator.cxx               |  9 +++-
 Source/cmGlobalGhsMultiGenerator.h                 |  2 +-
 Source/cmGlobalJOMMakefileGenerator.cxx            | 26 +++++++++++
 Source/cmGlobalJOMMakefileGenerator.h              | 18 ++++++--
 Source/cmGlobalNMakeMakefileGenerator.cxx          | 37 ++++++++++++++++
 Source/cmGlobalNMakeMakefileGenerator.h            | 20 +++++++--
 Source/cmGlobalNinjaGenerator.cxx                  |  8 +++-
 Source/cmGlobalNinjaGenerator.h                    | 12 +++---
 Source/cmGlobalUnixMakefileGenerator3.cxx          | 45 +++++++++----------
 Source/cmGlobalUnixMakefileGenerator3.h            | 12 +++---
 Source/cmGlobalVisualStudio10Generator.cxx         | 16 ++++++-
 Source/cmGlobalVisualStudio10Generator.h           | 12 +++---
 Source/cmGlobalVisualStudio7Generator.cxx          |  2 +-
 Source/cmGlobalVisualStudio7Generator.h            | 12 +++---
 Source/cmGlobalWatcomWMakeGenerator.cxx            | 30 +++++++++++++
 Source/cmGlobalWatcomWMakeGenerator.h              | 11 +++++
 Source/cmGlobalXCodeGenerator.cxx                  | 10 ++++-
 Source/cmGlobalXCodeGenerator.h                    | 12 +++---
 Source/cmMakefile.cxx                              |  4 +-
 Source/cmMakefile.h                                |  3 +-
 Source/cmake.cxx                                   |  6 ++-
 Source/cmake.h                                     |  5 ++-
 Source/cmakemain.cxx                               | 50 +++++++++++++++++++++-
 ...BuildDir--build--parallel-bad-number-result.txt |  1 +
 ...BuildDir--build--parallel-bad-number-stderr.txt |  3 ++
 ...uildDir--build--parallel-good-number-stderr.txt |  1 +
 ...arallel-good-number-trailing--target-stderr.txt |  1 +
 ...build--parallel-good-number-trailing-stderr.txt |  1 +
 .../BuildDir--build--parallel-no-number-stderr.txt |  1 +
 ...-parallel-no-number-trailing--target-stderr.txt |  1 +
 ...--build--parallel-no-number-trailing-stderr.txt |  1 +
 .../BuildDir--build-jobs-bad-number-result.txt     |  1 +
 .../BuildDir--build-jobs-bad-number-stderr.txt     |  3 ++
 .../BuildDir--build-jobs-good-number-stderr.txt    |  1 +
 ...ld-jobs-good-number-trailing--target-stderr.txt |  1 +
 ...Dir--build-jobs-good-number-trailing-stderr.txt |  1 +
 .../BuildDir--build-jobs-no-number-stderr.txt      |  1 +
 ...uild-jobs-no-number-trailing--target-stderr.txt |  1 +
 ...ldDir--build-jobs-no-number-trailing-stderr.txt |  1 +
 Tests/RunCMake/CommandLine/RunCMakeTest.cmake      | 23 ++++++++++
 50 files changed, 424 insertions(+), 100 deletions(-)
 create mode 100644 Help/envvar/CMAKE_BUILD_PARALLEL_LEVEL.rst
 create mode 100644 Help/release/dev/parallel_build_option.rst
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing--target-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing--target-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing--target-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing--target-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing-stderr.txt

diff --git a/Help/envvar/CMAKE_BUILD_PARALLEL_LEVEL.rst b/Help/envvar/CMAKE_BUILD_PARALLEL_LEVEL.rst
new file mode 100644
index 0000000..198dc51
--- /dev/null
+++ b/Help/envvar/CMAKE_BUILD_PARALLEL_LEVEL.rst
@@ -0,0 +1,9 @@
+CMAKE_BUILD_PARALLEL_LEVEL
+--------------------------
+
+Specifies the maximum number of concurrent processes to use when building
+using the ``cmake --build`` command line
+:ref:`Build Tool Mode <Build Tool Mode>`.
+
+If this variable is defined empty the native build tool's default number is
+used.
diff --git a/Help/manual/cmake-env-variables.7.rst b/Help/manual/cmake-env-variables.7.rst
index 2d8869f..2d17bb5 100644
--- a/Help/manual/cmake-env-variables.7.rst
+++ b/Help/manual/cmake-env-variables.7.rst
@@ -13,6 +13,7 @@ Environment Variables that Control the Build
 .. toctree::
    :maxdepth: 1
 
+   /envvar/CMAKE_BUILD_PARALLEL_LEVEL
    /envvar/CMAKE_CONFIG_TYPE
    /envvar/CMAKE_MSVCIDE_RUN_PATH
    /envvar/CMAKE_OSX_ARCHITECTURES
diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index 577d321..177acd4 100644
--- a/Help/manual/cmake.1.rst
+++ b/Help/manual/cmake.1.rst
@@ -159,6 +159,13 @@ following options:
 ``--build <dir>``
   Project binary directory to be built.  This is required and must be first.
 
+``-j [<jobs>], --parallel [<jobs>]``
+  The maximum number of concurrent processes to use when building.
+  If ``<jobs>`` is omitted the native build tool's default number is used.
+
+  The :envvar:`CMAKE_BUILD_PARALLEL_LEVEL` environment variable, if set,
+  specifies a default parallel level when this option is not given.
+
 ``--target <tgt>``
   Build ``<tgt>`` instead of default targets.  May only be specified once.
 
diff --git a/Help/release/dev/parallel_build_option.rst b/Help/release/dev/parallel_build_option.rst
new file mode 100644
index 0000000..2451fd0
--- /dev/null
+++ b/Help/release/dev/parallel_build_option.rst
@@ -0,0 +1,6 @@
+parallel_build_option
+---------------------
+
+* The :manual:`cmake(1)` :ref:`Build Tool Mode` (``cmake --build``) gained
+  ``--parallel [<jobs>]`` and ``-j [<jobs>]`` options to specify a parallel
+  build level.  They map to corresponding options of the native build tool.
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
index b2c68e7..fccbc95 100644
--- a/Source/CTest/cmCTestBuildAndTestHandler.cxx
+++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx
@@ -253,9 +253,9 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
       config = "Debug";
     }
     int retVal = cm.GetGlobalGenerator()->Build(
-      this->SourceDir, this->BinaryDir, this->BuildProject, tar, output,
-      this->BuildMakeProgram, config, !this->BuildNoClean, false, false,
-      remainingTime);
+      cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
+      this->BuildProject, tar, output, this->BuildMakeProgram, config,
+      !this->BuildNoClean, false, false, remainingTime);
     out << output;
     // if the build failed then return
     if (retVal) {
diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index 26e0db9..a9b7adf 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -818,7 +818,8 @@ int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
   // actually do the try compile now that everything is setup
   int res = this->Makefile->TryCompile(
     sourceDirectory, this->BinaryDirectory, projectName, targetName,
-    this->SrcFileSignature, &cmakeFlags, output);
+    this->SrcFileSignature, cmake::NO_BUILD_PARALLEL_LEVEL, &cmakeFlags,
+    output);
   if (erroroc) {
     cmSystemTools::SetErrorOccured();
   }
diff --git a/Source/cmGlobalBorlandMakefileGenerator.cxx b/Source/cmGlobalBorlandMakefileGenerator.cxx
index d2372a7..2389103 100644
--- a/Source/cmGlobalBorlandMakefileGenerator.cxx
+++ b/Source/cmGlobalBorlandMakefileGenerator.cxx
@@ -51,3 +51,32 @@ void cmGlobalBorlandMakefileGenerator::GetDocumentation(
   entry.Name = cmGlobalBorlandMakefileGenerator::GetActualName();
   entry.Brief = "Generates Borland makefiles.";
 }
+
+void cmGlobalBorlandMakefileGenerator::GenerateBuildCommand(
+  std::vector<std::string>& makeCommand, const std::string& makeProgram,
+  const std::string& projectName, const std::string& projectDir,
+  const std::string& targetName, const std::string& config, bool fast,
+  int /*jobs*/, bool verbose, std::vector<std::string> const& makeOptions)
+{
+  this->cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
+    makeCommand, makeProgram, projectName, projectDir, targetName, config,
+    fast, cmake::NO_BUILD_PARALLEL_LEVEL, verbose, makeOptions);
+}
+
+void cmGlobalBorlandMakefileGenerator::PrintBuildCommandAdvice(
+  std::ostream& os, int jobs) const
+{
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    // Borland's make does not support parallel builds
+    // see http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Make
+
+    /* clang-format off */
+    os <<
+      "Warning: Borland's make does not support parallel builds. "
+      "Ignoring parallel build command line option.\n";
+    /* clang-format on */
+  }
+
+  this->cmGlobalUnixMakefileGenerator3::PrintBuildCommandAdvice(
+    os, cmake::NO_BUILD_PARALLEL_LEVEL);
+}
diff --git a/Source/cmGlobalBorlandMakefileGenerator.h b/Source/cmGlobalBorlandMakefileGenerator.h
index 5578d76..85fee74 100644
--- a/Source/cmGlobalBorlandMakefileGenerator.h
+++ b/Source/cmGlobalBorlandMakefileGenerator.h
@@ -5,6 +5,8 @@
 
 #include "cmGlobalNMakeMakefileGenerator.h"
 
+#include <iosfwd>
+
 /** \class cmGlobalBorlandMakefileGenerator
  * \brief Write a Borland makefiles.
  *
@@ -21,7 +23,7 @@ public:
   }
 
   ///! Get the name for the generator.
-  virtual std::string GetName() const
+  std::string GetName() const override
   {
     return cmGlobalBorlandMakefileGenerator::GetActualName();
   }
@@ -31,17 +33,27 @@ public:
   static void GetDocumentation(cmDocumentationEntry& entry);
 
   ///! Create a local generator appropriate to this Global Generator
-  virtual cmLocalGenerator* CreateLocalGenerator(cmMakefile* mf);
+  cmLocalGenerator* CreateLocalGenerator(cmMakefile* mf) override;
 
   /**
    * Try to determine system information such as shared library
    * extension, pthreads, byte order etc.
    */
-  virtual void EnableLanguage(std::vector<std::string> const& languages,
-                              cmMakefile*, bool optional);
+  void EnableLanguage(std::vector<std::string> const& languages, cmMakefile*,
+                      bool optional) override;
+
+  bool AllowNotParallel() const override { return false; }
+  bool AllowDeleteOnError() const override { return false; }
+
+protected:
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
+                              std::vector<std::string>()) override;
 
-  virtual bool AllowNotParallel() const { return false; }
-  virtual bool AllowDeleteOnError() const { return false; }
+  void PrintBuildCommandAdvice(std::ostream& os, int jobs) const override;
 };
 
 #endif
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index cf277d5..8a89f36 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1741,7 +1741,7 @@ void cmGlobalGenerator::CheckTargetProperties()
   }
 }
 
-int cmGlobalGenerator::TryCompile(const std::string& srcdir,
+int cmGlobalGenerator::TryCompile(int jobs, const std::string& srcdir,
                                   const std::string& bindir,
                                   const std::string& projectName,
                                   const std::string& target, bool fast,
@@ -1782,7 +1782,7 @@ int cmGlobalGenerator::TryCompile(const std::string& srcdir,
   }
   std::string config =
     mf->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");
-  return this->Build(srcdir, bindir, projectName, newTarget, output, "",
+  return this->Build(jobs, srcdir, bindir, projectName, newTarget, output, "",
                      config, false, fast, false, this->TryCompileTimeout);
 }
 
@@ -1790,13 +1790,21 @@ void cmGlobalGenerator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& /*unused*/,
   const std::string& /*unused*/, const std::string& /*unused*/,
   const std::string& /*unused*/, const std::string& /*unused*/,
-  bool /*unused*/, bool /*unused*/, std::vector<std::string> const& /*unused*/)
+  bool /*unused*/, int /*unused*/, bool /*unused*/,
+  std::vector<std::string> const& /*unused*/)
 {
   makeCommand.push_back(
     "cmGlobalGenerator::GenerateBuildCommand not implemented");
 }
 
-int cmGlobalGenerator::Build(const std::string& /*unused*/,
+void cmGlobalGenerator::PrintBuildCommandAdvice(std::ostream& /*os*/,
+                                                int /*jobs*/) const
+{
+  // Subclasses override this method if they e.g want to give a warning that
+  // they do not support certain build command line options
+}
+
+int cmGlobalGenerator::Build(int jobs, const std::string& /*unused*/,
                              const std::string& bindir,
                              const std::string& projectName,
                              const std::string& target, std::string& output,
@@ -1832,7 +1840,8 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
 
   std::vector<std::string> makeCommand;
   this->GenerateBuildCommand(makeCommand, makeCommandCSTR, projectName, bindir,
-                             target, config, fast, verbose, nativeOptions);
+                             target, config, fast, jobs, verbose,
+                             nativeOptions);
 
   // Workaround to convince VCExpress.exe to produce output.
   if (outputflag == cmSystemTools::OUTPUT_PASSTHROUGH &&
@@ -1846,7 +1855,7 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
   if (clean) {
     std::vector<std::string> cleanCommand;
     this->GenerateBuildCommand(cleanCommand, makeCommandCSTR, projectName,
-                               bindir, "clean", config, fast, verbose);
+                               bindir, "clean", config, fast, jobs, verbose);
     output += "\nRun Clean Command:";
     output += cmSystemTools::PrintSingleCommand(cleanCommand);
     output += "\n";
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 34ed5b0..62c5441 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -147,9 +147,10 @@ public:
    * Try running cmake and building a file. This is used for dynamically
    * loaded commands, not as part of the usual build process.
    */
-  int TryCompile(const std::string& srcdir, const std::string& bindir,
-                 const std::string& projectName, const std::string& targetName,
-                 bool fast, std::string& output, cmMakefile* mf);
+  int TryCompile(int jobs, const std::string& srcdir,
+                 const std::string& bindir, const std::string& projectName,
+                 const std::string& targetName, bool fast, std::string& output,
+                 cmMakefile* mf);
 
   /**
    * Build a file given the following information. This is a more direct call
@@ -157,7 +158,7 @@ public:
    * empty then all is assumed. clean indicates if a "make clean" should be
    * done first.
    */
-  int Build(const std::string& srcdir, const std::string& bindir,
+  int Build(int jobs, const std::string& srcdir, const std::string& bindir,
             const std::string& projectName, const std::string& targetName,
             std::string& output, const std::string& makeProgram,
             const std::string& config, bool clean, bool fast, bool verbose,
@@ -176,9 +177,11 @@ public:
     std::vector<std::string>& makeCommand, const std::string& makeProgram,
     const std::string& projectName, const std::string& projectDir,
     const std::string& targetName, const std::string& config, bool fast,
-    bool verbose,
+    int jobs, bool verbose,
     std::vector<std::string> const& makeOptions = std::vector<std::string>());
 
+  virtual void PrintBuildCommandAdvice(std::ostream& os, int jobs) const;
+
   /** Generate a "cmake --build" call for a given target and config.  */
   std::string GenerateCMakeBuildCommand(const std::string& target,
                                         const std::string& config,
diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx
index 946ed80..f4ecff2 100644
--- a/Source/cmGlobalGhsMultiGenerator.cxx
+++ b/Source/cmGlobalGhsMultiGenerator.cxx
@@ -273,11 +273,18 @@ void cmGlobalGhsMultiGenerator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& /*projectName*/, const std::string& /*projectDir*/,
   const std::string& targetName, const std::string& /*config*/, bool /*fast*/,
-  bool /*verbose*/, std::vector<std::string> const& makeOptions)
+  int jobs, bool /*verbose*/, std::vector<std::string> const& makeOptions)
 {
   makeCommand.push_back(
     this->SelectMakeProgram(makeProgram, this->GetGhsBuildCommand()));
 
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    makeCommand.push_back("-parallel");
+    if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
+      makeCommand.push_back(std::to_string(jobs));
+    }
+  }
+
   makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                      makeOptions.end());
   if (!targetName.empty()) {
diff --git a/Source/cmGlobalGhsMultiGenerator.h b/Source/cmGlobalGhsMultiGenerator.h
index 7d4b2ba..c5388ad 100644
--- a/Source/cmGlobalGhsMultiGenerator.h
+++ b/Source/cmGlobalGhsMultiGenerator.h
@@ -89,7 +89,7 @@ protected:
     std::vector<std::string>& makeCommand, const std::string& makeProgram,
     const std::string& projectName, const std::string& projectDir,
     const std::string& targetName, const std::string& config, bool fast,
-    bool verbose,
+    int jobs, bool verbose,
     std::vector<std::string> const& makeOptions = std::vector<std::string>());
 
 private:
diff --git a/Source/cmGlobalJOMMakefileGenerator.cxx b/Source/cmGlobalJOMMakefileGenerator.cxx
index 18c45e0..0f41ea1 100644
--- a/Source/cmGlobalJOMMakefileGenerator.cxx
+++ b/Source/cmGlobalJOMMakefileGenerator.cxx
@@ -52,3 +52,29 @@ void cmGlobalJOMMakefileGenerator::PrintCompilerAdvice(
   }
   this->cmGlobalUnixMakefileGenerator3::PrintCompilerAdvice(os, lang, envVar);
 }
+
+void cmGlobalJOMMakefileGenerator::GenerateBuildCommand(
+  std::vector<std::string>& makeCommand, const std::string& makeProgram,
+  const std::string& projectName, const std::string& projectDir,
+  const std::string& targetName, const std::string& config, bool fast,
+  int jobs, bool verbose, std::vector<std::string> const& makeOptions)
+{
+  std::vector<std::string> jomMakeOptions;
+
+  // Since we have full control over the invocation of JOM, let us
+  // make it quiet.
+  jomMakeOptions.push_back(this->MakeSilentFlag);
+  jomMakeOptions.insert(jomMakeOptions.end(), makeOptions.begin(),
+                        makeOptions.end());
+
+  // JOM does parallel builds by default, the -j is only needed if a specific
+  // number is given
+  // see https://github.com/qt-labs/jom/blob/v1.1.2/src/jomlib/options.cpp
+  if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
+    jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
+  }
+
+  cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
+    makeCommand, makeProgram, projectName, projectDir, targetName, config,
+    fast, jobs, verbose, jomMakeOptions);
+}
diff --git a/Source/cmGlobalJOMMakefileGenerator.h b/Source/cmGlobalJOMMakefileGenerator.h
index 2e8ee29..65f340c 100644
--- a/Source/cmGlobalJOMMakefileGenerator.h
+++ b/Source/cmGlobalJOMMakefileGenerator.h
@@ -5,6 +5,8 @@
 
 #include "cmGlobalUnixMakefileGenerator3.h"
 
+#include <iosfwd>
+
 /** \class cmGlobalJOMMakefileGenerator
  * \brief Write a JOM makefiles.
  *
@@ -19,7 +21,7 @@ public:
     return new cmGlobalGeneratorSimpleFactory<cmGlobalJOMMakefileGenerator>();
   }
   ///! Get the name for the generator.
-  virtual std::string GetName() const
+  std::string GetName() const override
   {
     return cmGlobalJOMMakefileGenerator::GetActualName();
   }
@@ -34,12 +36,20 @@ public:
    * Try to determine system information such as shared library
    * extension, pthreads, byte order etc.
    */
-  virtual void EnableLanguage(std::vector<std::string> const& languages,
-                              cmMakefile*, bool optional);
+  void EnableLanguage(std::vector<std::string> const& languages, cmMakefile*,
+                      bool optional) override;
+
+protected:
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
+                              std::vector<std::string>()) override;
 
 private:
   void PrintCompilerAdvice(std::ostream& os, std::string const& lang,
-                           const char* envVar) const;
+                           const char* envVar) const override;
 };
 
 #endif
diff --git a/Source/cmGlobalNMakeMakefileGenerator.cxx b/Source/cmGlobalNMakeMakefileGenerator.cxx
index da683fb..eb66bd1 100644
--- a/Source/cmGlobalNMakeMakefileGenerator.cxx
+++ b/Source/cmGlobalNMakeMakefileGenerator.cxx
@@ -52,3 +52,40 @@ void cmGlobalNMakeMakefileGenerator::PrintCompilerAdvice(
   }
   this->cmGlobalUnixMakefileGenerator3::PrintCompilerAdvice(os, lang, envVar);
 }
+
+void cmGlobalNMakeMakefileGenerator::GenerateBuildCommand(
+  std::vector<std::string>& makeCommand, const std::string& makeProgram,
+  const std::string& projectName, const std::string& projectDir,
+  const std::string& targetName, const std::string& config, bool fast,
+  int /*jobs*/, bool verbose, std::vector<std::string> const& makeOptions)
+{
+  std::vector<std::string> nmakeMakeOptions;
+
+  // Since we have full control over the invocation of nmake, let us
+  // make it quiet.
+  nmakeMakeOptions.push_back(this->MakeSilentFlag);
+  nmakeMakeOptions.insert(nmakeMakeOptions.end(), makeOptions.begin(),
+                          makeOptions.end());
+
+  this->cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
+    makeCommand, makeProgram, projectName, projectDir, targetName, config,
+    fast, cmake::NO_BUILD_PARALLEL_LEVEL, verbose, nmakeMakeOptions);
+}
+
+void cmGlobalNMakeMakefileGenerator::PrintBuildCommandAdvice(std::ostream& os,
+                                                             int jobs) const
+{
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    // nmake does not support parallel build level
+    // see https://msdn.microsoft.com/en-us/library/afyyse50.aspx
+
+    /* clang-format off */
+    os <<
+      "Warning: NMake does not support parallel builds. "
+      "Ignoring parallel build command line option.\n";
+    /* clang-format on */
+  }
+
+  this->cmGlobalUnixMakefileGenerator3::PrintBuildCommandAdvice(
+    os, cmake::NO_BUILD_PARALLEL_LEVEL);
+}
diff --git a/Source/cmGlobalNMakeMakefileGenerator.h b/Source/cmGlobalNMakeMakefileGenerator.h
index 05ab904..4b6382e 100644
--- a/Source/cmGlobalNMakeMakefileGenerator.h
+++ b/Source/cmGlobalNMakeMakefileGenerator.h
@@ -5,6 +5,8 @@
 
 #include "cmGlobalUnixMakefileGenerator3.h"
 
+#include <iosfwd>
+
 /** \class cmGlobalNMakeMakefileGenerator
  * \brief Write a NMake makefiles.
  *
@@ -20,7 +22,7 @@ public:
       cmGlobalNMakeMakefileGenerator>();
   }
   ///! Get the name for the generator.
-  virtual std::string GetName() const
+  std::string GetName() const override
   {
     return cmGlobalNMakeMakefileGenerator::GetActualName();
   }
@@ -39,12 +41,22 @@ public:
    * Try to determine system information such as shared library
    * extension, pthreads, byte order etc.
    */
-  virtual void EnableLanguage(std::vector<std::string> const& languages,
-                              cmMakefile*, bool optional);
+  void EnableLanguage(std::vector<std::string> const& languages, cmMakefile*,
+                      bool optional) override;
+
+protected:
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
+                              std::vector<std::string>()) override;
+
+  void PrintBuildCommandAdvice(std::ostream& os, int jobs) const override;
 
 private:
   void PrintCompilerAdvice(std::ostream& os, std::string const& lang,
-                           const char* envVar) const;
+                           const char* envVar) const override;
 };
 
 #endif
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index c19a61c..69bc3be 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -674,7 +674,7 @@ void cmGlobalNinjaGenerator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& /*projectName*/, const std::string& /*projectDir*/,
   const std::string& targetName, const std::string& /*config*/, bool /*fast*/,
-  bool verbose, std::vector<std::string> const& makeOptions)
+  int jobs, bool verbose, std::vector<std::string> const& makeOptions)
 {
   makeCommand.push_back(this->SelectMakeProgram(makeProgram));
 
@@ -682,6 +682,12 @@ void cmGlobalNinjaGenerator::GenerateBuildCommand(
     makeCommand.push_back("-v");
   }
 
+  if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
+      (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
+    makeCommand.push_back("-j");
+    makeCommand.push_back(std::to_string(jobs));
+  }
+
   makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                      makeOptions.end());
   if (!targetName.empty()) {
diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h
index bfff3d9..17b9a7d 100644
--- a/Source/cmGlobalNinjaGenerator.h
+++ b/Source/cmGlobalNinjaGenerator.h
@@ -202,13 +202,11 @@ public:
   void EnableLanguage(std::vector<std::string> const& languages,
                       cmMakefile* mf, bool optional) override;
 
-  void GenerateBuildCommand(std::vector<std::string>& makeCommand,
-                            const std::string& makeProgram,
-                            const std::string& projectName,
-                            const std::string& projectDir,
-                            const std::string& targetName,
-                            const std::string& config, bool fast, bool verbose,
-                            std::vector<std::string> const& makeOptions =
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
                               std::vector<std::string>()) override;
 
   // Setup target names
diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx
index c07f10f..641b760 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.cxx
+++ b/Source/cmGlobalUnixMakefileGenerator3.cxx
@@ -7,7 +7,6 @@
 #include <sstream>
 #include <utility>
 
-#include "cmAlgorithms.h"
 #include "cmDocumentationEntry.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorTarget.h"
@@ -494,31 +493,33 @@ void cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& /*projectName*/, const std::string& /*projectDir*/,
   const std::string& targetName, const std::string& /*config*/, bool fast,
-  bool /*verbose*/, std::vector<std::string> const& makeOptions)
+  int jobs, bool /*verbose*/, std::vector<std::string> const& makeOptions)
 {
+  cmMakefile* mf;
+  if (!this->Makefiles.empty()) {
+    mf = this->Makefiles[0];
+  } else {
+    cmStateSnapshot snapshot = this->CMakeInstance->GetCurrentSnapshot();
+    snapshot.GetDirectory().SetCurrentSource(
+      this->CMakeInstance->GetHomeDirectory());
+    snapshot.GetDirectory().SetCurrentBinary(
+      this->CMakeInstance->GetHomeOutputDirectory());
+    snapshot.SetDefaultDefinitions();
+    mf = new cmMakefile(this, snapshot);
+  }
+
   makeCommand.push_back(this->SelectMakeProgram(makeProgram));
 
-  // Since we have full control over the invocation of nmake, let us
-  // make it quiet.
-  if (cmHasLiteralPrefix(this->GetName(), "NMake Makefiles")) {
-    makeCommand.push_back("/NOLOGO");
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    makeCommand.push_back("-j");
+    if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
+      makeCommand.push_back(std::to_string(jobs));
+    }
   }
+
   makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                      makeOptions.end());
   if (!targetName.empty()) {
-    cmMakefile* mf;
-    if (!this->Makefiles.empty()) {
-      mf = this->Makefiles[0];
-    } else {
-      cmStateSnapshot snapshot = this->CMakeInstance->GetCurrentSnapshot();
-      snapshot.GetDirectory().SetCurrentSource(
-        this->CMakeInstance->GetHomeDirectory());
-      snapshot.GetDirectory().SetCurrentBinary(
-        this->CMakeInstance->GetHomeOutputDirectory());
-      snapshot.SetDefaultDefinitions();
-      mf = new cmMakefile(this, snapshot);
-    }
-
     std::string tname = targetName;
     if (fast) {
       tname += "/fast";
@@ -528,9 +529,9 @@ void cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
       conv.ConvertToRelativePath(mf->GetState()->GetBinaryDirectory(), tname);
     cmSystemTools::ConvertToOutputSlashes(tname);
     makeCommand.push_back(std::move(tname));
-    if (this->Makefiles.empty()) {
-      delete mf;
-    }
+  }
+  if (this->Makefiles.empty()) {
+    delete mf;
   }
 }
 
diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h
index f9ce88c..097678f 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.h
+++ b/Source/cmGlobalUnixMakefileGenerator3.h
@@ -127,13 +127,11 @@ public:
   std::string GetEmptyRuleHackDepends() { return this->EmptyRuleHackDepends; }
 
   // change the build command for speed
-  void GenerateBuildCommand(std::vector<std::string>& makeCommand,
-                            const std::string& makeProgram,
-                            const std::string& projectName,
-                            const std::string& projectDir,
-                            const std::string& targetName,
-                            const std::string& config, bool fast, bool verbose,
-                            std::vector<std::string> const& makeOptions =
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
                               std::vector<std::string>()) override;
 
   /** Record per-target progress information.  */
diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index 205e0d0..51e9ab1 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -764,7 +764,7 @@ void cmGlobalVisualStudio10Generator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& projectName, const std::string& projectDir,
   const std::string& targetName, const std::string& config, bool fast,
-  bool verbose, std::vector<std::string> const& makeOptions)
+  int jobs, bool verbose, std::vector<std::string> const& makeOptions)
 {
   // Select the caller- or user-preferred make program, else MSBuild.
   std::string makeProgramSelected =
@@ -805,7 +805,7 @@ void cmGlobalVisualStudio10Generator::GenerateBuildCommand(
     // Use devenv to build solutions containing Intel Fortran projects.
     cmGlobalVisualStudio7Generator::GenerateBuildCommand(
       makeCommand, makeProgram, projectName, projectDir, targetName, config,
-      fast, verbose, makeOptions);
+      fast, jobs, verbose, makeOptions);
     return;
   }
 
@@ -813,6 +813,7 @@ void cmGlobalVisualStudio10Generator::GenerateBuildCommand(
 
   std::string realTarget = targetName;
   // msbuild.exe CxxOnly.sln /t:Build /p:Configuration=Debug /target:ALL_BUILD
+  //                         /m
   if (realTarget.empty()) {
     realTarget = "ALL_BUILD";
   }
@@ -841,6 +842,17 @@ void cmGlobalVisualStudio10Generator::GenerateBuildCommand(
   makeCommand.push_back(configArg);
   makeCommand.push_back(std::string("/p:VisualStudioVersion=") +
                         this->GetIDEVersion());
+
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
+      makeCommand.push_back("/m");
+    } else {
+      makeCommand.push_back(std::string("/m:") + std::to_string(jobs));
+    }
+    // Having msbuild.exe and cl.exe using multiple jobs is discouraged
+    makeCommand.push_back("/p:CL_MPCount=1");
+  }
+
   makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                      makeOptions.end());
 }
diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h
index f2501c2..098c8d4 100644
--- a/Source/cmGlobalVisualStudio10Generator.h
+++ b/Source/cmGlobalVisualStudio10Generator.h
@@ -24,13 +24,11 @@ public:
   bool SetGeneratorPlatform(std::string const& p, cmMakefile* mf) override;
   bool SetGeneratorToolset(std::string const& ts, cmMakefile* mf) override;
 
-  void GenerateBuildCommand(std::vector<std::string>& makeCommand,
-                            const std::string& makeProgram,
-                            const std::string& projectName,
-                            const std::string& projectDir,
-                            const std::string& targetName,
-                            const std::string& config, bool fast, bool verbose,
-                            std::vector<std::string> const& makeOptions =
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
                               std::vector<std::string>()) override;
 
   ///! create the correct local generator
diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx
index 7ff007f..158f484 100644
--- a/Source/cmGlobalVisualStudio7Generator.cxx
+++ b/Source/cmGlobalVisualStudio7Generator.cxx
@@ -199,7 +199,7 @@ void cmGlobalVisualStudio7Generator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& projectName, const std::string& /*projectDir*/,
   const std::string& targetName, const std::string& config, bool /*fast*/,
-  bool /*verbose*/, std::vector<std::string> const& makeOptions)
+  int /*jobs*/, bool /*verbose*/, std::vector<std::string> const& makeOptions)
 {
   // Select the caller- or user-preferred make program, else devenv.
   std::string makeProgramSelected =
diff --git a/Source/cmGlobalVisualStudio7Generator.h b/Source/cmGlobalVisualStudio7Generator.h
index 8d1bdc0..77d4a96 100644
--- a/Source/cmGlobalVisualStudio7Generator.h
+++ b/Source/cmGlobalVisualStudio7Generator.h
@@ -55,13 +55,11 @@ public:
    * Try running cmake and building a file. This is used for dynamically
    * loaded commands, not as part of the usual build process.
    */
-  void GenerateBuildCommand(std::vector<std::string>& makeCommand,
-                            const std::string& makeProgram,
-                            const std::string& projectName,
-                            const std::string& projectDir,
-                            const std::string& targetName,
-                            const std::string& config, bool fast, bool verbose,
-                            std::vector<std::string> const& makeOptions =
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
                               std::vector<std::string>()) override;
 
   /**
diff --git a/Source/cmGlobalWatcomWMakeGenerator.cxx b/Source/cmGlobalWatcomWMakeGenerator.cxx
index 94cdb38..558ef15 100644
--- a/Source/cmGlobalWatcomWMakeGenerator.cxx
+++ b/Source/cmGlobalWatcomWMakeGenerator.cxx
@@ -7,6 +7,8 @@
 #include "cmState.h"
 #include "cmake.h"
 
+#include <ostream>
+
 cmGlobalWatcomWMakeGenerator::cmGlobalWatcomWMakeGenerator(cmake* cm)
   : cmGlobalUnixMakefileGenerator3(cm)
 {
@@ -47,3 +49,31 @@ void cmGlobalWatcomWMakeGenerator::GetDocumentation(
   entry.Name = cmGlobalWatcomWMakeGenerator::GetActualName();
   entry.Brief = "Generates Watcom WMake makefiles.";
 }
+
+void cmGlobalWatcomWMakeGenerator::GenerateBuildCommand(
+  std::vector<std::string>& makeCommand, const std::string& makeProgram,
+  const std::string& projectName, const std::string& projectDir,
+  const std::string& targetName, const std::string& config, bool fast,
+  int /*jobs*/, bool verbose, std::vector<std::string> const& makeOptions)
+{
+  this->cmGlobalUnixMakefileGenerator3::GenerateBuildCommand(
+    makeCommand, makeProgram, projectName, projectDir, targetName, config,
+    fast, cmake::NO_BUILD_PARALLEL_LEVEL, verbose, makeOptions);
+}
+
+void cmGlobalWatcomWMakeGenerator::PrintBuildCommandAdvice(std::ostream& os,
+                                                           int jobs) const
+{
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    // wmake does not support parallel build level
+
+    /* clang-format off */
+    os <<
+      "Warning: Watcom's WMake does not support parallel builds. "
+      "Ignoring parallel build command line option.\n";
+    /* clang-format on */
+  }
+
+  this->cmGlobalUnixMakefileGenerator3::PrintBuildCommandAdvice(
+    os, cmake::NO_BUILD_PARALLEL_LEVEL);
+}
diff --git a/Source/cmGlobalWatcomWMakeGenerator.h b/Source/cmGlobalWatcomWMakeGenerator.h
index e8b3a73..1729bf1 100644
--- a/Source/cmGlobalWatcomWMakeGenerator.h
+++ b/Source/cmGlobalWatcomWMakeGenerator.h
@@ -8,6 +8,7 @@
 #include "cmGlobalGeneratorFactory.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
 
+#include <iosfwd>
 #include <string>
 #include <vector>
 
@@ -47,6 +48,16 @@ public:
 
   bool AllowNotParallel() const override { return false; }
   bool AllowDeleteOnError() const override { return false; }
+
+protected:
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
+                              std::vector<std::string>()) override;
+
+  void PrintBuildCommandAdvice(std::ostream& os, int jobs) const override;
 };
 
 #endif
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 4481bdc..f69f23e 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -325,7 +325,7 @@ void cmGlobalXCodeGenerator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& projectName, const std::string& /*projectDir*/,
   const std::string& targetName, const std::string& config, bool /*fast*/,
-  bool /*verbose*/, std::vector<std::string> const& makeOptions)
+  int jobs, bool /*verbose*/, std::vector<std::string> const& makeOptions)
 {
   // now build the test
   makeCommand.push_back(
@@ -356,6 +356,14 @@ void cmGlobalXCodeGenerator::GenerateBuildCommand(
   }
   makeCommand.push_back("-configuration");
   makeCommand.push_back(!config.empty() ? config : "Debug");
+
+  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
+    makeCommand.push_back("-jobs");
+    if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
+      makeCommand.push_back(std::to_string(jobs));
+    }
+  }
+
   makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                      makeOptions.end());
 }
diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h
index 7c51177..f7f4428 100644
--- a/Source/cmGlobalXCodeGenerator.h
+++ b/Source/cmGlobalXCodeGenerator.h
@@ -66,13 +66,11 @@ public:
    * Try running cmake and building a file. This is used for dynalically
    * loaded commands, not as part of the usual build process.
    */
-  void GenerateBuildCommand(std::vector<std::string>& makeCommand,
-                            const std::string& makeProgram,
-                            const std::string& projectName,
-                            const std::string& projectDir,
-                            const std::string& targetName,
-                            const std::string& config, bool fast, bool verbose,
-                            std::vector<std::string> const& makeOptions =
+  void GenerateBuildCommand(
+    std::vector<std::string>& makeCommand, const std::string& makeProgram,
+    const std::string& projectName, const std::string& projectDir,
+    const std::string& targetName, const std::string& config, bool fast,
+    int jobs, bool verbose, std::vector<std::string> const& makeOptions =
                               std::vector<std::string>()) override;
 
   /** Append the subdirectory for the given configuration.  */
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 33e76b2..f41c2f0 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -3269,7 +3269,7 @@ void cmMakefile::EnableLanguage(std::vector<std::string> const& lang,
 int cmMakefile::TryCompile(const std::string& srcdir,
                            const std::string& bindir,
                            const std::string& projectName,
-                           const std::string& targetName, bool fast,
+                           const std::string& targetName, bool fast, int jobs,
                            const std::vector<std::string>* cmakeArgs,
                            std::string& output)
 {
@@ -3381,7 +3381,7 @@ int cmMakefile::TryCompile(const std::string& srcdir,
 
   // finally call the generator to actually build the resulting project
   int ret = this->GetGlobalGenerator()->TryCompile(
-    srcdir, bindir, projectName, targetName, fast, output, this);
+    jobs, srcdir, bindir, projectName, targetName, fast, output, this);
 
   this->IsSourceFileTryCompile = false;
   return ret;
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index 9f32c4f..ac7baae 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -102,7 +102,8 @@ public:
    */
   int TryCompile(const std::string& srcdir, const std::string& bindir,
                  const std::string& projectName, const std::string& targetName,
-                 bool fast, const std::vector<std::string>* cmakeArgs,
+                 bool fast, int jobs,
+                 const std::vector<std::string>* cmakeArgs,
                  std::string& output);
 
   bool GetIsSourceFileTryCompile() const;
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 5bae4e7..801d52d 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -2398,7 +2398,7 @@ cmMessenger* cmake::GetMessenger() const
   return this->Messenger;
 }
 
-int cmake::Build(const std::string& dir, const std::string& target,
+int cmake::Build(int jobs, const std::string& dir, const std::string& target,
                  const std::string& config,
                  const std::vector<std::string>& nativeOptions, bool clean)
 {
@@ -2508,7 +2508,9 @@ int cmake::Build(const std::string& dir, const std::string& target,
   }
 #endif
 
-  return gen->Build("", dir, projName, target, output, "", config, clean,
+  gen->PrintBuildCommandAdvice(std::cerr, jobs);
+
+  return gen->Build(jobs, "", dir, projName, target, output, "", config, clean,
                     false, verbose, cmDuration::zero(),
                     cmSystemTools::OUTPUT_PASSTHROUGH, nativeOptions);
 }
diff --git a/Source/cmake.h b/Source/cmake.h
index 63dbe9f..6c0d77c 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -119,6 +119,9 @@ public:
 
   typedef std::map<std::string, cmInstalledFile> InstalledFilesMap;
 
+  static const int NO_BUILD_PARALLEL_LEVEL = -1;
+  static const int DEFAULT_BUILD_PARALLEL_LEVEL = 0;
+
   /// Default constructor
   cmake(Role role);
   /// Destructor
@@ -430,7 +433,7 @@ public:
     cmListFileBacktrace const& backtrace = cmListFileBacktrace()) const;
 
   ///! run the --build option
-  int Build(const std::string& dir, const std::string& target,
+  int Build(int jobs, const std::string& dir, const std::string& target,
             const std::string& config,
             const std::vector<std::string>& nativeOptions, bool clean);
 
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index b185a1b..9c9f65c 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -22,6 +22,8 @@
 #if defined(_WIN32) && defined(CMAKE_BUILD_WITH_CMAKE)
 #include "cmsys/ConsoleBuf.hxx"
 #endif
+
+#include <ctype.h>
 #include <iostream>
 #include <string.h>
 #include <string>
@@ -49,6 +51,12 @@ static const char* cmDocumentationUsageNote[][2] = {
 
 #define CMAKE_BUILD_OPTIONS                                                   \
   "  <dir>          = Project binary directory to be built.\n"                \
+  "  -j [<jobs>] --parallel [<jobs>] = Build in parallel using\n"             \
+  "                   the given number of jobs. If <jobs> is omitted\n"       \
+  "                   the native build tool's default number is used.\n"      \
+  "                   The CMAKE_BUILD_PARALLEL_LEVEL environment variable\n"  \
+  "                   specifies a default parallel level when this option\n"  \
+  "                   is not given.\n"                                        \
   "  --target <tgt> = Build <tgt> instead of default targets.\n"              \
   "                   May only be specified once.\n"                          \
   "  --config <cfg> = For multi-configuration tools, choose <cfg>.\n"         \
@@ -338,6 +346,7 @@ static int do_build(int ac, char const* const* av)
   std::cerr << "This cmake does not support --build\n";
   return -1;
 #else
+  int jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
   std::string target;
   std::string config = "Debug";
   std::string dir;
@@ -348,6 +357,7 @@ static int do_build(int ac, char const* const* av)
   enum Doing
   {
     DoingNone,
+    DoingJobs,
     DoingDir,
     DoingTarget,
     DoingConfig,
@@ -357,6 +367,13 @@ static int do_build(int ac, char const* const* av)
   for (int i = 2; i < ac; ++i) {
     if (doing == DoingNative) {
       nativeOptions.push_back(av[i]);
+    } else if ((strcmp(av[i], "-j") == 0) ||
+               (strcmp(av[i], "--parallel") == 0)) {
+      jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
+      /* does the next argument start with a number? */
+      if ((i + 1 < ac) && (isdigit(*av[i + 1]))) {
+        doing = DoingJobs;
+      }
     } else if (strcmp(av[i], "--target") == 0) {
       if (!hasTarget) {
         doing = DoingTarget;
@@ -377,6 +394,18 @@ static int do_build(int ac, char const* const* av)
       doing = DoingNative;
     } else {
       switch (doing) {
+        case DoingJobs: {
+          unsigned long numJobs = 0;
+          if (cmSystemTools::StringToULong(av[i], &numJobs)) {
+            jobs = int(numJobs);
+            doing = DoingNone;
+          } else {
+            std::cerr << "'" << av[i - 1] << "' invalid number '" << av[i]
+                      << "' given.\n\n";
+            dir.clear();
+            break;
+          }
+        } break;
         case DoingDir:
           dir = cmSystemTools::CollapseFullPath(av[i]);
           doing = DoingNone;
@@ -396,6 +425,25 @@ static int do_build(int ac, char const* const* av)
       }
     }
   }
+
+  if (jobs == cmake::NO_BUILD_PARALLEL_LEVEL) {
+    std::string parallel;
+    if (cmSystemTools::GetEnv("CMAKE_BUILD_PARALLEL_LEVEL", parallel)) {
+      if (parallel.empty()) {
+        jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
+      } else {
+        unsigned long numJobs = 0;
+        if (cmSystemTools::StringToULong(parallel.c_str(), &numJobs)) {
+          jobs = int(numJobs);
+        } else {
+          std::cerr << "'CMAKE_BUILD_PARALLEL_LEVEL' environment variable\n"
+                    << "invalid number '" << parallel << "' given.\n\n";
+          dir.clear();
+        }
+      }
+    }
+  }
+
   if (dir.empty()) {
     /* clang-format off */
     std::cerr <<
@@ -410,7 +458,7 @@ static int do_build(int ac, char const* const* av)
   cmake cm(cmake::RoleInternal);
   cmSystemTools::SetMessageCallback(cmakemainMessageCallback, &cm);
   cm.SetProgressCallback(cmakemainProgressCallback, &cm);
-  return cm.Build(dir, target, config, nativeOptions, clean);
+  return cm.Build(jobs, dir, target, config, nativeOptions, clean);
 #endif
 }
 
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-result.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt
new file mode 100644
index 0000000..e73d760
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt
@@ -0,0 +1,3 @@
+^'--parallel' invalid number '12ab' given\.
++
+Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing--target-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing--target-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing--target-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-good-number-trailing-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing--target-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing--target-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing--target-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-number-trailing-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-result.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt
new file mode 100644
index 0000000..c810087
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt
@@ -0,0 +1,3 @@
+^'-j' invalid number '12ab' given\.
++
+Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing--target-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing--target-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing--target-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-good-number-trailing-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing--target-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing--target-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing--target-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing-stderr.txt b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing-stderr.txt
new file mode 100644
index 0000000..3c2c808
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-number-trailing-stderr.txt
@@ -0,0 +1 @@
+(^$|^Warning: .* does not support parallel builds\. Ignoring parallel build command line option\.)
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index d8dbeec..3bb2a89 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -59,6 +59,29 @@ function(run_BuildDir)
     ${CMAKE_COMMAND} --build BuildDir-build --target CustomTarget)
   run_cmake_command(BuildDir--build-multiple-targets ${CMAKE_COMMAND} -E chdir ..
     ${CMAKE_COMMAND} --build BuildDir-build --target CustomTarget2 --target CustomTarget3)
+  run_cmake_command(BuildDir--build-jobs-bad-number ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build -j 12ab)
+  run_cmake_command(BuildDir--build-jobs-good-number ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build -j 2)
+  run_cmake_command(BuildDir--build-jobs-good-number-trailing--target ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build -j 2 --target CustomTarget)
+  run_cmake_command(BuildDir--build--parallel-bad-number ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build --parallel 12ab)
+  run_cmake_command(BuildDir--build--parallel-good-number ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build --parallel 2)
+  run_cmake_command(BuildDir--build--parallel-good-number-trailing--target ${CMAKE_COMMAND} -E chdir ..
+    ${CMAKE_COMMAND} --build BuildDir-build --parallel 2 --target CustomTarget)
+  # No default jobs for Xcode and FreeBSD build command
+  if(NOT RunCMake_GENERATOR MATCHES "Xcode" AND NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+    run_cmake_command(BuildDir--build-jobs-no-number ${CMAKE_COMMAND} -E chdir ..
+      ${CMAKE_COMMAND} --build BuildDir-build -j)
+    run_cmake_command(BuildDir--build-jobs-no-number-trailing--target ${CMAKE_COMMAND} -E chdir ..
+      ${CMAKE_COMMAND} --build BuildDir-build -j --target CustomTarget)
+    run_cmake_command(BuildDir--build--parallel-no-number ${CMAKE_COMMAND} -E chdir ..
+      ${CMAKE_COMMAND} --build BuildDir-build --parallel)
+    run_cmake_command(BuildDir--build--parallel-no-number-trailing--target ${CMAKE_COMMAND} -E chdir ..
+      ${CMAKE_COMMAND} --build BuildDir-build --parallel --target CustomTarget)
+  endif()
 endfunction()
 run_BuildDir()
 
-- 
cgit v0.12