1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-13 17:47:49 +08:00

add_library/add_executable: allow local alias to imported targets

Fixes: #20641
This commit is contained in:
Marc Chevrier
2020-05-28 13:51:22 +02:00
parent 254f2b9058
commit 056489d567
27 changed files with 310 additions and 65 deletions

View File

@@ -83,8 +83,13 @@ Alias Executables
Creates an :ref:`Alias Target <Alias Targets>`, such that ``<name>`` can
be used to refer to ``<target>`` in subsequent commands. The ``<name>``
does not appear in the generated buildsystem as a make target. The
``<target>`` may not be a non-``GLOBAL``
:ref:`Imported Target <Imported Targets>` or an ``ALIAS``.
``<target>`` may not be an ``ALIAS``.
An ``ALIAS`` to a non-``GLOBAL`` :ref:`Imported Target <Imported Targets>`
has scope in the directory in which the alias is created and below.
The :prop_tgt:`ALIAS_GLOBAL` target property can be used to check if the
alias is global or not.
``ALIAS`` targets can be used as targets to read properties
from, executables for custom commands and custom targets. They can also be
tested for existence with the regular :command:`if(TARGET)` subcommand.

View File

@@ -139,8 +139,13 @@ Alias Libraries
Creates an :ref:`Alias Target <Alias Targets>`, such that ``<name>`` can be
used to refer to ``<target>`` in subsequent commands. The ``<name>`` does
not appear in the generated buildsystem as a make target. The ``<target>``
may not be a non-``GLOBAL`` :ref:`Imported Target <Imported Targets>` or an
``ALIAS``.
may not be an ``ALIAS``.
An ``ALIAS`` to a non-``GLOBAL`` :ref:`Imported Target <Imported Targets>`
has scope in the directory in which the alias is created and below.
The :prop_tgt:`ALIAS_GLOBAL` target property can be used to check if the
alias is global or not.
``ALIAS`` targets can be used as linkable targets and as targets to
read properties from. They can also be tested for existence with the
regular :command:`if(TARGET)` subcommand. The ``<name>`` may not be used

View File

@@ -105,6 +105,7 @@ Properties on Targets
/prop_tgt/ADDITIONAL_CLEAN_FILES
/prop_tgt/AIX_EXPORT_ALL_SYMBOLS
/prop_tgt/ALIAS_GLOBAL
/prop_tgt/ALIASED_TARGET
/prop_tgt/ANDROID_ANT_ADDITIONAL_OPTIONS
/prop_tgt/ANDROID_API

View File

@@ -0,0 +1,17 @@
ALIAS_GLOBAL
------------
Read-only property indicating of whether an :ref:`ALIAS target <Alias Targets>`
is globally visible.
The boolean value of this property is ``TRUE`` for aliases to
:ref:`IMPORTED targets <Imported Targets>` created
with the ``GLOBAL`` options to :command:`add_executable()` or
:command:`add_library()`, ``FALSE`` otherwise. It is undefined for
targets built within the project.
.. note::
Promoting an :ref:`IMPORTED target <Imported Targets>` from ``LOCAL``
to ``GLOBAL`` scope by changing the value or :prop_tgt:`IMPORTED_GLOBAL`
target property do not change the scope of local aliases.

View File

@@ -16,7 +16,15 @@ property for such a locally ``IMPORTED`` target to True promotes that
target to global scope. This promotion can only be done in the same
directory where that ``IMPORTED`` target was created in the first place.
Once an imported target has been made global, it cannot be changed back to
non-global. Therefore, if a project sets this property, it may only
provide a value of True. CMake will issue an error if the project tries to
set the property to a non-True value, even if the value was already False.
.. note::
Once an imported target has been made global, it cannot be changed back to
non-global. Therefore, if a project sets this property, it may only
provide a value of True. CMake will issue an error if the project tries to
set the property to a non-True value, even if the value was already False.
.. note::
Local :ref:`ALIAS targets <Alias Targets>` created before promoting an
:ref:`IMPORTED target <Imported Targets>` from ``LOCAL`` to ``GLOBAL``, keep
their initial scope (see :prop_tgt:`ALIAS_GLOBAL` target property).

View File

@@ -0,0 +1,6 @@
alias-local-imported-target
---------------------------
* :command:`add_library` and :command:`add_executable` gain the capability
to create an ``ALIAS`` to
non-``GLOBAL`` :ref:`Imported Target <Imported Targets>`.

View File

@@ -117,14 +117,9 @@ bool cmAddExecutableCommand(std::vector<std::string> const& args,
"\" is not an executable."));
return false;
}
if (aliasedTarget->IsImported() &&
!aliasedTarget->IsImportedGloballyVisible()) {
status.SetError(cmStrCat("cannot create ALIAS target \"", exename,
"\" because target \"", aliasedName,
"\" is imported but not globally visible."));
return false;
}
mf.AddAlias(exename, aliasedName);
mf.AddAlias(exename, aliasedName,
!aliasedTarget->IsImported() ||
aliasedTarget->IsImportedGloballyVisible());
return true;
}

View File

@@ -219,14 +219,9 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args,
"\" is not a library."));
return false;
}
if (aliasedTarget->IsImported() &&
!aliasedTarget->IsImportedGloballyVisible()) {
status.SetError(cmStrCat("cannot create ALIAS target \"", libName,
"\" because target \"", aliasedName,
"\" is imported but not globally visible."));
return false;
}
mf.AddAlias(libName, aliasedName);
mf.AddAlias(libName, aliasedName,
!aliasedTarget->IsImported() ||
aliasedTarget->IsImportedGloballyVisible());
return true;
}

View File

@@ -1342,6 +1342,14 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
}
return std::string();
}
if (propertyName == "ALIAS_GLOBAL"_s) {
if (context->LG->GetMakefile()->IsAlias(targetName)) {
return context->LG->GetGlobalGenerator()->IsAlias(targetName)
? "TRUE"
: "FALSE";
}
return std::string();
}
target = context->LG->FindGeneratorTargetToUse(targetName);
if (!target) {

View File

@@ -344,10 +344,20 @@ bool HandleTargetMode(cmExecutionStatus& status, const std::string& name,
}
if (cmTarget* target = status.GetMakefile().FindTargetToUse(name)) {
if (propertyName == "ALIASED_TARGET") {
if (propertyName == "ALIASED_TARGET" || propertyName == "ALIAS_GLOBAL") {
if (status.GetMakefile().IsAlias(name)) {
return StoreResult(infoType, status.GetMakefile(), variable,
target->GetName().c_str());
if (propertyName == "ALIASED_TARGET") {
return StoreResult(infoType, status.GetMakefile(), variable,
target->GetName().c_str());
}
if (propertyName == "ALIAS_GLOBAL") {
return StoreResult(
infoType, status.GetMakefile(), variable,
status.GetMakefile().GetGlobalGenerator()->IsAlias(name)
? "TRUE"
: "FALSE");
}
}
return StoreResult(infoType, status.GetMakefile(), variable, nullptr);
}

View File

@@ -5,6 +5,7 @@
#include <sstream>
#include "cmExecutionStatus.h"
#include "cmGlobalGenerator.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
@@ -29,10 +30,17 @@ bool cmGetTargetPropertyCommand(std::vector<std::string> const& args,
cmMakefile& mf = status.GetMakefile();
if (cmTarget* tgt = mf.FindTargetToUse(targetName)) {
if (args[2] == "ALIASED_TARGET") {
if (args[2] == "ALIASED_TARGET" || args[2] == "ALIAS_GLOBAL") {
if (mf.IsAlias(targetName)) {
prop = tgt->GetName();
prop_exists = true;
if (args[2] == "ALIASED_TARGET") {
prop = tgt->GetName();
}
if (args[2] == "ALIAS_GLOBAL") {
prop =
mf.GetGlobalGenerator()->IsAlias(targetName) ? "TRUE" : "FALSE";
}
}
} else if (!args[2].empty()) {
cmProp prop_cstr = nullptr;

View File

@@ -2049,6 +2049,15 @@ cmGeneratorTarget* cmLocalGenerator::FindGeneratorTargetToUse(
return imported->second;
}
// find local alias to imported target
auto aliased = this->AliasTargets.find(name);
if (aliased != this->AliasTargets.end()) {
imported = this->ImportedGeneratorTargets.find(aliased->second);
if (imported != this->ImportedGeneratorTargets.end()) {
return imported->second;
}
}
if (cmGeneratorTarget* t = this->FindLocalNonAliasGeneratorTarget(name)) {
return t;
}
@@ -2468,7 +2477,8 @@ bool cmLocalGenerator::GetShouldUseOldFlags(bool shared,
std::ostringstream e;
e << "Variable " << flagsVar
<< " has been modified. CMake "
"will ignore the POSITION_INDEPENDENT_CODE target property for "
"will ignore the POSITION_INDEPENDENT_CODE target property "
"for "
"shared libraries and will use the "
<< flagsVar
<< " variable "
@@ -2565,7 +2575,8 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
}
for (std::string const& config : configsList) {
// FIXME: Refactor collection of sources to not evaluate object libraries.
// FIXME: Refactor collection of sources to not evaluate object
// libraries.
std::vector<cmSourceFile*> sources;
target->GetSourceFiles(sources, config);
@@ -3270,8 +3281,8 @@ const char* cmLocalGenerator::GetFeature(const std::string& feature,
const std::string& config)
{
std::string featureName = feature;
// TODO: Define accumulation policy for features (prepend, append, replace).
// Currently we always replace.
// TODO: Define accumulation policy for features (prepend, append,
// replace). Currently we always replace.
if (!config.empty()) {
featureName += "_";
featureName += cmSystemTools::UpperCase(config);
@@ -4120,9 +4131,9 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
cmImplicitDependsList no_implicit_depends;
cmSourceFile* rule = AddCustomCommand(
lg, lfbt, { force.Name }, byproducts, depends, no_main_dependency,
no_implicit_depends, commandLines, comment, workingDir, /*replace=*/false,
escapeOldStyle, uses_terminal, command_expand_lists, /*depfile=*/"",
job_pool, stdPipesUTF8);
no_implicit_depends, commandLines, comment, workingDir,
/*replace=*/false, escapeOldStyle, uses_terminal, command_expand_lists,
/*depfile=*/"", job_pool, stdPipesUTF8);
if (rule) {
lg.GetMakefile()->AddTargetByproducts(target, byproducts);
}

View File

@@ -2035,10 +2035,13 @@ void cmMakefile::AddGlobalLinkInformation(cmTarget& target)
}
}
void cmMakefile::AddAlias(const std::string& lname, std::string const& tgtName)
void cmMakefile::AddAlias(const std::string& lname, std::string const& tgtName,
bool globallyVisible)
{
this->AliasTargets[lname] = tgtName;
this->GetGlobalGenerator()->AddAlias(lname, tgtName);
if (globallyVisible) {
this->GetGlobalGenerator()->AddAlias(lname, tgtName);
}
}
cmTarget* cmMakefile::AddLibrary(const std::string& lname,
@@ -4286,7 +4289,15 @@ cmTarget* cmMakefile::FindTargetToUse(const std::string& name,
{
// Look for an imported target. These take priority because they
// are more local in scope and do not have to be globally unique.
auto imported = this->ImportedTargets.find(name);
auto targetName = name;
if (!excludeAliases) {
// Look for local alias targets.
auto alias = this->AliasTargets.find(name);
if (alias != this->AliasTargets.end()) {
targetName = alias->second;
}
}
auto imported = this->ImportedTargets.find(targetName);
if (imported != this->ImportedTargets.end()) {
return imported->second;
}

View File

@@ -356,7 +356,8 @@ public:
cmStateEnums::TargetType type,
const std::vector<std::string>& srcs,
bool excludeFromAll = false);
void AddAlias(const std::string& libname, const std::string& tgt);
void AddAlias(const std::string& libname, const std::string& tgt,
bool globallyVisible = true);
//@{
/**

View File

@@ -111,6 +111,7 @@ run_cmake(TARGET_LINKER_FILE_BASE_NAME-non-valid-target)
run_cmake(TARGET_PROPERTY-INCLUDE_DIRECTORIES)
run_cmake(TARGET_PROPERTY-LOCATION)
run_cmake(TARGET_PROPERTY-SOURCES)
run_cmake(TARGET_PROPERTY-ALIAS_GLOBAL)
run_cmake(LINK_ONLY-not-linking)
run_cmake(TARGET_EXISTS-no-arg)
run_cmake(TARGET_EXISTS-empty-arg)

View File

@@ -0,0 +1,6 @@
file(STRINGS ${RunCMake_TEST_BINARY_DIR}/alias_global.txt alias_global)
set(expected "TRUE(lib-global):TRUE;FALSE(lib-local):FALSE;TRUE(lib):FALSE")
if(NOT alias_global STREQUAL expected)
set(RunCMake_TEST_FAILED "ALIAS_GLOBAL was:\n [[${alias_global}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.17)
add_library(lib-global SHARED IMPORTED GLOBAL)
add_library(alias-lib-global ALIAS lib-global)
add_library(lib-local SHARED IMPORTED)
add_library(alias-lib-local ALIAS lib-local)
add_library(lib SHARED IMPORTED)
add_library(alias-lib ALIAS lib)
# switch from local to global
set_property (TARGET lib PROPERTY IMPORTED_GLOBAL TRUE)
file(GENERATE OUTPUT alias_global.txt CONTENT "$<TARGET_PROPERTY:lib-global,IMPORTED_GLOBAL>($<TARGET_PROPERTY:alias-lib-global,ALIASED_TARGET>):$<TARGET_PROPERTY:alias-lib-global,ALIAS_GLOBAL>\n$<TARGET_PROPERTY:lib-local,IMPORTED_GLOBAL>($<TARGET_PROPERTY:alias-lib-local,ALIASED_TARGET>):$<TARGET_PROPERTY:alias-lib-local,ALIAS_GLOBAL>\n$<TARGET_PROPERTY:lib,IMPORTED_GLOBAL>($<TARGET_PROPERTY:alias-lib,ALIASED_TARGET>):$<TARGET_PROPERTY:alias-lib,ALIAS_GLOBAL>\n")

View File

@@ -12,6 +12,7 @@ run_cmake(imported-global-target)
run_cmake(imported-target)
run_cmake(alias-target)
run_cmake(set_property)
run_cmake(get_property)
run_cmake(set_target_properties)
run_cmake(target_link_libraries)
run_cmake(target_include_directories)

View File

@@ -0,0 +1,8 @@
add_library(alias::import-local-subdir ALIAS import-local)
check_property (alias::import-local-subdir ALIASED_TARGET "import-local")
check_property (alias::import-local-subdir IMPORTED "TRUE")
check_property (alias::import-local-subdir ALIAS_GLOBAL "FALSE")
check_property (alias::import-local-subdir IMPORT_LOCAL_PROPERTY "IMPORT_LOCAL")

View File

@@ -0,0 +1,59 @@
enable_language(CXX)
function (check_property alias property value)
get_property (data TARGET ${alias} PROPERTY ${property})
if (NOT "${value}" STREQUAL "${data}")
message (SEND_ERROR "get_property(): Target property '${property}' from ALIAS '${alias}' has wrong value: '${data}' instead of '${value}'.")
endif()
get_target_property (data ${alias} ${property})
if (NOT "${value}" STREQUAL "${data}")
message (SEND_ERROR "get_target_property(): Target property '${property}' from ALIAS '${alias}' has wrong value: '${data}' instead of '${value}'.")
endif()
endfunction()
add_library(lib empty.cpp)
set_property (TARGET lib PROPERTY LIB_PROPERTY "LIB")
add_library(alias::lib ALIAS lib)
check_property (alias::lib ALIASED_TARGET "lib")
check_property (alias::lib IMPORTED "FALSE")
check_property (alias::lib ALIAS_GLOBAL "TRUE")
check_property (alias::lib LIB_PROPERTY "LIB")
add_library(import-global SHARED IMPORTED GLOBAL)
set_property (TARGET import-global PROPERTY IMPORT_GLOBAL_PROPERTY "IMPORT_GLOBAL")
add_library(alias::import-global ALIAS import-global)
check_property (alias::import-global ALIASED_TARGET "import-global")
check_property (alias::import-global IMPORTED "TRUE")
check_property (alias::import-global ALIAS_GLOBAL "TRUE")
check_property (alias::import-global IMPORT_GLOBAL_PROPERTY "IMPORT_GLOBAL")
add_library(import-local SHARED IMPORTED)
set_property (TARGET import-local PROPERTY IMPORT_LOCAL_PROPERTY "IMPORT_LOCAL")
add_library(alias::import-local ALIAS import-local)
check_property (alias::import-local ALIASED_TARGET "import-local")
check_property (alias::import-local IMPORTED "TRUE")
check_property (alias::import-local ALIAS_GLOBAL "FALSE")
check_property (alias::import-local IMPORT_LOCAL_PROPERTY "IMPORT_LOCAL")
## upgrade imported target from local to global, alias stay local
add_library(import-lib SHARED IMPORTED)
add_library(alias::import-lib ALIAS import-lib)
check_property (alias::import-lib IMPORTED_GLOBAL "FALSE")
check_property (alias::import-lib ALIAS_GLOBAL "FALSE")
set_property (TARGET import-lib PROPERTY IMPORTED_GLOBAL "TRUE")
check_property (alias::import-lib IMPORTED_GLOBAL "TRUE")
check_property (alias::import-lib ALIAS_GLOBAL "FALSE")
add_subdirectory (get_property-subdir)

View File

@@ -1,15 +1,2 @@
^CMake Error at imported-target.cmake:[0-9]+ \(add_executable\):
add_executable cannot create ALIAS target \"alias-test-exe\" because target
\"test-exe\" is imported but not globally visible.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
+
'alias-test-exe' does not exist![?]
*
CMake Error at imported-target.cmake:[0-9]+ \(add_library\):
add_library cannot create ALIAS target "alias-test-lib" because target
"test-lib" is imported but not globally visible.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
+
'alias-test-lib' does not exist![?]$
^'alias-test-exe' is an alias for 'test-exe' and its name-property contains 'test-exe'.
'alias-test-lib' is an alias for 'test-lib' and its name-property contains 'test-lib'.$

View File

@@ -0,0 +1,6 @@
add_executable(alias-test-exe-subdir1 ALIAS test-exe)
add_executable(alias-test-exe-local ALIAS test-exe)
add_library(alias-test-lib-subdir1 ALIAS test-lib)
add_library(alias-test-lib-local ALIAS test-lib)

View File

@@ -0,0 +1,20 @@
add_executable(alias-test-exe-subdir2 ALIAS test-exe)
add_executable(alias-test-exe-local ALIAS test-exe)
add_library(alias-test-lib-subdir2 ALIAS test-lib)
add_library(alias-test-lib-local ALIAS test-lib)
foreach (item IN ITEMS exe lib)
get_property (aliasedTarget TARGET alias-test-${item}-local PROPERTY ALIASED_TARGET)
if (NOT aliasedTarget STREQUAL "test-${item}")
message (SEND_ERROR "Wrong aliased target '${aliasedTarget}' for ALIAS 'alias-test-${item}-local'.")
endif()
endforeach()
foreach (item IN ITEMS exe lib)
if (TARGET alias-test-${item}-subdir1)
message (SEND_ERROR "ALIAS 'alias-test-${item}-subdir1' unexpectedly defined.")
endif()
endforeach()

View File

@@ -44,3 +44,14 @@ if(TARGET alias-test-lib)
else()
message("'alias-test-lib' does not exist!?")
endif()
add_subdirectory (imported-target-subdir1)
add_subdirectory (imported-target-subdir2)
foreach (alias IN ITEMS exe-local lib-local
exe-subdir1 lib-subdir1
exe-subdir2 lib-subdir2)
if (TARGET alias-test-${alias})
message (SEND_ERROR "ALIAS 'alias-test-${alias}' unexpectedly defined.")
endif()
endforeach()

View File

@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.16...3.17)
enable_language(C)
add_library (func SHARED func.c)
set (binary_dir "${CMAKE_BINARY_DIR}")
get_property (is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (is_multi_config)
string (APPEND binary_dir "/Release")
endif()
add_library(import-local SHARED IMPORTED)
set_property(TARGET import-local PROPERTY IMPORTED_LOCATION "${binary_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}func${CMAKE_SHARED_LIBRARY_SUFFIX}")
set_property(TARGET import-local PROPERTY IMPORTED_IMPLIB "${binary_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}func${CMAKE_IMPORT_LIBRARY_SUFFIX}")
add_library(alias-local ALIAS import-local)
add_library (lib-local SHARED lib.c)
target_link_libraries (lib-local PRIVATE import-local)
add_executable (main-local main.c)
target_link_libraries (main-local PRIVATE import-local)
add_library(import-global SHARED IMPORTED GLOBAL)
set_property(TARGET import-global PROPERTY IMPORTED_LOCATION "${binary_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}func${CMAKE_SHARED_LIBRARY_SUFFIX}")
set_property(TARGET import-global PROPERTY IMPORTED_IMPLIB "${binary_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}func${CMAKE_IMPORT_LIBRARY_SUFFIX}")
add_library(alias-global ALIAS import-global)
add_library (lib-global SHARED lib.c)
target_link_libraries (lib-global PRIVATE import-global)
add_executable (main-global main.c)
target_link_libraries (main-global PRIVATE import-global)

View File

@@ -30,6 +30,29 @@ run_cmake(StaticPrivateDepNotExported)
run_cmake(StaticPrivateDepNotTarget)
run_cmake(UNKNOWN-IMPORTED-GLOBAL)
run_cmake(empty_keyword_args)
macro(run_cmake_target test subtest target)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(${test}-${subtest} ${CMAKE_COMMAND} --build . --target ${target} ${ARGN})
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
endmacro()
set(RunCMake_TEST_OUTPUT_MERGE TRUE)
if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
endif()
run_cmake(AliasTargets)
run_cmake_target(AliasTargets func func --config Release)
run_cmake_target(AliasTargets lib-local lib-local --config Release)
run_cmake_target(AliasTargets main-local main-local --config Release)
run_cmake_target(AliasTargets lib-global lib-global --config Release)
run_cmake_target(AliasTargets main-global main-global --config Release)
unset(RunCMake_TEST_OPTIONS)
unset(RunCMake_TEST_OUTPUT_MERGE)
run_cmake(genex_LINK_LANGUAGE-bad-usage)
if (RunCMake_GENERATOR MATCHES "Makefiles|Ninja|Visual Studio|Xcode|Watcom WMake")
@@ -37,15 +60,6 @@ if (RunCMake_GENERATOR MATCHES "Makefiles|Ninja|Visual Studio|Xcode|Watcom WMake
run_cmake(genex_LINK_LANGUAGE-bad-mix-lang)
run_cmake(genex_LINK_LANG_AND_ID-bad-mix-lang)
macro(run_cmake_target test subtest target)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(${test}-${subtest} ${CMAKE_COMMAND} --build . --target ${target} ${ARGN})
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
endmacro()
set(RunCMake_TEST_OUTPUT_MERGE TRUE)
if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)