From 3825d6ec981d20ee55b12aed8cc8f94ab1afc88f Mon Sep 17 00:00:00 2001
From: Chris Mahoney <chris.mahoney@kitware.com>
Date: Fri, 21 Jul 2023 11:24:14 -0400
Subject: add_custom_{command,target}: Teach JOB_SERVER_AWARE about
 WORKING_DIRECTORY

Issue: #16273
---
 Source/cmAddCustomCommandCommand.cxx               | 10 +------
 Source/cmAddCustomTargetCommand.cxx                | 20 ++++----------
 Source/cmCustomCommand.cxx                         | 10 +++++++
 Source/cmCustomCommand.h                           |  6 ++++
 Source/cmLocalUnixMakefileGenerator3.cxx           |  9 ++++++
 Tests/RunCMake/Make/Foo/CMakeLists.txt             |  4 +++
 .../Make/GNUMakeJobServerAware-check.cmake         | 32 ++++++++++++++++------
 Tests/RunCMake/Make/GNUMakeJobServerAware.cmake    | 27 +++++++++++++++---
 8 files changed, 82 insertions(+), 36 deletions(-)
 create mode 100644 Tests/RunCMake/Make/Foo/CMakeLists.txt

diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx
index 044b5df..b1589ff 100644
--- a/Source/cmAddCustomCommandCommand.cxx
+++ b/Source/cmAddCustomCommandCommand.cxx
@@ -334,15 +334,6 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  // If using a GNU Make generator and `JOB_SERVER_AWARE` is set then
-  // prefix all commands with '+'.
-  if (cmIsOn(job_server_aware) &&
-      mf.GetGlobalGenerator()->IsGNUMakeJobServerAware()) {
-    for (auto& commandLine : commandLines) {
-      commandLine.insert(commandLine.begin(), "+");
-    }
-  }
-
   // Choose which mode of the command to use.
   auto cc = cm::make_unique<cmCustomCommand>();
   cc->SetByproducts(byproducts);
@@ -353,6 +344,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
   cc->SetUsesTerminal(uses_terminal);
   cc->SetDepfile(depfile);
   cc->SetJobPool(job_pool);
+  cc->SetJobserverAware(cmIsOn(job_server_aware));
   cc->SetCommandExpandLists(command_expand_lists);
   cc->SetDependsExplicitOnly(depends_explicit_only);
   if (source.empty() && output.empty()) {
diff --git a/Source/cmAddCustomTargetCommand.cxx b/Source/cmAddCustomTargetCommand.cxx
index 6339062..711eaa5 100644
--- a/Source/cmAddCustomTargetCommand.cxx
+++ b/Source/cmAddCustomTargetCommand.cxx
@@ -55,7 +55,7 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
   const char* comment = nullptr;
   std::vector<std::string> sources;
   std::string job_pool;
-  std::string JOB_SERVER_AWARE;
+  std::string job_server_aware;
 
   // Keep track of parser state.
   enum tdoing
@@ -67,7 +67,7 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
     doing_comment,
     doing_source,
     doing_job_pool,
-    doing_JOB_SERVER_AWARE,
+    doing_job_server_aware,
     doing_nothing
   };
   tdoing doing = doing_command;
@@ -106,7 +106,7 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
     } else if (copy == "JOB_POOL") {
       doing = doing_job_pool;
     } else if (copy == "JOB_SERVER_AWARE") {
-      doing = doing_JOB_SERVER_AWARE;
+      doing = doing_job_server_aware;
     } else if (copy == "COMMAND") {
       doing = doing_command;
 
@@ -153,8 +153,8 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
         case doing_job_pool:
           job_pool = copy;
           break;
-        case doing_JOB_SERVER_AWARE:
-          JOB_SERVER_AWARE = copy;
+        case doing_job_server_aware:
+          job_server_aware = copy;
           break;
         default:
           status.SetError("Wrong syntax. Unknown type of argument.");
@@ -220,15 +220,6 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  // If using a GNU Make generator and `JOB_SERVER_AWARE` is set then
-  // prefix all commands with '+'.
-  if (cmIsOn(JOB_SERVER_AWARE) &&
-      mf.GetGlobalGenerator()->IsGNUMakeJobServerAware()) {
-    for (auto& commandLine : commandLines) {
-      commandLine.insert(commandLine.begin(), "+");
-    }
-  }
-
   // Add the utility target to the makefile.
   auto cc = cm::make_unique<cmCustomCommand>();
   cc->SetWorkingDirectory(working_directory.c_str());
@@ -240,6 +231,7 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
   cc->SetUsesTerminal(uses_terminal);
   cc->SetCommandExpandLists(command_expand_lists);
   cc->SetJobPool(job_pool);
+  cc->SetJobserverAware(cmIsOn(job_server_aware));
   cmTarget* target =
     mf.AddUtilityCommand(targetName, excludeFromAll, std::move(cc));
 
diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx
index e12cf70..9958e4d 100644
--- a/Source/cmCustomCommand.cxx
+++ b/Source/cmCustomCommand.cxx
@@ -194,6 +194,16 @@ void cmCustomCommand::SetJobPool(const std::string& job_pool)
   this->JobPool = job_pool;
 }
 
+bool cmCustomCommand::GetJobserverAware() const
+{
+  return this->JobserverAware;
+}
+
+void cmCustomCommand::SetJobserverAware(bool b)
+{
+  this->JobserverAware = b;
+}
+
 #define DEFINE_CC_POLICY_ACCESSOR(P)                                          \
   cmPolicies::PolicyStatus cmCustomCommand::Get##P##Status() const            \
   {                                                                           \
diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h
index 1e68dbf..167e601 100644
--- a/Source/cmCustomCommand.h
+++ b/Source/cmCustomCommand.h
@@ -115,6 +115,11 @@ public:
   const std::string& GetJobPool() const;
   void SetJobPool(const std::string& job_pool);
 
+  /** Set/Get whether this custom command should be given access to the
+      jobserver (if possible).  */
+  bool GetJobserverAware() const;
+  void SetJobserverAware(bool b);
+
 #define DECLARE_CC_POLICY_ACCESSOR(P)                                         \
   cmPolicies::PolicyStatus Get##P##Status() const;
   CM_FOR_EACH_CUSTOM_COMMAND_POLICY(DECLARE_CC_POLICY_ACCESSOR)
@@ -139,6 +144,7 @@ private:
   std::string WorkingDirectory;
   std::string Depfile;
   std::string JobPool;
+  bool JobserverAware = false;
   bool HaveComment = false;
   bool EscapeAllowMakeVars = false;
   bool EscapeOldStyle = true;
diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx
index 3c6b303..e26a6ea 100644
--- a/Source/cmLocalUnixMakefileGenerator3.cxx
+++ b/Source/cmLocalUnixMakefileGenerator3.cxx
@@ -1080,6 +1080,15 @@ void cmLocalUnixMakefileGenerator3::AppendCustomCommand(
   // Setup the proper working directory for the commands.
   this->CreateCDCommand(commands1, dir, relative);
 
+  cmGlobalUnixMakefileGenerator3* gg =
+    static_cast<cmGlobalUnixMakefileGenerator3*>(this->GlobalGenerator);
+
+  // Prefix the commands with the jobserver prefix "+"
+  if (ccg.GetCC().GetJobserverAware() && gg->IsGNUMakeJobServerAware()) {
+    std::transform(commands1.begin(), commands1.end(), commands1.begin(),
+                   [](std::string const& cmd) { return cmStrCat("+", cmd); });
+  }
+
   // push back the custom commands
   cm::append(commands, commands1);
 }
diff --git a/Tests/RunCMake/Make/Foo/CMakeLists.txt b/Tests/RunCMake/Make/Foo/CMakeLists.txt
new file mode 100644
index 0000000..baa6634
--- /dev/null
+++ b/Tests/RunCMake/Make/Foo/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.26)
+project(Foo NONE)
+
+add_custom_target(drive ALL COMMAND ${CMAKE_COMMAND} -E true)
diff --git a/Tests/RunCMake/Make/GNUMakeJobServerAware-check.cmake b/Tests/RunCMake/Make/GNUMakeJobServerAware-check.cmake
index 7c5c296..da18f00 100644
--- a/Tests/RunCMake/Make/GNUMakeJobServerAware-check.cmake
+++ b/Tests/RunCMake/Make/GNUMakeJobServerAware-check.cmake
@@ -1,12 +1,26 @@
-# This test verifies that the commands in the generated Makefiles contain the
-# `+` prefix
-function(check_for_plus_prefix target)
-  set(file "${RunCMake_BINARY_DIR}/GNUMakeJobServerAware-build/${target}")
-  file(READ "${file}" build_file)
-  if(NOT "${build_file}" MATCHES [[\+]])
-    message(FATAL_ERROR "The file ${file} does not contain the expected prefix in the custom command.")
+set(BUILD_DIR "${RunCMake_BINARY_DIR}/GNUMakeJobServerAware-build")
+
+function(check target line)
+  # Read the file and split it into a list of lines
+  file(READ ${BUILD_DIR}/${target} contents)
+  STRING(REGEX REPLACE ";" "\\\\;" contents "${contents}")
+  STRING(REGEX REPLACE "\n" ";" contents "${contents}")
+
+  set(found FALSE)
+  foreach(entry ${contents})
+    if("${entry}" MATCHES "${line}")
+      set(found TRUE)
+      break()
+    endif()
+  endforeach()
+
+  if(NOT found)
+    message(FATAL_ERROR "Could not find '${line}' in ${BUILD_DIR}/${target}\n${contents}")
   endif()
 endfunction()
 
-check_for_plus_prefix("CMakeFiles/dummy.dir/build.make")
-check_for_plus_prefix("CMakeFiles/dummy2.dir/build.make")
+check("CMakeFiles/dummy.dir/build.make" [[\+\$\(CMAKE_COMMAND\) -E true]])
+check("CMakeFiles/dummy2.dir/build.make" [[\+\$\(CMAKE_COMMAND\) -E true]])
+
+check("CMakeFiles/dummy3.dir/build.make" [[\+cd (/d )?"?.*"? && \$\(CMAKE_COMMAND\) -E true]])
+check("CMakeFiles/dummy4.dir/build.make" [[\+cd (/d )?"?.*"? && \$\(CMAKE_COMMAND\) -E true]])
diff --git a/Tests/RunCMake/Make/GNUMakeJobServerAware.cmake b/Tests/RunCMake/Make/GNUMakeJobServerAware.cmake
index 951c2d7..d92e842 100644
--- a/Tests/RunCMake/Make/GNUMakeJobServerAware.cmake
+++ b/Tests/RunCMake/Make/GNUMakeJobServerAware.cmake
@@ -1,12 +1,31 @@
+# Test JOB_SERVER_AWARE with custom commands
 add_custom_command(
-  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/custom-command"
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/missing"
   JOB_SERVER_AWARE ON
-  COMMAND $(CMAKE_COMMAND) -E touch "${CMAKE_CURRENT_BINARY_DIR}/custom-command"
+  COMMAND $(CMAKE_COMMAND) -E true
 )
-add_custom_target(dummy ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/custom-command")
+add_custom_target(dummy ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/missing")
 
+# Test JOB_SERVER_AWARE with custom targets
 add_custom_target(
   dummy2 ALL
   JOB_SERVER_AWARE ON
-  COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND $(CMAKE_COMMAND) -E true
+)
+
+# Test JOB_SERVER_AWARE with custom commands with WORKING_DIRECTORY
+add_custom_command(
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/missing2"
+  JOB_SERVER_AWARE ON
+  COMMAND $(CMAKE_COMMAND) -E true
+  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Foo"
+)
+add_custom_target(dummy3 ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/missing2")
+
+# Test JOB_SERVER_AWARE with custom targets with WORKING_DIRECTORY
+add_custom_target(
+  dummy4 ALL
+  JOB_SERVER_AWARE ON
+  COMMAND $(CMAKE_COMMAND) -E true
+  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Foo"
 )
-- 
cgit v0.12