From da42e4ed20cc50937c6f8329f232b0eccd4d529d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Germain?= Date: Fri, 6 Mar 2026 18:43:09 -0800 Subject: [PATCH] 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. --- .../prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst | 4 ++ Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst | 4 ++ .../include/VERIFY_XXX_HEADER_SETS.rst | 15 ++++-- Help/release/dev/verify-header-sets-objc.rst | 6 +++ Source/cmGeneratorTarget.cxx | 48 ++++++++++++------- Tests/RunCMake/CMakeLists.txt | 2 +- .../VerifyHeaderSets/RunCMakeTest.cmake | 22 +++++++++ .../VerifyInterfaceHeaderSetsObjC.cmake | 12 +++++ .../VerifyPrivateHeaderSetsObjC.cmake | 12 +++++ Tests/RunCMake/VerifyHeaderSets/lang_test.h | 7 +++ Tests/RunCMake/VerifyHeaderSets/lib.m | 3 ++ Tests/RunCMake/VerifyHeaderSets/lib.mm | 3 ++ 12 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 Help/release/dev/verify-header-sets-objc.rst create mode 100644 Tests/RunCMake/VerifyHeaderSets/VerifyInterfaceHeaderSetsObjC.cmake create mode 100644 Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsObjC.cmake create mode 100644 Tests/RunCMake/VerifyHeaderSets/lib.m create mode 100644 Tests/RunCMake/VerifyHeaderSets/lib.mm diff --git a/Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst b/Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst index a14f7c8f23..0c7c70809b 100644 --- a/Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst +++ b/Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst @@ -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. diff --git a/Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst b/Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst index df9d18384d..c7f578ee42 100644 --- a/Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst +++ b/Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst @@ -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. diff --git a/Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst b/Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst index f1902136bd..71e34cd4c7 100644 --- a/Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst +++ b/Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst @@ -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. diff --git a/Help/release/dev/verify-header-sets-objc.rst b/Help/release/dev/verify-header-sets-objc.rst new file mode 100644 index 0000000000..4b53e88dbd --- /dev/null +++ b/Help/release/dev/verify-header-sets-objc.rst @@ -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``. diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 2ed3cabe59..1ef2d428d7 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -5907,19 +5907,21 @@ bool cmGeneratorTarget::AddHeaderSetVerification() // the same things so we pick up the same transitive // properties. For the _... 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 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 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 cmGeneratorTarget::ResolveHeaderLanguage( cmSourceFile& source, cm::optional>& defaultLanguage) const { - static std::array const supportedLangs{ { + static std::array 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 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 langs; @@ -6060,8 +6068,12 @@ cm::optional cmGeneratorTarget::ResolveHeaderLanguage( } cm::optional 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 cmGeneratorTarget::GenerateStubForLanguage( std::string const& language, std::string const& headerFilename, std::string const& verifyTargetName, cmSourceFile& source) const { - static std::array, 2> const + static std::array, 4> const langToExt = { { { "C", ".c" }, { "CXX", ".cxx" }, + { "OBJC", ".m" }, + { "OBJCXX", ".mm" }, } }; // NOLINTNEXTLINE(readability-qualified-auto) diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 6e1e596aeb..ccb9c8d3d3 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -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) diff --git a/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake b/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake index 61e4df7c2e..26fc8cb9b1 100644 --- a/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake +++ b/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake @@ -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) diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyInterfaceHeaderSetsObjC.cmake b/Tests/RunCMake/VerifyHeaderSets/VerifyInterfaceHeaderSetsObjC.cmake new file mode 100644 index 0000000000..4830d24d55 --- /dev/null +++ b/Tests/RunCMake/VerifyHeaderSets/VerifyInterfaceHeaderSetsObjC.cmake @@ -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) diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsObjC.cmake b/Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsObjC.cmake new file mode 100644 index 0000000000..53bfce2e89 --- /dev/null +++ b/Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsObjC.cmake @@ -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) diff --git a/Tests/RunCMake/VerifyHeaderSets/lang_test.h b/Tests/RunCMake/VerifyHeaderSets/lang_test.h index 633a2a4508..347a247228 100644 --- a/Tests/RunCMake/VerifyHeaderSets/lang_test.h +++ b/Tests/RunCMake/VerifyHeaderSets/lang_test.h @@ -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); diff --git a/Tests/RunCMake/VerifyHeaderSets/lib.m b/Tests/RunCMake/VerifyHeaderSets/lib.m new file mode 100644 index 0000000000..5952ed26df --- /dev/null +++ b/Tests/RunCMake/VerifyHeaderSets/lib.m @@ -0,0 +1,3 @@ +void lang_test_h(void) +{ +} diff --git a/Tests/RunCMake/VerifyHeaderSets/lib.mm b/Tests/RunCMake/VerifyHeaderSets/lib.mm new file mode 100644 index 0000000000..1119f87f44 --- /dev/null +++ b/Tests/RunCMake/VerifyHeaderSets/lib.mm @@ -0,0 +1,3 @@ +void lang_test_h_mm(void) +{ +}