Allow generator expressions in the EXCLUDE_FROM_ALL target property

This allows for setting EXCLUDE_FROM_ALL, conditional on the build
configuration. However, only the Ninja Multi-Config generator supports
different property values per config. All other multi-config
generators will yield an error in that situation.

Fixes: #20923
This commit is contained in:
Joerg Bornemann
2020-07-16 10:15:04 +02:00
parent c7b7547d8d
commit 2cdaf43d96
20 changed files with 168 additions and 14 deletions

View File

@@ -19,3 +19,10 @@ If a target has ``EXCLUDE_FROM_ALL`` set to true, it may still be listed
in an :command:`install(TARGETS)` command, but the user is responsible for
ensuring that the target's build artifacts are not missing or outdated when
an install is performed.
This property may use "generator expressions" with the syntax ``$<...>``. See
the :manual:`cmake-generator-expressions(7)` manual for available expressions.
Only the "Ninja Multi-Config" generator supports a property value that varies by
configuration. For all other generators the value of this property must be the
same for all configurations.

View File

@@ -5,8 +5,12 @@
#include <memory>
#include <utility>
#include <cmext/algorithm>
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmProperty.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
@@ -31,6 +35,8 @@ cmGlobalCommonGenerator::ComputeDirectoryTargets() const
lg->GetStateSnapshot().GetDirectory().GetCurrentBinary());
DirectoryTarget& dirTarget = dirTargets[currentBinaryDir];
dirTarget.LG = lg.get();
const std::vector<std::string>& configs =
lg->GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
// The directory-level rule should depend on the target-level rules
// for all targets in the directory.
@@ -46,11 +52,18 @@ cmGlobalCommonGenerator::ComputeDirectoryTargets() const
}
DirectoryTarget::Target t;
t.GT = gt.get();
if (cmProp exclude = gt->GetProperty("EXCLUDE_FROM_ALL")) {
if (cmIsOn(*exclude)) {
// This target has been explicitly excluded.
t.ExcludeFromAll = true;
} else {
const std::string EXCLUDE_FROM_ALL("EXCLUDE_FROM_ALL");
if (cmProp exclude = gt->GetProperty(EXCLUDE_FROM_ALL)) {
for (const std::string& config : configs) {
cmGeneratorExpressionInterpreter genexInterpreter(lg.get(), config,
gt.get());
if (cmIsOn(genexInterpreter.Evaluate(*exclude, EXCLUDE_FROM_ALL))) {
// This target has been explicitly excluded.
t.ExcludedFromAllInConfigs.push_back(config);
}
}
if (t.ExcludedFromAllInConfigs.empty()) {
// This target has been explicitly un-excluded. The directory-level
// rule for every directory between this and the root should depend
// on the target-level rule for this target.
@@ -78,3 +91,12 @@ cmGlobalCommonGenerator::ComputeDirectoryTargets() const
return dirTargets;
}
bool cmGlobalCommonGenerator::IsExcludedFromAllInConfig(
const DirectoryTarget::Target& t, const std::string& config)
{
if (this->IsMultiConfig()) {
return cm::contains(t.ExcludedFromAllInConfigs, config);
}
return !t.ExcludedFromAllInConfigs.empty();
}

View File

@@ -30,7 +30,7 @@ public:
struct Target
{
cmGeneratorTarget const* GT = nullptr;
bool ExcludeFromAll = false;
std::vector<std::string> ExcludedFromAllInConfigs;
};
std::vector<Target> Targets;
struct Dir
@@ -41,6 +41,8 @@ public:
std::vector<Dir> Children;
};
std::map<std::string, DirectoryTarget> ComputeDirectoryTargets() const;
bool IsExcludedFromAllInConfig(const DirectoryTarget::Target& t,
const std::string& config);
};
#endif

View File

@@ -2173,13 +2173,38 @@ bool cmGlobalGenerator::IsExcluded(cmLocalGenerator* root,
}
bool cmGlobalGenerator::IsExcluded(cmLocalGenerator* root,
cmGeneratorTarget* target) const
const cmGeneratorTarget* target) const
{
if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
return true;
}
if (cmProp exclude = target->GetProperty("EXCLUDE_FROM_ALL")) {
return cmIsOn(*exclude);
cmMakefile* mf = root->GetMakefile();
const std::string EXCLUDE_FROM_ALL = "EXCLUDE_FROM_ALL";
if (cmProp exclude = target->GetProperty(EXCLUDE_FROM_ALL)) {
// Expand the property value per configuration.
unsigned int trueCount = 0;
unsigned int falseCount = 0;
const std::vector<std::string>& configs =
mf->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
for (const std::string& config : configs) {
cmGeneratorExpressionInterpreter genexInterpreter(root, config, target);
if (cmIsOn(genexInterpreter.Evaluate(*exclude, EXCLUDE_FROM_ALL))) {
++trueCount;
} else {
++falseCount;
}
}
// Check whether the genex expansion of the property agrees in all
// configurations.
if (trueCount && falseCount) {
std::ostringstream e;
e << "The EXCLUDED_FROM_ALL property of target \"" << target->GetName()
<< "\" varies by configuration. This is not supported by the \""
<< root->GetGlobalGenerator()->GetName() << "\" generator.";
mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
}
return trueCount;
}
// This target is included in its directory. Check whether the
// directory is excluded.

View File

@@ -542,7 +542,8 @@ protected:
bool IsExcluded(cmStateSnapshot const& root,
cmStateSnapshot const& snp) const;
bool IsExcluded(cmLocalGenerator* root, cmLocalGenerator* gen) const;
bool IsExcluded(cmLocalGenerator* root, cmGeneratorTarget* target) const;
bool IsExcluded(cmLocalGenerator* root,
const cmGeneratorTarget* target) const;
virtual void InitializeProgressMarks() {}
struct GlobalTargetInfo

View File

@@ -470,7 +470,7 @@ void cmGlobalGhsMultiGenerator::WriteAllTarget(
if (t->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
continue;
}
if (!cmIsOn(t->GetProperty("EXCLUDE_FROM_ALL"))) {
if (!IsExcluded(t->GetLocalGenerator(), t)) {
defaultTargets.push_back(t);
}
}

View File

@@ -1357,7 +1357,7 @@ void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
configDeps.emplace_back(build.Outputs.front());
for (DirectoryTarget::Target const& t : dt.Targets) {
if (!t.ExcludeFromAll) {
if (!IsExcludedFromAllInConfig(t, config)) {
this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config);
}
}

View File

@@ -416,7 +416,7 @@ void cmGlobalUnixMakefileGenerator3::WriteDirectoryRule2(
std::vector<std::string> depends;
for (DirectoryTarget::Target const& t : dt.Targets) {
// Add this to the list of depends rules in this directory.
if ((!check_all || !t.ExcludeFromAll) &&
if ((!check_all || t.ExcludedFromAllInConfigs.empty()) &&
(!check_relink ||
t.GT->NeedRelinkBeforeInstall(lg->GetConfigName()))) {
// The target may be from a different directory; use its local gen.
@@ -846,7 +846,7 @@ void cmGlobalUnixMakefileGenerator3::InitializeProgressMarks()
cmLocalGenerator* tlg = gt->GetLocalGenerator();
if (gt->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
gt->GetPropertyAsBool("EXCLUDE_FROM_ALL")) {
IsExcluded(lg.get(), gt.get())) {
continue;
}

View File

@@ -207,6 +207,7 @@ add_RunCMake_test(DisallowedCommands)
if("${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")
add_RunCMake_test(ExportCompileCommands)
endif()
add_RunCMake_test(ExcludeFromAll)
add_RunCMake_test(ExternalData)
add_RunCMake_test(FeatureSummary)
add_RunCMake_test(FPHSA)

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.3)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@@ -0,0 +1,26 @@
include(RunCMake)
function(run_single_config_test label config exclude_from_all_value expectation)
set(case single-config)
message("-- Starting ${case} test: ${label}")
set(full_case_name "${case}-build-${config}")
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${full_case_name}/")
run_cmake_with_options(${case}
-DCMAKE_BUILD_TYPE=${config}
-DTOOL_EXCLUDE_FROM_ALL=${exclude_from_all_value})
set(RunCMake_TEST_NO_CLEAN 1)
include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config ${config})
endfunction()
run_single_config_test("explictly not excluded" Debug 0 "should_exist")
run_single_config_test("excluded" Debug 1 "should_not_exist")
if(RunCMake_GENERATOR MATCHES "^(Xcode|Visual Studio)")
run_cmake(error-on-mixed-config)
else()
run_single_config_test("explicitly not excluded with genex"
Release $<CONFIG:Debug> "should_exist")
run_single_config_test("excluded with genex"
Debug $<CONFIG:Debug> "should_not_exist")
endif()

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,3 @@
CMake Error in CMakeLists.txt:
The EXCLUDED_FROM_ALL property of target "release_only_tool" varies by
configuration. This is not supported by the "[^"]+"

View File

@@ -0,0 +1,6 @@
enable_language(C)
set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "")
add_executable(release_only_tool main.c)
set_property(TARGET release_only_tool PROPERTY EXCLUDE_FROM_ALL "$<NOT:$<CONFIG:Release>>")

View File

@@ -0,0 +1,3 @@
int main()
{
}

View File

@@ -0,0 +1,17 @@
if(expectation STREQUAL "should_not_exist")
set(should_exist FALSE)
elseif(expectation STREQUAL "should_exist")
set(should_exist TRUE)
else()
message(FATAL_ERROR "Encountered unknown expectation: ${expectation}")
endif()
if(EXISTS "${TARGET_FILE_tool_${config}}")
if(NOT should_exist)
message(FATAL_ERROR "${TARGET_FILE_tool_${config}} should not exist.")
endif()
else()
if(should_exist)
message(FATAL_ERROR "${TARGET_FILE_tool_${config}} should exist.")
endif()
endif()

View File

@@ -0,0 +1,11 @@
enable_language(C)
add_executable(tool main.c)
set_property(TARGET tool PROPERTY EXCLUDE_FROM_ALL "${TOOL_EXCLUDE_FROM_ALL}")
include(../NinjaMultiConfig/Common.cmake)
set(orig_CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES})
if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "")
set(CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE})
endif()
generate_output_files(tool)
set(CMAKE_CONFIGURATION_TYPES ${orig_CMAKE_CONFIGURATION_TYPES})

View File

@@ -0,0 +1,9 @@
check_files("${RunCMake_TEST_BINARY_DIR}"
INCLUDE
${TARGET_FILE_release_only_tool_Release}
${TARGET_EXE_FILE_release_only_tool_Release}
EXCLUDE
${TARGET_FILE_release_only_tool_Debug}
${TARGET_EXE_FILE_release_only_tool_Debug}
)

View File

@@ -0,0 +1,12 @@
enable_language(C)
set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "")
set(CMAKE_DEFAULT_BUILD_TYPE "Release" CACHE STRING "")
set(CMAKE_CROSS_CONFIGS "all" CACHE STRING "")
set(CMAKE_DEFAULT_CONFIGS "all" CACHE STRING "")
add_executable(release_only_tool main.c)
set_property(TARGET release_only_tool PROPERTY EXCLUDE_FROM_ALL "$<NOT:$<CONFIG:Release>>")
include(${CMAKE_CURRENT_LIST_DIR}/Common.cmake)
generate_output_files(release_only_tool)

View File

@@ -274,6 +274,11 @@ run_ninja(Install default-install build.ninja install)
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}/install")
run_ninja(Install all-install build.ninja install:all)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ExcludeFromAll-build)
run_cmake_configure(ExcludeFromAll)
include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
run_cmake_build(ExcludeFromAll all "" all:all)
# FIXME Get this working
#set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/AutoMocExecutable-build)
#run_cmake_configure(AutoMocExecutable)