cmGeneratorTarget: Add OBJC/OBJCXX support to header set verification

Extend header set verification to support Objective-C and Objective-C++.
The supported-language table grows to {"C", "CXX", "OBJC", "OBJCXX"}
and the lattice join is updated so OBJCXX is chosen when both CXX and
OBJC sources are present. GenerateStubForLanguage emits .m/.mm stubs
and the relevant OBJC/OBJCXX standard properties are copied to the
verification target.

Add OBJC/OBJCXX test cases conditioned on CMake_TEST_OBJC, isolated in
dedicated project files to avoid affecting language inference for
unrelated targets. Update property documentation and add a release note.
This commit is contained in:
Mickaël Germain authored and Brad King committed 2026-03-10 09:22:28 -04:00
1 parent f974ef0f75
commit da42e4ed20
12 files changed
+115 -23

No files matched your search

@@ -3,6 +3,10 @@ VERIFY_INTERFACE_HEADER_SETS
.. versionadded:: 3.24
.. versionchanged:: 4.4
``OBJC`` and ``OBJCXX`` languages are now supported in addition to ``C``
and ``CXX``.
Used to verify that all headers in a target's ``PUBLIC`` and ``INTERFACE``
header sets can be included on their own.
@@ -3,6 +3,10 @@ VERIFY_PRIVATE_HEADER_SETS
.. versionadded:: 4.3
.. versionchanged:: 4.4
``OBJC`` and ``OBJCXX`` languages are now supported in addition to ``C``
and ``CXX``.
Used to verify that all headers in a target's ``PUBLIC`` and ``PRIVATE``
header sets can be included on their own.
@@ -6,11 +6,16 @@ properties set to true, and its :prop_tgt:`AUTOMOC`, :prop_tgt:`AUTORCC`,
If the header's :prop_sf:`LANGUAGE` property is set, the value of that property
is used to determine the language with which to compile the header file.
Otherwise, if the target has any C++ sources, the header is compiled as C++.
Otherwise, if the target has any C sources, the header is compiled as C.
Otherwise, if C++ is enabled globally, the header is compiled as C++.
Otherwise, if C is enabled globally, the header is compiled as C. Otherwise,
the header file is not compiled.
The supported languages are ``C``, ``CXX``, ``OBJC``, and ``OBJCXX``.
If the language is not one of those four, the header file is not compiled.
Otherwise, the language is inferred from the target's sources using the
following precedence: ``OBJCXX`` takes priority over all others; if both
``CXX`` and ``OBJC`` sources are present, the header is compiled as
``OBJCXX``; otherwise ``CXX`` takes precedence over ``OBJC``, and ``OBJC``
takes precedence over ``C``. If no matching sources are found, the same
precedence is applied to the globally enabled languages. If no supported
language is found, the header file is not compiled.
If the header's :prop_sf:`SKIP_LINTING` property is set to true, the file is
not compiled.
@@ -0,0 +1,6 @@
verify-header-sets-objc
-----------------------
* The :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` and
:prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` target properties now support
``OBJC`` and ``OBJCXX`` languages in addition to ``C`` and ``CXX``.
+31 -17
View File
@@ -5907,19 +5907,21 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
// the same things so we pick up the same transitive
// properties. For the <LANG>_... properties, we don't care if
// we set them for languages this target won't eventually use.
// The verify header sets feature currently only supports the
// C and C++ languages, so we just always set those here for
// simplicity rather than working out all languages the target
// has to compile for.
static std::vector<std::string> propertiesToCopy = {
"COMPILE_DEFINITIONS", "COMPILE_FEATURES",
"COMPILE_FLAGS", "COMPILE_OPTIONS",
"DEFINE_SYMBOL", "INCLUDE_DIRECTORIES",
"LINK_LIBRARIES", "C_STANDARD",
"C_STANDARD_REQUIRED", "C_EXTENSIONS",
"CXX_STANDARD", "CXX_STANDARD_REQUIRED",
"CXX_EXTENSIONS"
};
// Copy language-standard properties for all supported
// languages. We don't care if we set properties for languages
// this target won't eventually use.
static std::array<std::string, 19> const propertiesToCopy{ {
"COMPILE_DEFINITIONS", "COMPILE_FEATURES",
"COMPILE_FLAGS", "COMPILE_OPTIONS",
"DEFINE_SYMBOL", "INCLUDE_DIRECTORIES",
"LINK_LIBRARIES", "C_STANDARD",
"C_STANDARD_REQUIRED", "C_EXTENSIONS",
"CXX_STANDARD", "CXX_STANDARD_REQUIRED",
"CXX_EXTENSIONS", "OBJC_STANDARD",
"OBJC_STANDARD_REQUIRED", "OBJC_EXTENSIONS",
"OBJCXX_STANDARD", "OBJCXX_STANDARD_REQUIRED",
"OBJCXX_EXTENSIONS",
} };
for (std::string const& prop : propertiesToCopy) {
cmValue propValue = this->Target->GetProperty(prop);
if (propValue.IsSet()) {
@@ -6018,9 +6020,11 @@ cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
cmSourceFile& source,
cm::optional<cm::optional<std::string>>& defaultLanguage) const
{
static std::array<cm::string_view, 2> const supportedLangs{ {
static std::array<cm::string_view, 4> const supportedLangs{ {
"C",
"CXX",
"OBJC",
"OBJCXX",
} };
auto isSupported = [](cm::string_view lang) -> bool {
return std::find(supportedLangs.begin(), supportedLangs.end(), lang) !=
@@ -6039,7 +6043,11 @@ cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
/*
Compute and cache the default language for unlanguaged headers.
The lattice join is run once per file set, not once per header.
Lattice: CXX > C
Lattice: OBJCXX
/ \
CXX OBJC
\ /
C
*/
if (!defaultLanguage) {
std::set<std::string> langs;
@@ -6060,8 +6068,12 @@ cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
}
cm::optional<std::string> resolved;
if (langs.count("CXX")) {
if (langs.count("OBJCXX") || (langs.count("CXX") && langs.count("OBJC"))) {
resolved = "OBJCXX"; // promote
} else if (langs.count("CXX")) {
resolved = "CXX";
} else if (langs.count("OBJC")) {
resolved = "OBJC";
} else if (langs.count("C")) {
resolved = "C";
}
@@ -6075,10 +6087,12 @@ cm::optional<std::string> cmGeneratorTarget::GenerateStubForLanguage(
std::string const& language, std::string const& headerFilename,
std::string const& verifyTargetName, cmSourceFile& source) const
{
static std::array<std::pair<cm::string_view, cm::string_view>, 2> const
static std::array<std::pair<cm::string_view, cm::string_view>, 4> const
langToExt = { {
{ "C", ".c" },
{ "CXX", ".cxx" },
{ "OBJC", ".m" },
{ "OBJCXX", ".mm" },
} };
// NOLINTNEXTLINE(readability-qualified-auto)
+1 -1
View File
@@ -1500,7 +1500,7 @@ add_RunCMake_test(CMakePresetsWorkflow
-DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
)
add_RunCMake_test(VerifyHeaderSets)
add_RunCMake_test(VerifyHeaderSets -DCMake_TEST_OBJC=${CMake_TEST_OBJC})
add_RunCMake_test(set_tests_properties)
add_RunCMake_test(CMakeListFileName)
@@ -46,6 +46,17 @@ run_cmake_build(VerifyInterfaceHeaderSets interface iface_lang_cxx)
run_cmake_build(VerifyInterfaceHeaderSets interface list)
run_cmake_build(VerifyInterfaceHeaderSets interface skip_linting)
if(CMake_TEST_OBJC)
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON)
if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
endif()
run_cmake(VerifyInterfaceHeaderSetsObjC)
unset(RunCMake_TEST_OPTIONS)
run_cmake_build(VerifyInterfaceHeaderSetsObjC interface lang_test_objc)
run_cmake_build(VerifyInterfaceHeaderSetsObjC interface lang_test_objcxx)
endif()
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON)
run_cmake(AllVerifyInterfaceHeaderSets)
unset(RunCMake_TEST_OPTIONS)
@@ -94,6 +105,17 @@ run_cmake_build(VerifyPrivateHeaderSets private iface_lang_cxx)
run_cmake_build(VerifyPrivateHeaderSets private list)
run_cmake_build(VerifyPrivateHeaderSets private skip_linting)
if(CMake_TEST_OBJC)
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
endif()
run_cmake(VerifyPrivateHeaderSetsObjC)
unset(RunCMake_TEST_OPTIONS)
run_cmake_build(VerifyPrivateHeaderSetsObjC private lang_test_objc)
run_cmake_build(VerifyPrivateHeaderSetsObjC private lang_test_objcxx)
endif()
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
run_cmake(AllVerifyPrivateHeaderSets)
unset(RunCMake_TEST_OPTIONS)
@@ -0,0 +1,12 @@
cmake_policy(SET CMP0209 NEW)
enable_language(C CXX OBJC OBJCXX)
add_library(lang_test_objc STATIC lib.m)
target_sources(lang_test_objc INTERFACE FILE_SET HEADERS FILES lang_test.h)
target_compile_definitions(lang_test_objc INTERFACE EXPECT_OBJC)
# OBJC + OBJCXX sources -> lattice promotes to OBJCXX for unlanguaged headers
add_library(lang_test_objcxx STATIC lib.m lib.mm)
target_sources(lang_test_objcxx INTERFACE FILE_SET HEADERS FILES lang_test.h)
target_compile_definitions(lang_test_objcxx INTERFACE EXPECT_CXX EXPECT_OBJC)
@@ -0,0 +1,12 @@
cmake_policy(SET CMP0209 NEW)
enable_language(C CXX OBJC OBJCXX)
add_library(lang_test_objc STATIC lib.m)
target_sources(lang_test_objc PRIVATE FILE_SET HEADERS FILES lang_test.h)
target_compile_definitions(lang_test_objc PRIVATE EXPECT_OBJC)
# OBJC + OBJCXX sources -> lattice promotes to OBJCXX for unlanguaged headers
add_library(lang_test_objcxx STATIC lib.m lib.mm)
target_sources(lang_test_objcxx PRIVATE FILE_SET HEADERS FILES lang_test.h)
target_compile_definitions(lang_test_objcxx PRIVATE EXPECT_CXX EXPECT_OBJC)
@@ -5,4 +5,11 @@
# error "__cplusplus not defined but EXPECT_CXX defined"
#endif
#if defined(__OBJC__) && !defined(EXPECT_OBJC)
# error "__OBJC__ defined but EXPECT_OBJC not defined"
#endif
#if !defined(__OBJC__) && defined(EXPECT_OBJC)
# error "__OBJC__ not defined but EXPECT_OBJC defined"
#endif
extern void lang_test_h(void);
+3
View File
@@ -0,0 +1,3 @@
void lang_test_h(void)
{
}
+3
View File
@@ -0,0 +1,3 @@
void lang_test_h_mm(void)
{
}