From 8c2cedc6243b281a0814b284abbcd1c45c42b085 Mon Sep 17 00:00:00 2001
From: Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
Date: Wed, 4 May 2016 13:30:19 -0400
Subject: CustomCommandGenerator: Add support for CROSSCOMPILING_EMULATOR

Teach the `add_custom_command` and `add_custom_target' commands to
substitute argv0 with the crosscompiling emulator if it is a target with
the `CROSSCOMPILING_EMULATOR` property set.
---
 Help/command/add_custom_command.rst                |  7 ++--
 Help/command/add_custom_target.rst                 |  7 ++--
 Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst          |  7 ++--
 .../dev/custom-command-CROSSCOMPILING_EMULATOR.rst |  6 ++++
 Source/cmCustomCommandGenerator.cxx                | 34 +++++++++++++++++--
 Source/cmCustomCommandGenerator.h                  |  1 +
 Tests/RunCMake/CMakeLists.txt                      |  8 ++++-
 .../AddCustomCommand-build-check.cmake             |  5 +++
 .../CrosscompilingEmulator/AddCustomCommand.cmake  | 38 ++++++++++++++++++++++
 .../AddCustomTarget-build-check.cmake              |  1 +
 .../CrosscompilingEmulator/AddCustomTarget.cmake   | 30 +++++++++++++++++
 .../CrosscompilingEmulator/RunCMakeTest.cmake      | 15 +++++++++
 .../simple_src_exitsuccess.cxx                     |  4 +++
 Tests/RunCMake/pseudo_emulator_custom_command.c    | 34 +++++++++++++++++++
 14 files changed, 187 insertions(+), 10 deletions(-)
 create mode 100644 Help/release/dev/custom-command-CROSSCOMPILING_EMULATOR.rst
 create mode 100644 Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand-build-check.cmake
 create mode 100644 Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand.cmake
 create mode 100644 Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget-build-check.cmake
 create mode 100644 Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget.cmake
 create mode 100644 Tests/RunCMake/CrosscompilingEmulator/simple_src_exitsuccess.cxx
 create mode 100644 Tests/RunCMake/pseudo_emulator_custom_command.c

diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index 8726b70..d421364 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -76,9 +76,12 @@ The options are:
   The optional ``ARGS`` argument is for backward compatibility and
   will be ignored.
 
-  If ``COMMAND`` specifies an executable target (created by the
+  If ``COMMAND`` specifies an executable target name (created by the
   :command:`add_executable` command) it will automatically be replaced
-  by the location of the executable created at build time.
+  by the location of the executable created at build time. If set, the
+  :prop_tgt:`CROSSCOMPILING_EMULATOR` executable target property will
+  also be prepended to the command to allow the executable to run on
+  the host.
   (Use the ``TARGET_FILE``
   :manual:`generator expression <cmake-generator-expressions(7)>` to
   reference an executable later in the command line.)
diff --git a/Help/command/add_custom_target.rst b/Help/command/add_custom_target.rst
index 82d69db..6980d61 100644
--- a/Help/command/add_custom_target.rst
+++ b/Help/command/add_custom_target.rst
@@ -58,9 +58,12 @@ The options are:
   :command:`file(GENERATE)` command to create it, and then specify
   a ``COMMAND`` to launch it.)
 
-  If ``COMMAND`` specifies an executable target (created by the
+  If ``COMMAND`` specifies an executable target name (created by the
   :command:`add_executable` command) it will automatically be replaced
-  by the location of the executable created at build time.
+  by the location of the executable created at build time. If set, the
+  :prop_tgt:`CROSSCOMPILING_EMULATOR` executable target property will
+  also be prepended to the command to allow the executable to run on
+  the host.
   Additionally a target-level dependency will be added so that the
   executable target will be built before this custom target.
 
diff --git a/Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst b/Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst
index 3ef8e03..d30a2f2 100644
--- a/Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst
+++ b/Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst
@@ -1,6 +1,7 @@
 CROSSCOMPILING_EMULATOR
 -----------------------
 
-Use the given emulator to run executables created when crosscompiling.  This
-command will be added as a prefix to :command:`add_test` test commands for
-built target system executables.
+Use the given emulator to run executables created when crosscompiling.
+This command will be added as a prefix to :command:`add_test`,
+:command:`add_custom_command`, and :command:`add_custom_target` commands
+for built target system executables.
diff --git a/Help/release/dev/custom-command-CROSSCOMPILING_EMULATOR.rst b/Help/release/dev/custom-command-CROSSCOMPILING_EMULATOR.rst
new file mode 100644
index 0000000..390463e
--- /dev/null
+++ b/Help/release/dev/custom-command-CROSSCOMPILING_EMULATOR.rst
@@ -0,0 +1,6 @@
+custom-command-CROSSCOMPILING_EMULATOR
+--------------------------------------
+
+* The :command:`add_custom_command` and :command:`add_custom_target` commands
+  learned how to use the :prop_tgt:`CROSSCOMPILING_EMULATOR` executable
+  target property.
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 5d3a1ce..81c5142 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -39,6 +39,19 @@ unsigned int cmCustomCommandGenerator::GetNumberOfCommands() const
 }
 
 //----------------------------------------------------------------------------
+bool cmCustomCommandGenerator::UseCrossCompilingEmulator(unsigned int c) const
+{
+  std::string const& argv0 = this->CC.GetCommandLines()[c][0];
+  cmGeneratorTarget* target =
+      this->LG->FindGeneratorTargetToUse(argv0);
+  if(target && target->GetType() == cmState::EXECUTABLE)
+    {
+    return target->GetProperty("CROSSCOMPILING_EMULATOR") != 0;
+    }
+  return false;
+}
+
+//----------------------------------------------------------------------------
 std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const
 {
   std::string const& argv0 = this->CC.GetCommandLines()[c][0];
@@ -50,7 +63,19 @@ std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const
     {
     return target->GetLocation(this->Config);
     }
-  return this->GE->Parse(argv0)->Evaluate(this->LG, this->Config);
+  if (target && target->GetType() == cmState::EXECUTABLE)
+    {
+    const char* emulator = target->GetProperty("CROSSCOMPILING_EMULATOR");
+    if (emulator)
+      {
+      return  std::string(emulator);
+      }
+    }
+
+  cmsys::auto_ptr<cmCompiledGeneratorExpression> cge = this->GE->Parse(argv0);
+  std::string exe = cge->Evaluate(this->LG, this->Config);
+
+  return exe;
 }
 
 //----------------------------------------------------------------------------
@@ -87,8 +112,13 @@ void
 cmCustomCommandGenerator
 ::AppendArguments(unsigned int c, std::string& cmd) const
 {
+  unsigned int offset = 1;
+  if (this->UseCrossCompilingEmulator(c))
+    {
+    offset = 0;
+    }
   cmCustomCommandLine const& commandLine = this->CC.GetCommandLines()[c];
-  for(unsigned int j=1;j < commandLine.size(); ++j)
+  for(unsigned int j=offset;j < commandLine.size(); ++j)
     {
     std::string arg =
         this->GE->Parse(commandLine[j])->Evaluate(this->LG,
diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h
index a637fed..65ce031 100644
--- a/Source/cmCustomCommandGenerator.h
+++ b/Source/cmCustomCommandGenerator.h
@@ -36,6 +36,7 @@ public:
   cmCustomCommand const& GetCC() const { return this->CC; }
   unsigned int GetNumberOfCommands() const;
   std::string GetCommand(unsigned int c) const;
+  bool UseCrossCompilingEmulator(unsigned int c) const;
   void AppendArguments(unsigned int c, std::string& cmd) const;
   const char* GetComment() const;
   std::string GetWorkingDirectory() const;
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 02e14e6..d16e5e7 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -289,8 +289,10 @@ if(CMake_TEST_FindMatlab)
 endif()
 
 add_executable(pseudo_emulator pseudo_emulator.c)
+add_executable(pseudo_emulator_custom_command pseudo_emulator_custom_command.c)
 add_RunCMake_test(CrosscompilingEmulator
- -DPSEUDO_EMULATOR=$<TARGET_FILE:pseudo_emulator>)
+ -DPSEUDO_EMULATOR=$<TARGET_FILE:pseudo_emulator>
+ -DPSEUDO_EMULATOR_CUSTOM_COMMAND=$<TARGET_FILE:pseudo_emulator_custom_command>)
 # Xcode 2.x forgets to create the output directory before linking
 # the individual architectures.
 if(CMAKE_OSX_ARCHITECTURES AND XCODE AND NOT "${XCODE_VERSION}" MATCHES "^[^12]")
@@ -298,6 +300,10 @@ if(CMAKE_OSX_ARCHITECTURES AND XCODE AND NOT "${XCODE_VERSION}" MATCHES "^[^12]"
     TARGET pseudo_emulator
     PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CFG_INTDIR}"
     )
+  add_custom_command(
+    TARGET pseudo_emulator_custom_command
+    PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CFG_INTDIR}"
+    )
 endif()
 
 if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
diff --git a/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand-build-check.cmake b/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand-build-check.cmake
new file mode 100644
index 0000000..e10b161
--- /dev/null
+++ b/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand-build-check.cmake
@@ -0,0 +1,5 @@
+foreach(output IN ITEMS output1 output2 output3 output4)
+  if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${output}")
+    message(FATAL_ERROR "Failed to create output: ${RunCMake_TEST_BINARY_DIR}/${output}")
+  endif()
+endforeach()
diff --git a/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand.cmake b/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand.cmake
new file mode 100644
index 0000000..67fa30f
--- /dev/null
+++ b/Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand.cmake
@@ -0,0 +1,38 @@
+set(CMAKE_CROSSCOMPILING 1)
+
+# Executable: Return error code different from 0
+add_executable(generated_exe_emulator_expected simple_src_exiterror.cxx)
+
+# Executable: Return error code equal to 0
+add_executable(generated_exe_emulator_unexpected simple_src_exitsuccess.cxx)
+
+# DoesNotUseEmulator
+add_custom_command(OUTPUT output1
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output1)
+
+# DoesNotUseEmulator: The command will fail if emulator is prepended
+add_custom_command(OUTPUT output2
+  COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:generated_exe_emulator_unexpected>"
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output2
+  DEPENDS generated_exe_emulator_unexpected)
+
+# DoesNotUseEmulator: The command will fail if emulator is prepended
+add_custom_command(OUTPUT output3
+  COMMAND $<TARGET_FILE:generated_exe_emulator_unexpected>
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output3
+  DEPENDS generated_exe_emulator_unexpected)
+
+# UsesEmulator: The command only succeeds if the emulator is prepended
+#               to the command.
+add_custom_command(OUTPUT output4
+  COMMAND generated_exe_emulator_expected
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output4
+  DEPENDS generated_exe_emulator_expected)
+
+add_custom_target(ensure_build ALL
+  SOURCES
+    ${CMAKE_CURRENT_BINARY_DIR}/output1
+    ${CMAKE_CURRENT_BINARY_DIR}/output2
+    ${CMAKE_CURRENT_BINARY_DIR}/output3
+    ${CMAKE_CURRENT_BINARY_DIR}/output4
+)
diff --git a/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget-build-check.cmake b/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget-build-check.cmake
new file mode 100644
index 0000000..c621922
--- /dev/null
+++ b/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget-build-check.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/AddCustomCommand-build-check.cmake)
diff --git a/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget.cmake b/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget.cmake
new file mode 100644
index 0000000..ced569f
--- /dev/null
+++ b/Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget.cmake
@@ -0,0 +1,30 @@
+set(CMAKE_CROSSCOMPILING 1)
+
+# Executable: Return error code different from 0
+add_executable(generated_exe_emulator_expected simple_src_exiterror.cxx)
+
+# Executable: Return error code equal to 0
+add_executable(generated_exe_emulator_unexpected simple_src_exitsuccess.cxx)
+
+# DoesNotUseEmulator
+add_custom_target(generate_output1 ALL
+  ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output1)
+
+# DoesNotUseEmulator: The command will fail if emulator is prepended
+add_custom_target(generate_output2 ALL
+  ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:generated_exe_emulator_unexpected>"
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output2
+  DEPENDS generated_exe_emulator_unexpected)
+
+# DoesNotUseEmulator: The command will fail if emulator is prepended
+add_custom_target(generate_output3 ALL
+  $<TARGET_FILE:generated_exe_emulator_unexpected>
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output3
+  DEPENDS generated_exe_emulator_unexpected)
+
+# UsesEmulator: The command only succeeds if the emulator is prepended
+#               to the command.
+add_custom_target(generate_output4 ALL
+  generated_exe_emulator_expected
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output4
+  DEPENDS generated_exe_emulator_expected)
diff --git a/Tests/RunCMake/CrosscompilingEmulator/RunCMakeTest.cmake b/Tests/RunCMake/CrosscompilingEmulator/RunCMakeTest.cmake
index 2581cfc..71aaad1 100644
--- a/Tests/RunCMake/CrosscompilingEmulator/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CrosscompilingEmulator/RunCMakeTest.cmake
@@ -6,3 +6,18 @@ set(RunCMake_TEST_OPTIONS
 run_cmake(CrosscompilingEmulatorProperty)
 run_cmake(TryRun)
 run_cmake(AddTest)
+
+function(CustomCommandGenerator_run_and_build case)
+  # Use a single build tree for a few tests without cleaning.
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_CROSSCOMPILING_EMULATOR=${PSEUDO_EMULATOR_CUSTOM_COMMAND}")
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  run_cmake(${case})
+  run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+CustomCommandGenerator_run_and_build(AddCustomCommand)
+CustomCommandGenerator_run_and_build(AddCustomTarget)
diff --git a/Tests/RunCMake/CrosscompilingEmulator/simple_src_exitsuccess.cxx b/Tests/RunCMake/CrosscompilingEmulator/simple_src_exitsuccess.cxx
new file mode 100644
index 0000000..630adc6
--- /dev/null
+++ b/Tests/RunCMake/CrosscompilingEmulator/simple_src_exitsuccess.cxx
@@ -0,0 +1,4 @@
+int main(int, char **)
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/pseudo_emulator_custom_command.c b/Tests/RunCMake/pseudo_emulator_custom_command.c
new file mode 100644
index 0000000..17181c9
--- /dev/null
+++ b/Tests/RunCMake/pseudo_emulator_custom_command.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Usage:
+//
+//  /path/to/program arg1 [arg2 [...]]
+//
+// Return EXIT_SUCCESS if 'generated_exe_emulator_expected'
+// string was found in <arg1>.
+// Return EXIT_FAILURE if 'generated_exe_emulator_unexpected'
+// string was found in <arg1>.
+
+int main(int argc, const char* argv[])
+{
+  const char* substring_failure = "generated_exe_emulator_unexpected";
+  const char* substring_success = "generated_exe_emulator_expected";
+  const char* str = argv[1];
+  if (argc < 2)
+    {
+    return EXIT_FAILURE;
+    }
+  if (strstr(str, substring_success) != 0)
+    {
+    return EXIT_SUCCESS;
+    }
+  if (strstr(str, substring_failure) != 0)
+    {
+    return EXIT_FAILURE;
+    }
+  fprintf(stderr, "Failed to find string '%s' in '%s'\n",
+          substring_success, str);
+  return EXIT_FAILURE;
+}
-- 
cgit v0.12