Repository: conan-io/conan
Pull request: Removed ObjC/C++ flags from C/C++ ones
Changelog: Bugfix: Redirected Apple ARC flags to the ObjC/C++ ones.
Docs: omit
Closes: https://github.com/conan-io/conan/issues/18467


Problem statement:
[bug] ObjC ARC compile flag isn't passed to objc files when building with CMake+Ninja
### Describe the bug

I'm trying to build an ObjC library that requires ARC with CMake+Ninja. Adding `tools.apple:enable_arc=True` to the `[conf]` section doesn't help - the `-fobjc-arc` flag isn't passed when compiling objc files. In the generated toolchain file I see that the flag is set only for C and C++ files, but ARC actually apples only to objc and objc++ files:

```cmake
# ARC ON
set(FOBJC_ARC "-fobjc-arc")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES")
#Check if Xcode generator is used, since that will handle these flags automagically
if(CMAKE_GENERATOR MATCHES "Xcode")
  message(DEBUG "Not setting any manual command-line buildflags, since Xcode is selected as generator.")
else()
    string(APPEND CONAN_C_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
    string(APPEND CONAN_CXX_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
endif()
```

One workaround is:
```py
tc.variables["CMAKE_OBJC_FLAGS"] = "-fobjc-arc"
```

env:
macOS 13
Python 3.9
Conan 2.17
Xcode 15.2 (Apple-clang 15)
CMake 4.0.2

### How to reproduce it

1. have a CMake project that contains objc files requiring ARC
2. set `tools.apple:enable_arc=True` conf
3. try to build it with Ninja (non-Xcode actually)
4. observe errors like:

```
FAILED: CMakeFiles/CocoaLumberjack.dir/src/Sources/CocoaLumberjack/DDFileLogger.m.o 
/usr/bin/clang  -I/Users/kambala/.conan2/p/b/cocoa2181430922f78/b/src/Sources/CocoaLumberjack/include -x objective-c -O3 -DNDEBUG -std=gnu17 -arch arm64 -isysroot /Applications/Xcode-15.2.0.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.2.sdk -miphoneos-version-min=12.0 -fPIC -MD -MT CMakeFiles/CocoaLumberjack.dir/src/Sources/CocoaLumberjack/DDFileLogger.m.o -MF CMakeFiles/CocoaLumberjack.dir/src/Sources/CocoaLumberjack/DDFileLogger.m.o.d -o CMakeFiles/CocoaLumberjack.dir/src/Sources/CocoaLumberjack/DDFileLogger.m.o -c /Users/kambala/.conan2/p/b/cocoa2181430922f78/b/src/Sources/CocoaLumberjack/DDFileLogger.m
/Users/kambala/.conan2/p/b/cocoa2181430922f78/b/src/Sources/CocoaLumberjack/DDFileLogger.m:17:2: error: This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
 ^
/Users/kambala/.conan2/p/b/cocoa2181430922f78/b/src/Sources/CocoaLumberjack/DDFileLogger.m:705:1: warning: method possibly missing a [super dealloc] call [-Wobjc-missing-super-calls]
}
^
/Users/kambala/.conan2/p/b/cocoa2181430922f78/b/src/Sources/CocoaLumberjack/DDFileLogger.m:844:5: error: cannot create __weak reference in file using manual reference counting
    __weak __auto_type weakSelf = self;
    ^
```

P.S. Gave the whole ARC thing a second thought. I think Conan handles it completely incorrectly: it's a project "property" that is configured on project / target / individual file level, not something a user / consumer can adjust to his liking (unlike visibility or bitcode). So it must be specified on build system level, as incorrect configuration will result in either build errors (like above) or memory leaks at runtime. I strongly recommend to remove ARC handling from Conan: absolute majority of Apple projects use ARC and don't allow selecting build "mode" by consumer.

Hints from issue discussion:
Hi @kambala-decapitator 

> tc.variables["CMAKE_OBJC_FLAGS"] = "-fobjc-arc"

As a quick workaround, recall that you can inject CMake variables directly from command line or profiles, without modifying recipes with ``tools.cmake.cmaketoolchain:extra_variables`` conf. 


> P.S. Gave the whole ARC thing a second thought. I think Conan handles it completely incorrectly: it's a project "property" that is configured on project / target / individual file level, not something a user / consumer can adjust to his liking (unlike visibility or bitcode). So it must be specified on build system level, as incorrect configuration will result in either build errors (like above) or memory leaks at runtime. I strongly recommend to remove ARC handling from Conan: absolute majority of Apple projects use ARC and don't allow selecting build "mode" by consumer.

The truth that is that we didn't push this. It was pushed by users wanting this configuration.
So maybe we need to review the history and summon those users to understand their use cases in retrospective with this information.

@memsharded thanks

> As a quick workaround, recall that you can inject CMake variables directly from command line or profiles, without modifying recipes with tools.cmake.cmaketoolchain:extra_variables conf.

yes, you're absolutely right. But since it's my own recipe, I could do it right there, although in the end I specified the flag right inside CML.

> The truth that is that we didn't push this. It was pushed by users wanting this configuration.
> So maybe we need to review the history and summon those users to understand their use cases in retrospective with this information.

do you want me to open a separate issue about this? As this one is basically about a bug in existing functionality that may affect someone.
> do you want me to open a separate issue about this? As this one is basically about a bug in existing functionality that may affect someone.

No, I think we can cite those other people in this ticket, we just need to check history to check about who to cite.

Commit message: wip

Patch to review:
diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py
index 8afda1ad23a..0e3d7efcebd 100644
--- a/conan/tools/apple/apple.py
+++ b/conan/tools/apple/apple.py
@@ -334,20 +334,14 @@ def apple_extra_flags(conanfile):
     if not is_apple_os(conanfile):
         return []
     enable_bitcode = conanfile.conf.get("tools.apple:enable_bitcode", check_type=bool)
-    enable_arc = conanfile.conf.get("tools.apple:enable_arc", check_type=bool)
     enable_visibility = conanfile.conf.get("tools.apple:enable_visibility", check_type=bool)
     is_debug = conanfile.settings.get_safe('build_type') == "Debug"
-
     flags = []
     if enable_bitcode:
         if is_debug:
             flags.append("-fembed-bitcode-marker")
         else:
             flags.append("-fembed-bitcode")
-    if enable_arc:
-        flags.append("-fobjc-arc")
-    if enable_arc is False:
-        flags.append("-fno-objc-arc")
     if enable_visibility:
         flags.append("-fvisibility=default")
     if enable_visibility is False:
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 951aa2662bb..2df118716f6 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -464,8 +464,11 @@ class AppleSystemBlock(Block):
         if(CMAKE_GENERATOR MATCHES "Xcode")
           message(DEBUG "Not setting any manual command-line buildflags, since Xcode is selected as generator.")
         else()
-            string(APPEND CONAN_C_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
-            string(APPEND CONAN_CXX_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
+            string(APPEND CONAN_C_FLAGS " ${BITCODE} ${VISIBILITY}")
+            string(APPEND CONAN_CXX_FLAGS " ${BITCODE} ${VISIBILITY}")
+            # Objective-C/C++ specific flags
+            string(APPEND CONAN_OBJC_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
+            string(APPEND CONAN_OBJCXX_FLAGS " ${BITCODE} ${VISIBILITY} ${FOBJC_ARC}")
         endif()
         """)
 
@@ -852,6 +855,12 @@ class CMakeFlagsInitBlock(Block):
         if(DEFINED CONAN_EXE_LINKER_FLAGS)
           string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${CONAN_EXE_LINKER_FLAGS}")
         endif()
+        if(DEFINED CONAN_OBJCXX_FLAGS)
+          string(APPEND CMAKE_OBJCXX_FLAGS_INIT " ${CONAN_OBJCXX_FLAGS}")
+        endif()
+        if(DEFINED CONAN_OBJC_FLAGS)
+          string(APPEND CMAKE_OBJC_FLAGS_INIT " ${CONAN_OBJC_FLAGS}")
+        endif()
         """)
 
 
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index 5014c134871..5c2ff6cc0de 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -403,11 +403,18 @@ def _resolve_apple_flags_and_variables(self, build_env, compilers_by_conf):
         self.apple_isysroot_flag = isysroot_flag.split() if isysroot_flag else []
         self.apple_min_version_flag = [apple_min_version_flag(self._conanfile)]
         # Objective C/C++ ones
+        flags = []
         self.objc = compilers_by_conf.get("objc", "clang")
         self.objcpp = compilers_by_conf.get("objcpp", "clang++")
-        self.objc_args = self._get_env_list(build_env.get('OBJCFLAGS', []))
+        enable_arc = self._conanfile.conf.get("tools.apple:enable_arc", check_type=bool)
+        fobj_arc = ""
+        if enable_arc:
+            fobj_arc = "-fobjc-arc"
+        if enable_arc is False:
+            fobj_arc = "-fno-objc-arc"
+        self.objc_args = self._get_env_list(build_env.get('OBJCFLAGS', [fobj_arc]))
         self.objc_link_args = self._get_env_list(build_env.get('LDFLAGS', []))
-        self.objcpp_args = self._get_env_list(build_env.get('OBJCXXFLAGS', []))
+        self.objcpp_args = self._get_env_list(build_env.get('OBJCXXFLAGS', [fobj_arc]))
         self.objcpp_link_args = self._get_env_list(build_env.get('LDFLAGS', []))
 
     def _resolve_android_cross_compilation(self):
@@ -474,6 +481,7 @@ def _sanitize_env_format(value):
         ret = [x.strip() for x in value.split() if x]
         return ret[0] if len(ret) == 1 else ret
 
+    @property
     def _context(self):
         apple_flags = self.apple_isysroot_flag + self.apple_arch_flag + self.apple_min_version_flag
         extra_flags = self._get_extra_flags()
@@ -483,8 +491,8 @@ def _context(self):
         self.c_link_args.extend(apple_flags + extra_flags["ldflags"])
         self.cpp_link_args.extend(apple_flags + extra_flags["ldflags"])
         # Objective C/C++
-        self.objc_args.extend(self.c_args + extra_flags["defines"])
-        self.objcpp_args.extend(self.cpp_args + extra_flags["defines"])
+        self.objc_args.extend(self.c_args)
+        self.objcpp_args.extend(self.cpp_args)
         # These link_args have already the LDFLAGS env value so let's add only the new possible ones
         self.objc_link_args.extend(apple_flags + extra_flags["ldflags"])
         self.objcpp_link_args.extend(apple_flags + extra_flags["ldflags"])
@@ -502,7 +510,6 @@ def _context(self):
                     raise ConanException("MesonToolchain.subproject_options must be a list of dicts")
                 subproject_options[subproject] = [{k: to_meson_value(v) for k, v in keypair.items()}
                                                   for keypair in listkeypair]
-
         return {
             # https://mesonbuild.com/Machine-files.html#properties
             "properties": {k: to_meson_value(v) for k, v in self.properties.items()},
@@ -567,7 +574,7 @@ def _content(self):
 
         :return: ``str`` whole Meson context content.
         """
-        context = self._context()
+        context = self._context
         content = Template(self._meson_file_template, trim_blocks=True, lstrip_blocks=True,
                            undefined=StrictUndefined).render(context)
         return content
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py b/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
index 0cfc20661eb..63fe747f947 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
@@ -52,8 +52,10 @@ def test_cmake_apple_bitcode_arc_and_visibility_flags_enabled(op_system, os_vers
 
     client.run("create . --profile:build=default --profile:host=host -tf=")
     # flags
-    assert "-- CONAN_C_FLAGS:  -fembed-bitcode -fvisibility=default -fobjc-arc" in client.out
-    assert "-- CONAN_CXX_FLAGS:  -fembed-bitcode -fvisibility=default -fobjc-arc" in client.out
+    assert "-- CONAN_C_FLAGS:  -fembed-bitcode -fvisibility=default" in client.out
+    assert "-- CONAN_CXX_FLAGS:  -fembed-bitcode -fvisibility=default" in client.out
+    assert "-- CONAN_OBJC_FLAGS:  -fembed-bitcode -fvisibility=default -fobjc-arc" in client.out
+    assert "-- CONAN_OBJCXX_FLAGS:  -fembed-bitcode -fvisibility=default -fobjc-arc" in client.out
     assert "[100%] Built target hello" in client.out
 
 
@@ -136,8 +138,10 @@ def test_cmake_apple_bitcode_arc_and_visibility_flags_disabled(op_system, os_ver
 
     client.run("create . --profile:build=default --profile:host=host -tf=\"\"")
     # flags
-    assert "-- CONAN_C_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden -fno-objc-arc" in client.out
-    assert "-- CONAN_CXX_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden -fno-objc-arc" in client.out
+    assert "-- CONAN_C_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden" in client.out
+    assert "-- CONAN_CXX_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden" in client.out
+    assert "-- CONAN_OBJC_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden -fno-objc-arc" in client.out
+    assert "-- CONAN_OBJCXX_FLAGS:   -fvisibility=hidden -fvisibility-inlines-hidden -fno-objc-arc" in client.out
     assert "[100%] Built target hello" in client.out
 
 
diff --git a/test/integration/toolchains/gnu/test_gnutoolchain.py b/test/integration/toolchains/gnu/test_gnutoolchain.py
index e0575491630..891e5619097 100644
--- a/test/integration/toolchains/gnu/test_gnutoolchain.py
+++ b/test/integration/toolchains/gnu/test_gnutoolchain.py
@@ -476,15 +476,15 @@ def test_conf_extra_apple_flags(toolchain):
     c.run("install . -pr:a host")
     f = "conanautotoolstoolchain.sh" if toolchain == "AutotoolsToolchain" else "conangnutoolchain.sh"
     tc = c.load(f)
-    assert 'export CXXFLAGS="$CXXFLAGS -fembed-bitcode -fobjc-arc -fvisibility=default"' in tc
-    assert 'export CFLAGS="$CFLAGS -fembed-bitcode -fobjc-arc -fvisibility=default"' in tc
-    assert 'export LDFLAGS="$LDFLAGS -fembed-bitcode -fobjc-arc -fvisibility=default"' in tc
+    assert 'export CXXFLAGS="$CXXFLAGS -fembed-bitcode -fvisibility=default"' in tc
+    assert 'export CFLAGS="$CFLAGS -fembed-bitcode -fvisibility=default"' in tc
+    assert 'export LDFLAGS="$LDFLAGS -fembed-bitcode -fvisibility=default"' in tc
 
     c.run("install . -pr:a host -s build_type=Debug")
     tc = c.load(f)
-    assert 'export CXXFLAGS="$CXXFLAGS -fembed-bitcode-marker -fobjc-arc -fvisibility=default"' in tc
-    assert 'export CFLAGS="$CFLAGS -fembed-bitcode-marker -fobjc-arc -fvisibility=default"' in tc
-    assert 'export LDFLAGS="$LDFLAGS -fembed-bitcode-marker -fobjc-arc -fvisibility=default"' in tc
+    assert 'export CXXFLAGS="$CXXFLAGS -fembed-bitcode-marker -fvisibility=default"' in tc
+    assert 'export CFLAGS="$CFLAGS -fembed-bitcode-marker -fvisibility=default"' in tc
+    assert 'export LDFLAGS="$LDFLAGS -fembed-bitcode-marker -fvisibility=default"' in tc
 
     host = textwrap.dedent("""
         [settings]
@@ -499,9 +499,9 @@ def test_conf_extra_apple_flags(toolchain):
     c.save({"host": host})
     c.run("install . -pr:a host")
     tc = c.load(f)
-    assert 'CXXFLAGS="$CXXFLAGS -fno-objc-arc -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
-    assert 'CFLAGS="$CFLAGS -fno-objc-arc -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
-    assert 'LDFLAGS="$LDFLAGS -fno-objc-arc -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
+    assert 'CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
+    assert 'CFLAGS="$CFLAGS -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
+    assert 'LDFLAGS="$LDFLAGS -fvisibility=hidden -fvisibility-inlines-hidden"' in tc
 
 
 def test_toolchain_crossbuild_to_android():
diff --git a/test/integration/toolchains/meson/test_mesontoolchain.py b/test/integration/toolchains/meson/test_mesontoolchain.py
index 3b5411212df..c1a7e83064a 100644
--- a/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -768,13 +768,15 @@ def test_conf_extra_apple_flags():
     f = "conan_meson_native.ini"
     tc = c.load(f)
     for flags in ["c_args", "cpp_args", "c_link_args", "cpp_link_args"]:
-        assert f"{flags} = ['-m64', '-fembed-bitcode', '-fobjc-arc', '-fvisibility=default']" in tc
-
+        assert f"{flags} = ['-m64', '-fembed-bitcode', '-fvisibility=default']" in tc
+    for flags in ["objcpp_args", "objc_args"]:
+        assert f"{flags} = ['-fobjc-arc', '-m64', '-fembed-bitcode', '-fvisibility=default']" in tc
     c.run("install . -pr:a host -s build_type=Debug")
     tc = c.load(f)
     for flags in ["c_args", "cpp_args", "c_link_args", "cpp_link_args"]:
-        assert f"{flags} = ['-m64', '-fembed-bitcode-marker'," \
-               " '-fobjc-arc', '-fvisibility=default']" in tc
+        assert f"{flags} = ['-m64', '-fembed-bitcode-marker', '-fvisibility=default']" in tc
+    for flags in ["objcpp_args", "objc_args"]:
+        assert f"{flags} = ['-fobjc-arc', '-m64', '-fembed-bitcode-marker', '-fvisibility=default']" in tc
 
     host = textwrap.dedent("""
         [settings]
@@ -792,5 +794,6 @@ def test_conf_extra_apple_flags():
     c.run("install . -pr:a host")
     tc = c.load(f)
     for flags in ["c_args", "cpp_args", "c_link_args", "cpp_link_args"]:
-        assert f"{flags} = ['-m64', '-fno-objc-arc', '-fvisibility=hidden', " \
-               "'-fvisibility-inlines-hidden']" in tc
+        assert f"{flags} = ['-m64', '-fvisibility=hidden', '-fvisibility-inlines-hidden']" in tc
+    for flags in ["objcpp_args", "objc_args"]:
+        assert f"{flags} = ['-fno-objc-arc', '-m64', '-fvisibility=hidden', '-fvisibility-inlines-hidden']" in tc


Code review comments:
diff_hunk: @@ -403,11 +403,18 @@ def _resolve_apple_flags_and_variables(self, build_env, compilers_by_conf):
         self.apple_isysroot_flag = isysroot_flag.split() if isysroot_flag else []
         self.apple_min_version_flag = [apple_min_version_flag(self._conanfile)]
         # Objective C/C++ ones
+        flags = []
         self.objc = compilers_by_conf.get("objc", "clang")
         self.objcpp = compilers_by_conf.get("objcpp", "clang++")
-        self.objc_args = self._get_env_list(build_env.get('OBJCFLAGS', []))
+        enable_arc = self._conanfile.conf.get("tools.apple:enable_arc", check_type=bool)
+        fobj_arc = ""
+        if enable_arc:
+            fobj_arc = "-fobjc-arc"
+        if enable_arc is False:
+            fobj_arc = "-fno-objc-arc"
+        self.objc_args = self._get_env_list(build_env.get('OBJCFLAGS', [fobj_arc]))
path: conan/tools/meson/toolchain.py
text: @user1:
This doesn't seem correct, if OBJCFLAGS is defined, then the ``fobj_arc`` would be lost?

@author:
Absolutely 🤦‍♂️