Browse Source

Merge topic 'test-launcher-emulator-genex'

13ece67a58 Add genex support to TEST_LAUNCHER and CROSSCOMPILING_EMULATOR
b9ad73fcb2 cmTestGenerator: De-duplicate TEST_LAUNCHER and CROSSCOMPILING_EMULATOR impl

Acked-by: Kitware Robot <[email protected]>
Merge-request: !9198
Brad King 1 year ago
parent
commit
622a498477

+ 4 - 0
Help/prop_tgt/CROSSCOMPILING_EMULATOR.rst

@@ -13,6 +13,10 @@ for built target system executables.
   Lists>`, then the first value is the command and remaining values are its
   arguments.
 
+.. versionadded:: 3.29
+  Contents of ``CROSSCOMPILING_EMULATOR`` may use
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
 This property is initialized by the value of the
 :variable:`CMAKE_CROSSCOMPILING_EMULATOR` variable if it is set when a target
 is created.

+ 3 - 0
Help/prop_tgt/TEST_LAUNCHER.rst

@@ -15,6 +15,9 @@ If this property contains a :ref:`semicolon-separated list <CMake Language
 Lists>`, then the first value is the command and remaining values are its
 arguments.
 
+Contents of ``TEST_LAUNCHER`` may use
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
 This property is initialized by the value of the
 :variable:`CMAKE_TEST_LAUNCHER` variable if it is set when a target
 is created.

+ 3 - 0
Help/release/dev/cmake-test-launcher.rst

@@ -9,3 +9,6 @@ cmake-test-launcher
 * The :command:`add_test` command now honors
   :variable:`CMAKE_CROSSCOMPILING_EMULATOR` only when cross-compiling.
   See policy :policy:`CMP0158`.
+
+* The :prop_tgt:`CROSSCOMPILING_EMULATOR` target property now
+  supports :manual:`generator expressions <cmake-generator-expressions(7)>`.

+ 5 - 2
Help/variable/CMAKE_CROSSCOMPILING_EMULATOR.rst

@@ -19,5 +19,8 @@ for the target system.
 The command will be used to run :command:`try_run` generated executables,
 which avoids manual population of the ``TryRunResults.cmake`` file.
 
-It is also used as the default value for the
-:prop_tgt:`CROSSCOMPILING_EMULATOR` target property of executables.
+This variable is also used as the default value for the
+:prop_tgt:`CROSSCOMPILING_EMULATOR` target property of executables.  However,
+while :manual:`generator expressions <cmake-generator-expressions(7)>` are
+supported by the target property (since CMake 3.29), they are *not* supported
+by this variable's :command:`try_run` functionality.

+ 5 - 5
Help/variable/CMAKE_TEST_LAUNCHER.rst

@@ -3,14 +3,14 @@ CMAKE_TEST_LAUNCHER
 
 .. versionadded:: 3.29
 
-This variable is used to specify a launcher for running tests, added
-by the :command:`add_test` command, that run an executable target.
+This variable is used to initialize the :prop_tgt:`TEST_LAUNCHER` target
+property of executable targets as they are created.  It is used to specify
+a launcher for running tests, added by the :command:`add_test` command,
+that run an executable target.
+
 If this variable contains a :ref:`semicolon-separated list <CMake Language
 Lists>`, then the first value is the command and remaining values are its
 arguments.
 
 This variable can be initialized via an
 :envvar:`CMAKE_TEST_LAUNCHER` environment variable.
-
-It is also used as the default value for the
-:prop_tgt:`TEST_LAUNCHER` target property of executables.

+ 12 - 1
Source/cmCustomCommandGenerator.cxx

@@ -284,8 +284,12 @@ void cmCustomCommandGenerator::FillEmulatorsWithArguments()
   if (!this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
     return;
   }
+  cmGeneratorExpression ge(*this->LG->GetCMakeInstance(),
+                           this->CC->GetBacktrace());
 
   for (unsigned int c = 0; c < this->GetNumberOfCommands(); ++c) {
+    // If the command is the plain name of an executable target,
+    // launch it with its emulator.
     std::string const& argv0 = this->CommandLines[c][0];
     cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
     if (target && target->GetType() == cmStateEnums::EXECUTABLE &&
@@ -297,7 +301,12 @@ void cmCustomCommandGenerator::FillEmulatorsWithArguments()
         continue;
       }
 
-      cmExpandList(*emulator_property, this->EmulatorsWithArguments[c]);
+      // Plain target names are replaced by GetArgv0Location with the
+      // path to the executable artifact in the command config, so
+      // evaluate the launcher's location in the command config too.
+      std::string const emulator =
+        ge.Parse(*emulator_property)->Evaluate(this->LG, this->CommandConfig);
+      cmExpandList(emulator, this->EmulatorsWithArguments[c]);
     }
   }
 }
@@ -313,6 +322,8 @@ std::vector<std::string> cmCustomCommandGenerator::GetCrossCompilingEmulator(
 
 const char* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const
 {
+  // If the command is the plain name of an executable target, we replace it
+  // with the path to the executable artifact in the command config.
   std::string const& argv0 = this->CommandLines[c][0];
   cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
   if (target && target->GetType() == cmStateEnums::EXECUTABLE &&

+ 16 - 12
Source/cmFileAPICodemodel.cxx

@@ -2092,18 +2092,22 @@ Json::Value Target::DumpLauncher(const char* name, const char* type)
   cmValue property = this->GT->GetProperty(name);
   Json::Value launcher;
   if (property) {
-    cmList commandWithArgs{ *property };
-    std::string command(commandWithArgs[0]);
-    cmSystemTools::ConvertToUnixSlashes(command);
-    launcher = Json::objectValue;
-    launcher["command"] = RelativeIfUnder(this->TopSource, command);
-    launcher["type"] = type;
-    Json::Value args;
-    for (std::string const& arg : cmMakeRange(commandWithArgs).advance(1)) {
-      args.append(arg);
-    }
-    if (!args.empty()) {
-      launcher["arguments"] = std::move(args);
+    cmLocalGenerator* lg = this->GT->GetLocalGenerator();
+    cmGeneratorExpression ge(*lg->GetCMakeInstance());
+    cmList commandWithArgs{ ge.Parse(*property)->Evaluate(lg, this->Config) };
+    if (!commandWithArgs.empty() && !commandWithArgs[0].empty()) {
+      std::string command(commandWithArgs[0]);
+      cmSystemTools::ConvertToUnixSlashes(command);
+      launcher = Json::objectValue;
+      launcher["command"] = RelativeIfUnder(this->TopSource, command);
+      launcher["type"] = type;
+      Json::Value args;
+      for (std::string const& arg : cmMakeRange(commandWithArgs).advance(1)) {
+        args.append(arg);
+      }
+      if (!args.empty()) {
+        launcher["arguments"] = std::move(args);
+      }
     }
   }
   return launcher;

+ 23 - 21
Source/cmTestGenerator.cxx

@@ -167,31 +167,33 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
   if (target && target->GetType() == cmStateEnums::EXECUTABLE) {
     // Use the target file on disk.
     exe = target->GetFullPath(config);
-    auto useEmulator = !this->GetTest()->GetCMP0158IsNew() ||
-      this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING");
 
-    // Prepend with the test launcher if specified.
-    cmValue launcher = target->GetProperty("TEST_LAUNCHER");
-    if (cmNonempty(launcher)) {
-      cmList launcherWithArgs{ *launcher };
-      std::string launcherExe(launcherWithArgs[0]);
-      cmSystemTools::ConvertToUnixSlashes(launcherExe);
-      os << cmOutputConverter::EscapeForCMake(launcherExe) << " ";
-      for (std::string const& arg : cmMakeRange(launcherWithArgs).advance(1)) {
-        os << cmOutputConverter::EscapeForCMake(arg) << " ";
+    auto addLauncher = [this, &config, &ge, &os,
+                        target](std::string const& propertyName) {
+      cmValue launcher = target->GetProperty(propertyName);
+      if (!cmNonempty(launcher)) {
+        return;
       }
-    }
+      cmList launcherWithArgs{ ge.Parse(*launcher)->Evaluate(this->LG,
+                                                             config) };
+      if (!launcherWithArgs.empty() && !launcherWithArgs[0].empty()) {
+        std::string launcherExe(launcherWithArgs[0]);
+        cmSystemTools::ConvertToUnixSlashes(launcherExe);
+        os << cmOutputConverter::EscapeForCMake(launcherExe) << " ";
+        for (std::string const& arg :
+             cmMakeRange(launcherWithArgs).advance(1)) {
+          os << cmOutputConverter::EscapeForCMake(arg) << " ";
+        }
+      }
+    };
+
+    // Prepend with the test launcher if specified.
+    addLauncher("TEST_LAUNCHER");
 
     // Prepend with the emulator when cross compiling if required.
-    cmValue emulator = target->GetProperty("CROSSCOMPILING_EMULATOR");
-    if (cmNonempty(emulator) && useEmulator) {
-      cmList emulatorWithArgs{ *emulator };
-      std::string emulatorExe(emulatorWithArgs[0]);
-      cmSystemTools::ConvertToUnixSlashes(emulatorExe);
-      os << cmOutputConverter::EscapeForCMake(emulatorExe) << " ";
-      for (std::string const& arg : cmMakeRange(emulatorWithArgs).advance(1)) {
-        os << cmOutputConverter::EscapeForCMake(arg) << " ";
-      }
+    if (!this->GetTest()->GetCMP0158IsNew() ||
+        this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
+      addLauncher("CROSSCOMPILING_EMULATOR");
     }
   } else {
     // Use the command name given.

+ 1 - 1
Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand-build-check.cmake

@@ -1,4 +1,4 @@
-foreach(output IN ITEMS output1 output2 output3 output4)
+foreach(output IN ITEMS output1 output2 output3 output4 output5)
   if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${output}")
     set(RunCMake_TEST_FAILED "Failed to create output:\n  ${RunCMake_TEST_BINARY_DIR}/${output}")
     return()

+ 14 - 0
Tests/RunCMake/CrosscompilingEmulator/AddCustomCommand.cmake

@@ -3,6 +3,8 @@ set(CMAKE_CROSSCOMPILING 1)
 
 # Executable: Return error code different from 0
 add_executable(generated_exe_emulator_expected simple_src_exiterror.c)
+get_property(emulator TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR)
+set_property(TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR "$<1:${emulator}>")
 
 # Executable: Return error code equal to 0
 add_executable(generated_exe_emulator_unexpected emulator_unexpected.c)
@@ -47,6 +49,17 @@ add_custom_command(OUTPUT output4
   COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output4
   DEPENDS generated_exe_emulator_expected)
 
+# Use locally-built emulator.
+add_executable(local_emulator ../pseudo_emulator.c)
+add_executable(use_emulator_local simple_src_exiterror.c)
+set_property(TARGET use_emulator_local PROPERTY CROSSCOMPILING_EMULATOR "$<TARGET_FILE:local_emulator>")
+add_dependencies(use_emulator_local local_emulator)
+add_custom_command(OUTPUT output5
+  COMMAND ${CMAKE_COMMAND} -E echo use_emulator_local
+  COMMAND use_emulator_local
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output5
+  DEPENDS use_emulator_local)
+
 add_custom_target(ensure_build ALL
   SOURCES
     ${CMAKE_CURRENT_BINARY_DIR}/output1
@@ -54,4 +67,5 @@ add_custom_target(ensure_build ALL
     ${CMAKE_CURRENT_BINARY_DIR}/output3
     ${CMAKE_CURRENT_BINARY_DIR}/outputImp
     ${CMAKE_CURRENT_BINARY_DIR}/output4
+    ${CMAKE_CURRENT_BINARY_DIR}/output5
 )

+ 2 - 0
Tests/RunCMake/CrosscompilingEmulator/AddCustomCommandWithArg.cmake

@@ -3,6 +3,8 @@ set(CMAKE_CROSSCOMPILING 1)
 
 # Executable: Return error code different from 0
 add_executable(generated_exe_emulator_expected simple_src_exiterror.c)
+get_property(emulator TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR)
+set_property(TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR "$<1:${emulator}>")
 
 add_custom_command(OUTPUT output
   COMMAND generated_exe_emulator_expected

+ 12 - 0
Tests/RunCMake/CrosscompilingEmulator/AddCustomTarget.cmake

@@ -3,6 +3,8 @@ set(CMAKE_CROSSCOMPILING 1)
 
 # Executable: Return error code different from 0
 add_executable(generated_exe_emulator_expected simple_src_exiterror.c)
+get_property(emulator TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR)
+set_property(TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR "$<1:${emulator}>")
 
 # Executable: Return error code equal to 0
 add_executable(generated_exe_emulator_unexpected emulator_unexpected.c)
@@ -43,3 +45,13 @@ 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)
+
+# Use locally-built emulator.
+add_executable(local_emulator ../pseudo_emulator.c)
+add_executable(use_emulator_local simple_src_exiterror.c)
+set_property(TARGET use_emulator_local PROPERTY CROSSCOMPILING_EMULATOR "$<TARGET_FILE:local_emulator>")
+add_dependencies(use_emulator_local local_emulator)
+add_custom_target(generate_output5 ALL
+  use_emulator_local
+  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/output5
+  DEPENDS use_emulator_local)

+ 2 - 0
Tests/RunCMake/CrosscompilingEmulator/AddCustomTargetWithArg.cmake

@@ -3,6 +3,8 @@ set(CMAKE_CROSSCOMPILING 1)
 
 # Executable: Return error code different from 0
 add_executable(generated_exe_emulator_expected simple_src_exiterror.c)
+get_property(emulator TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR)
+set_property(TARGET generated_exe_emulator_expected PROPERTY CROSSCOMPILING_EMULATOR "$<1:${emulator}>")
 
 add_custom_target(generate_output ALL
   generated_exe_emulator_expected

+ 5 - 5
Tests/RunCMake/CrosscompilingEmulator/AddTest-CMP0158-NEW-check.cmake

@@ -8,27 +8,27 @@ endif()
 
 set(error_details "There is a problem with generated test file:\n  ${testfile}")
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]ShouldNotUseEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]ShouldNotUseEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]ShouldNotUseEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]ShouldNotUseEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()

+ 5 - 5
Tests/RunCMake/CrosscompilingEmulator/AddTest-CMP0158-OLD-check.cmake

@@ -8,27 +8,27 @@ endif()
 
 set(error_details "There is a problem with generated test file:\n  ${testfile}")
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()

+ 10 - 6
Tests/RunCMake/CrosscompilingEmulator/AddTest-check.cmake

@@ -8,32 +8,36 @@ endif()
 
 set(error_details "There is a problem with generated test file:\n  ${testfile}")
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesEmulator [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesEmulator [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use emulator when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used emulator when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherAndEmulator[^\n]+pseudo_emulator[^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherAndEmulator[^$<>\n]+pseudo_emulator[^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use test launcher and emulator when they should be used. ${error_details}")
   return()
 endif()
+
+if(NOT testfile_contents MATCHES "add_test[(]UsesLocalEmulator [^$<>\n]+local_emulator[^$<>\n]+use_emulator_local[^$<>\n]+\n")
+  message(SEND_ERROR "Did not use local emulator when it should be used. ${error_details}")
+endif()

+ 16 - 6
Tests/RunCMake/CrosscompilingEmulator/AddTest-test-stdout.txt

@@ -7,7 +7,7 @@ test 1
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 1: Test timeout computed to be: [0-9]+
 1: Hi
-1/6 Test #1: DoesNotUseEmulator [.]* +Passed +[0-9.]+ sec
+1/7 Test #1: DoesNotUseEmulator [.]* +Passed +[0-9.]+ sec
 test 2
     Start 2: UsesEmulator
 
@@ -17,7 +17,7 @@ test 2
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 2: Test timeout computed to be: [0-9]+
 2: Command: "[^"]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build(/Debug)?/exe(\.exe)?"
-2/6 Test #2: UsesEmulator [.]* +Passed +[0-9.]+ sec
+2/7 Test #2: UsesEmulator [.]* +Passed +[0-9.]+ sec
 test 3
     Start 3: DoesNotUseEmulatorWithGenex
 
@@ -26,7 +26,7 @@ test 3
 3: Working Directory: [^
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 3: Test timeout computed to be: [0-9]+
-3/6 Test #3: DoesNotUseEmulatorWithGenex [.]* +Passed +[0-9.]+ sec
+3/7 Test #3: DoesNotUseEmulatorWithGenex [.]* +Passed +[0-9.]+ sec
 test 4
     Start 4: UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex
 
@@ -36,7 +36,7 @@ test 4
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 4: Test timeout computed to be: [0-9]+
 4: Command: "[^"]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build/AddTest(/Debug)?/subdir_exe_no_genex(\.exe)?"
-4/6 Test #4: UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [.]* +Passed +[0-9.]+ sec
+4/7 Test #4: UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex [.]* +Passed +[0-9.]+ sec
 test 5
     Start 5: DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex
 
@@ -45,7 +45,7 @@ test 5
 5: Working Directory: [^
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 5: Test timeout computed to be: [0-9]+
-5/6 Test #5: DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [.]* +Passed +[0-9.]+ sec
+5/7 Test #5: DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [.]* +Passed +[0-9.]+ sec
 test 6
     Start 6: UsesTestLauncherAndEmulator
 
@@ -55,4 +55,14 @@ test 6
 ]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
 6: Test timeout computed to be: [0-9]+
 6: Command: "[^"]*/Tests/RunCMake(/[^/]+)?/pseudo_emulator(\.exe)?" "[^"]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build(/Debug)?/exe_test_launcher(\.exe)?"
-6/6 Test #6: UsesTestLauncherAndEmulator [.]* +Passed +[0-9.]+ sec
+6/7 Test #6: UsesTestLauncherAndEmulator [.]* +Passed +[0-9.]+ sec
+test 7
+    Start 7: UsesLocalEmulator
+
+7: Test command: "?[^
+]*[/\]Tests[/\]RunCMake[/\]CrosscompilingEmulator[/\]AddTest-build([/\]Debug)?[/\]local_emulator(\.exe)?"? "[^"]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build(/Debug)?/use_emulator_local(\.exe)?"
+7: Working Directory: [^
+]*/Tests/RunCMake/CrosscompilingEmulator/AddTest-build
+7: Test timeout computed to be: [0-9]+
+7: Command: "[^"]*/use_emulator_local[^"]*"
+7/7 Test #7: UsesLocalEmulator [.]* +Passed +[0-9.]+ sec

+ 9 - 1
Tests/RunCMake/CrosscompilingEmulator/AddTest.cmake

@@ -6,6 +6,8 @@ add_test(NAME DoesNotUseEmulator
   COMMAND ${CMAKE_COMMAND} -E echo "Hi")
 
 add_executable(exe main.c)
+get_property(emulator TARGET exe PROPERTY CROSSCOMPILING_EMULATOR)
+set_property(TARGET exe PROPERTY CROSSCOMPILING_EMULATOR "$<1:${emulator}>")
 
 add_test(NAME UsesEmulator
   COMMAND exe)
@@ -21,8 +23,14 @@ add_test(NAME UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex
 add_test(NAME DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex
   COMMAND $<TARGET_FILE:subdir_exe_with_genex>)
 
-set(CMAKE_TEST_LAUNCHER ${CMAKE_CROSSCOMPILING_EMULATOR})
+set(CMAKE_TEST_LAUNCHER "$<1:${CMAKE_CROSSCOMPILING_EMULATOR}>")
 add_executable(exe_test_launcher main.c)
+unset(CMAKE_TEST_LAUNCHER)
 
 add_test(NAME UsesTestLauncherAndEmulator
   COMMAND exe_test_launcher)
+
+add_executable(local_emulator ../pseudo_emulator.c)
+add_executable(use_emulator_local main.c)
+set_property(TARGET use_emulator_local PROPERTY CROSSCOMPILING_EMULATOR "$<TARGET_FILE:local_emulator>")
+add_test(NAME UsesLocalEmulator COMMAND use_emulator_local)

+ 1 - 1
Tests/RunCMake/FileAPI/cxx/CMakeLists.txt

@@ -47,6 +47,6 @@ if(_rdeps)
 endif()
 
 add_executable(cxx_exe_test_launcher ../empty.cxx)
-set_property(TARGET cxx_exe_test_launcher PROPERTY TEST_LAUNCHER no-such-launcher)
+set_property(TARGET cxx_exe_test_launcher PROPERTY TEST_LAUNCHER "$<1:no-such-launcher>")
 
 add_subdirectory(cross)

+ 2 - 2
Tests/RunCMake/FileAPI/cxx/cross/CMakeLists.txt

@@ -7,5 +7,5 @@ add_executable(cxx_exe_cross_emulator_args ../../empty.cxx)
 set_property(TARGET cxx_exe_cross_emulator_args PROPERTY CROSSCOMPILING_EMULATOR "no-such-emulator;arg1;arg2 with space")
 
 add_executable(cxx_exe_test_launcher_and_cross_emulator ../../empty.cxx)
-set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY TEST_LAUNCHER "no-such-launcher")
-set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY CROSSCOMPILING_EMULATOR "no-such-emulator")
+set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY TEST_LAUNCHER "$<1:no-such-launcher>")
+set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY CROSSCOMPILING_EMULATOR "$<1:no-such-emulator>")

+ 9 - 5
Tests/RunCMake/add_test/TestLauncher-check.cmake

@@ -8,27 +8,31 @@ endif()
 
 set(error_details "There is a problem with generated test file:\n  ${testfile}")
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncher [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncher [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used test launcher when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncher [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncher [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use test launcher when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used test launcher when it should not be used. ${error_details}")
   return()
 endif()
 
-if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Did not use test launcher when it should be used. ${error_details}")
   return()
 endif()
 
-if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_emulator[^\n]+\n")
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex [^$<>\n]+pseudo_emulator[^$<>\n]+\n")
   set(RunCMake_TEST_FAILED "Used test launcher when it should not be used. ${error_details}")
   return()
 endif()
+
+if(NOT testfile_contents MATCHES "add_test[(]UsesLocalLauncher [^$<>\n]+local_launcher[^$<>\n]+use_launcher_local[^$<>\n]+\n")
+  message(SEND_ERROR "Did not use local test launcher when it should be used. ${error_details}")
+endif()

+ 15 - 5
Tests/RunCMake/add_test/TestLauncher-test-stdout.txt

@@ -7,7 +7,7 @@ test 1
 ]*/Tests/RunCMake/add_test/TestLauncher-build
 1: Test timeout computed to be: [0-9]+
 1: Hi
-1/5 Test #1: DoesNotUseLauncher [.]* +Passed +[0-9.]+ sec
+1/6 Test #1: DoesNotUseLauncher [.]* +Passed +[0-9.]+ sec
 test 2
     Start 2: UsesTestLauncher
 +
@@ -17,7 +17,7 @@ test 2
 ]*Tests/RunCMake/add_test/TestLauncher-build
 2: Test timeout computed to be: [0-9]+
 2: Command: "[^"]*/Tests/RunCMake/add_test/TestLauncher-build(/Debug)?/exe(\.exe)?"
-2/5 Test #2: UsesTestLauncher [.]* +Passed +[0-9.]+ sec
+2/6 Test #2: UsesTestLauncher [.]* +Passed +[0-9.]+ sec
 test 3
     Start 3: DoesNotUseTestLauncherWithGenex
 +
@@ -26,7 +26,7 @@ test 3
 3: Working Directory: [^
 ]*Tests/RunCMake/add_test/TestLauncher-build
 3: Test timeout computed to be: [0-9]+
-3/5 Test #3: DoesNotUseTestLauncherWithGenex [.]* +Passed +[0-9.]+ sec
+3/6 Test #3: DoesNotUseTestLauncherWithGenex [.]* +Passed +[0-9.]+ sec
 test 4
     Start 4: UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex
 +
@@ -36,7 +36,7 @@ test 4
 ]*Tests/RunCMake/add_test/TestLauncher-build
 4: Test timeout computed to be: [0-9]+
 4: Command: "[^"]*/Tests/RunCMake/add_test/TestLauncher-build/TestLauncher(/Debug)?/subdir_exe_no_genex(\.exe)?"
-4/5 Test #4: UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex [.]* +Passed +[0-9.]+ sec
+4/6 Test #4: UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex [.]* +Passed +[0-9.]+ sec
 test 5
     Start 5: DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex
 +
@@ -45,4 +45,14 @@ test 5
 5: Working Directory: [^
 ]*Tests/RunCMake/add_test/TestLauncher-build
 5: Test timeout computed to be: [0-9]+
-5/5 Test #5: DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex [.]* +Passed +[0-9.]+ sec
+5/6 Test #5: DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex [.]* +Passed +[0-9.]+ sec
+test 6
+    Start 6: UsesLocalLauncher
+
+6: Test command: "?[^
+]*[/\]Tests[/\]RunCMake[/\]add_test[/\]TestLauncher-build([/\]Debug)?[/\]local_launcher(\.exe)?"? "[^"]*/Tests/RunCMake/add_test/TestLauncher-build(/Debug)?/use_launcher_local(\.exe)?"
+6: Working Directory: [^
+]*/Tests/RunCMake/add_test/TestLauncher-build
+6: Test timeout computed to be: [0-9]+
+6: Command: "[^"]*/Tests/RunCMake/add_test/TestLauncher-build(/Debug)?/use_launcher_local(\.exe)?"
+6/6 Test #6: UsesLocalLauncher [.]* +Passed +[0-9.]+ sec

+ 7 - 0
Tests/RunCMake/add_test/TestLauncher.cmake

@@ -5,6 +5,8 @@ add_test(NAME DoesNotUseLauncher
   COMMAND ${CMAKE_COMMAND} -E echo "Hi")
 
 add_executable(exe main.c)
+get_property(test_launcher TARGET exe PROPERTY TEST_LAUNCHER)
+set_property(TARGET exe PROPERTY TEST_LAUNCHER "$<1:${test_launcher}>")
 
 add_test(NAME UsesTestLauncher
   COMMAND exe)
@@ -19,3 +21,8 @@ add_test(NAME UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex
 
 add_test(NAME DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex
   COMMAND $<TARGET_FILE:subdir_exe_with_genex>)
+
+add_executable(local_launcher ../pseudo_emulator.c)
+add_executable(use_launcher_local main.c)
+set_property(TARGET use_launcher_local PROPERTY TEST_LAUNCHER "$<TARGET_FILE:local_launcher>")
+add_test(NAME UsesLocalLauncher COMMAND use_launcher_local)