Repository: conan-io/conan
Pull request #11632 (2022-07-13 14:24:28)

[MesonToolchain] Added Objective-C/C++ arguments
Changelog: Feature: Added `objc_args`/`objc_link_args`/`objcpp_args`/`objcpp_link_args` to MesonToolchain for Objective-C/C++ language.
Closes: https://github.com/conan-io/conan/issues/11628
Docs: https://github.com/conan-io/docs/pull/XXXX



franramirez688: I want to add another test using Objective-C++
SSE4: 
> * Shouldn't it be its own compiler? 

in which context?
if you mean compiler executable, there are just `clang` and `clang++`. there are no dedicated executables to compile Objective-C[++] code. it's basically controlled by flags (e.g. `-x objective-c`)
if you mean compiler as a suite, it's part of LLVM/Clang package.
if you mean compiler as a conan setting, it is just `apple-clang` entry.

> * Are there packages mixed with C/C++ and objC? Only ObjC?

there are MANY packages mixing C/C++ with Objective-C/Objective-C++.
Objective-C[++] basically acts as a glue for al the modern Apple APIs - there is just no way to invoke these APIs from C/C++.

> * What happens with the binary model for objC?

it's basically follows pure C model more or less. we didn't need to complicate it in the past.

> * What happens with other build systems like CMake for ObjC?

CMake supports Objective-C[++] out of the box. other build systems (e.g. autotools) as well. probably some rare used might not support it, but it has nothing to do with conan.

Patch:
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index f095c8679..6bcacf509 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -40,6 +40,10 @@ class MesonToolchain(object):
     [binaries]
     {% if c %}c = '{{c}}'{% endif %}
     {% if cpp %}cpp = '{{cpp}}'{% endif %}
+    {% if is_apple_system %}
+    {% if objc %}objc = '{{objc}}'{% endif %}
+    {% if objcpp %}objcpp = '{{objcpp}}'{% endif %}
+    {% endif %}
     {% if c_ld %}c_ld = '{{c_ld}}'{% endif %}
     {% if cpp_ld %}cpp_ld = '{{cpp_ld}}'{% endif %}
     {% if ar %}ar = '{{ar}}'{% endif %}
@@ -57,11 +61,19 @@ class MesonToolchain(object):
     {% if b_staticpic %}b_staticpic = {{b_staticpic}}{% endif %}
     {% if cpp_std %}cpp_std = '{{cpp_std}}' {% endif %}
     {% if backend %}backend = '{{backend}}' {% endif %}
+    {% if pkg_config_path %}pkg_config_path = '{{pkg_config_path}}'{% endif %}
+    # C/C++ arguments
     c_args = {{c_args}} + preprocessor_definitions + deps_c_args
     c_link_args = {{c_link_args}} + deps_c_link_args
     cpp_args = {{cpp_args}} + preprocessor_definitions + deps_cpp_args
     cpp_link_args = {{cpp_link_args}} + deps_cpp_link_args
-    {% if pkg_config_path %}pkg_config_path = '{{pkg_config_path}}'{% endif %}
+    {% if is_apple_system %}
+    # Objective-C/C++ arguments
+    objc_args = {{objc_args}} + preprocessor_definitions + deps_c_args
+    objc_link_args = {{objc_link_args}} + deps_c_link_args
+    objcpp_args = {{objcpp_args}} + preprocessor_definitions + deps_cpp_args
+    objcpp_link_args = {{objcpp_link_args}} + deps_cpp_link_args
+    {% endif %}
 
     {% for context, values in cross_build.items() %}
     [{{context}}_machine]
@@ -75,6 +87,7 @@ class MesonToolchain(object):
     def __init__(self, conanfile, backend=None):
         self._conanfile = conanfile
         self._os = self._conanfile.settings.get_safe("os")
+        self._is_apple_system = is_apple_os(self._os)
 
         # Values are kept as Python built-ins so users can modify them more easily, and they are
         # only converted to Meson file syntax for rendering
@@ -161,16 +174,22 @@ class MesonToolchain(object):
         self.cpp_args = self._get_env_list(build_env.get("CXXFLAGS", []))
         self.cpp_link_args = self._get_env_list(build_env.get("LDFLAGS", []))
 
-        # Apple flags
+        # Apple flags and variables
         self.apple_arch_flag = []
         self.apple_isysroot_flag = []
         self.apple_min_version_flag = []
-
-        self._resolve_apple_flags()
+        self.objc = None
+        self.objcpp = None
+        self.objc_args = []
+        self.objc_link_args = []
+        self.objcpp_args = []
+        self.objcpp_link_args = []
+
+        self._resolve_apple_flags_and_variables(build_env)
         self._resolve_android_cross_compilation()
 
-    def _resolve_apple_flags(self):
-        if not is_apple_os(self._os):
+    def _resolve_apple_flags_and_variables(self, build_env):
+        if not self._is_apple_system:
             return
         # SDK path is mandatory for cross-building
         sdk_path = self._conanfile.conf.get("tools.apple:sdk_path")
@@ -187,6 +206,13 @@ class MesonToolchain(object):
         self.apple_arch_flag = ["-arch", arch] if arch else []
         self.apple_isysroot_flag = ["-isysroot", sdk_path] if sdk_path else []
         self.apple_min_version_flag = [apple_min_version_flag(self._conanfile)]
+        # Objective C/C++ ones
+        self.objc = "clang"
+        self.objcpp = "clang++"
+        self.objc_args = self._get_env_list(build_env.get('OBJCFLAGS', []))
+        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_link_args = self._get_env_list(build_env.get('LDFLAGS', []))
 
     def _resolve_android_cross_compilation(self):
         if not self.cross_build or not self.cross_build["host"]["system"] == "android":
@@ -238,6 +264,12 @@ class MesonToolchain(object):
         self.cpp_args.extend(apple_flags + extra_flags["cxxflags"])
         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)
+        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"])
 
         return {
             # https://mesonbuild.com/Machine-files.html#properties
@@ -250,6 +282,8 @@ class MesonToolchain(object):
             # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables
             "c": self.c,
             "cpp": self.cpp,
+            "objc": self.objc,
+            "objcpp": self.objcpp,
             "c_ld": self.c_ld,
             "cpp_ld": self.cpp_ld,
             "ar": self.ar,
@@ -271,9 +305,14 @@ class MesonToolchain(object):
             "c_link_args": to_meson_value(self._filter_list_empty_fields(self.c_link_args)),
             "cpp_args": to_meson_value(self._filter_list_empty_fields(self.cpp_args)),
             "cpp_link_args": to_meson_value(self._filter_list_empty_fields(self.cpp_link_args)),
+            "objc_args": to_meson_value(self._filter_list_empty_fields(self.objc_args)),
+            "objc_link_args": to_meson_value(self._filter_list_empty_fields(self.objc_link_args)),
+            "objcpp_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_args)),
+            "objcpp_link_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_link_args)),
             "pkg_config_path": self.pkg_config_path,
             "preprocessor_definitions": self.preprocessor_definitions,
-            "cross_build": self.cross_build
+            "cross_build": self.cross_build,
+            "is_apple_system": self._is_apple_system
         }
 
     @property
diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py
index 151b7bc50..1ba1fd7e5 100644
--- a/conan/tools/microsoft/msbuilddeps.py
+++ b/conan/tools/microsoft/msbuilddeps.py
@@ -48,7 +48,8 @@ class MSBuildDeps(object):
           </ImportGroup>
           {% if host_context %}
           <PropertyGroup>
-            <LocalDebuggerEnvironment>PATH=%PATH%;$(Conan{{name}}BinaryDirectories)$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
+            <ConanDebugPath>$(Conan{{name}}BinaryDirectories);$(ConanDebugPath)</ConanDebugPath>
+            <LocalDebuggerEnvironment>PATH=$(ConanDebugPath);%PATH%</LocalDebuggerEnvironment>
             <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
             {% if ca_exclude %}
             <CAExcludePath>$(Conan{{name}}IncludeDirectories);$(CAExcludePath)</CAExcludePath>
diff --git a/conans/client/generators/cmake_common.py b/conans/client/generators/cmake_common.py
index 06d0d2294..ddb4bf8dd 100644
--- a/conans/client/generators/cmake_common.py
+++ b/conans/client/generators/cmake_common.py
@@ -257,7 +257,7 @@ class CMakeCommonMacros:
     conan_find_libraries_abs_path = textwrap.dedent("""
         function(conan_find_libraries_abs_path libraries package_libdir libraries_abs_path)
             foreach(_LIBRARY_NAME ${libraries})
-                find_library(CONAN_FOUND_LIBRARY NAME ${_LIBRARY_NAME} PATHS ${package_libdir}
+                find_library(CONAN_FOUND_LIBRARY NAMES ${_LIBRARY_NAME} PATHS ${package_libdir}
                              NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
                 if(CONAN_FOUND_LIBRARY)
                     conan_message(STATUS "Library ${_LIBRARY_NAME} found ${CONAN_FOUND_LIBRARY}")
@@ -277,7 +277,7 @@ class CMakeCommonMacros:
             unset(_CONAN_ACTUAL_TARGETS CACHE)
             unset(_CONAN_FOUND_SYSTEM_LIBS CACHE)
             foreach(_LIBRARY_NAME ${libraries})
-                find_library(CONAN_FOUND_LIBRARY NAME ${_LIBRARY_NAME} PATHS ${package_libdir}
+                find_library(CONAN_FOUND_LIBRARY NAMES ${_LIBRARY_NAME} PATHS ${package_libdir}
                              NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
                 if(CONAN_FOUND_LIBRARY)
                     conan_message(STATUS "Library ${_LIBRARY_NAME} found ${CONAN_FOUND_LIBRARY}")
@@ -856,7 +856,7 @@ class CMakeCommonMacros:
                 endif()
                 foreach(_FRAMEWORK ${FRAMEWORKS})
                     # https://cmake.org/pipermail/cmake-developers/2017-August/030199.html
-                    find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAME ${_FRAMEWORK} PATHS ${CONAN_FRAMEWORK_DIRS${SUFFIX}} CMAKE_FIND_ROOT_PATH_BOTH)
+                    find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAMES ${_FRAMEWORK} PATHS ${CONAN_FRAMEWORK_DIRS${SUFFIX}} CMAKE_FIND_ROOT_PATH_BOTH)
                     if(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND)
                         list(APPEND ${FRAMEWORKS_FOUND} ${CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND})
                     else()
diff --git a/conans/client/generators/cmake_find_package_common.py b/conans/client/generators/cmake_find_package_common.py
index de54ea56a..11acbe640 100644
--- a/conans/client/generators/cmake_find_package_common.py
+++ b/conans/client/generators/cmake_find_package_common.py
@@ -112,7 +112,7 @@ class CMakeFindPackageCommonMacros:
             if(APPLE)
                 foreach(_FRAMEWORK ${FRAMEWORKS})
                     # https://cmake.org/pipermail/cmake-developers/2017-August/030199.html
-                    find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAME ${_FRAMEWORK} PATHS ${FRAMEWORKS_DIRS} CMAKE_FIND_ROOT_PATH_BOTH)
+                    find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAMES ${_FRAMEWORK} PATHS ${FRAMEWORKS_DIRS} CMAKE_FIND_ROOT_PATH_BOTH)
                     if(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND)
                         list(APPEND ${FRAMEWORKS_FOUND} ${CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND})
                     else()
@@ -128,7 +128,7 @@ class CMakeFindPackageCommonMacros:
             unset(_CONAN_ACTUAL_TARGETS CACHE)
             unset(_CONAN_FOUND_SYSTEM_LIBS CACHE)
             foreach(_LIBRARY_NAME ${libraries})
-                find_library(CONAN_FOUND_LIBRARY NAME ${_LIBRARY_NAME} PATHS ${package_libdir}
+                find_library(CONAN_FOUND_LIBRARY NAMES ${_LIBRARY_NAME} PATHS ${package_libdir}
                              NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
                 if(CONAN_FOUND_LIBRARY)
                     conan_message(STATUS "Library ${_LIBRARY_NAME} found ${CONAN_FOUND_LIBRARY}")


Test patch:
diff --git a/conans/test/functional/toolchains/meson/test_meson_and_objc.py b/conans/test/functional/toolchains/meson/test_meson_and_objc.py
new file mode 100644
index 000000000..6f657a87b
--- /dev/null
+++ b/conans/test/functional/toolchains/meson/test_meson_and_objc.py
@@ -0,0 +1,141 @@
+import os
+import platform
+import sys
+import textwrap
+
+import pytest
+
+from conans.client.tools.apple import XCRun, to_apple_arch
+from conans.test.utils.tools import TestClient
+
+_conanfile_py = textwrap.dedent("""
+from conan import ConanFile
+from conan.tools.meson import Meson, MesonToolchain
+
+
+class App(ConanFile):
+    settings = "os", "arch", "compiler", "build_type"
+    options = {"shared": [True, False], "fPIC": [True, False]}
+    default_options = {"shared": False, "fPIC": True}
+
+    def layout(self):
+        self.folders.build = "build"
+
+    def config_options(self):
+        if self.settings.os == "Windows":
+            del self.options.fPIC
+
+    def generate(self):
+        tc = MesonToolchain(self)
+        tc.generate()
+
+    def build(self):
+        meson = Meson(self)
+        meson.configure()
+        meson.build()
+""")
+
+_meson_build_objc = textwrap.dedent("""
+project('tutorial', 'objc')
+executable('demo', 'main.m', link_args: ['-framework', 'Foundation'])
+""")
+
+
+@pytest.mark.tool_meson
+@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="requires Xcode")
+def test_apple_meson_toolchain_native_compilation_objective_c():
+    profile = textwrap.dedent("""
+    [settings]
+    os = Macos
+    arch = x86_64
+    compiler = apple-clang
+    compiler.version = 12.0
+    compiler.libcxx = libc++
+    """)
+    app = textwrap.dedent("""
+    #import <Foundation/Foundation.h>
+
+    int main(int argc, const char * argv[]) {
+        @autoreleasepool {
+            // insert code here...
+            NSLog(@"Hello, World!");
+        }
+        return 0;
+    }
+    """)
+    t = TestClient()
+    t.save({"conanfile.py": _conanfile_py,
+            "meson.build": _meson_build_objc,
+            "main.m": app,
+            "macos_pr": profile})
+
+    t.run("install . -pr macos_pr")
+    t.run("build .")
+    t.run_command("./demo", cwd=os.path.join(t.current_folder, "build"))
+    assert "Hello, World!" in t.out
+
+
+@pytest.mark.parametrize("arch, os_, os_version, sdk", [
+    ('armv8', 'iOS', '10.0', 'iphoneos'),
+    ('armv7', 'iOS', '10.0', 'iphoneos'),
+    ('x86', 'iOS', '10.0', 'iphonesimulator'),
+    ('x86_64', 'iOS', '10.0', 'iphonesimulator'),
+    ('armv8', 'Macos', None, None)  # MacOS M1
+])
+@pytest.mark.tool_meson
+@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="requires Xcode")
+def test_apple_meson_toolchain_cross_compiling_and_objective_c(arch, os_, os_version, sdk):
+    profile = textwrap.dedent("""
+    include(default)
+
+    [settings]
+    os = {os}
+    os.version = {os_version}
+    os.sdk = {os_sdk}
+    arch = {arch}
+    compiler = apple-clang
+    compiler.version = 12.0
+    compiler.libcxx = libc++
+
+    [conf]
+    tools.apple:sdk_path={sdk_path}
+    """)
+
+    xcrun = XCRun(None, sdk)
+    sdk_path = xcrun.sdk_path
+    app = textwrap.dedent("""
+    #import <Foundation/Foundation.h>
+
+    int main(int argc, const char * argv[]) {
+        @autoreleasepool {
+            // insert code here...
+            NSLog(@"Hello, World!");
+        }
+        return 0;
+    }
+    """)
+    profile = profile.format(
+        os=os_,
+        os_version=os_version,
+        os_sdk=sdk,
+        arch=arch,
+        sdk_path=sdk_path)
+
+    t = TestClient()
+    t.save({"conanfile.py": _conanfile_py,
+            "meson.build": _meson_build_objc,
+            "main.m": app,
+            "profile_host": profile})
+
+    t.run("install . --profile:build=default --profile:host=profile_host")
+    t.run("build .")
+    assert "Objective-C compiler for the host machine: clang" in t.out
+
+    demo = os.path.join(t.current_folder, "build", "demo")
+    assert os.path.isfile(demo) is True
+
+    lipo = xcrun.find('lipo')
+    t.run_command('"%s" -info "%s"' % (lipo, demo))
+    assert "architecture: %s" % to_apple_arch(arch) in t.out
diff --git a/conans/test/integration/py_requires/python_requires_test.py b/conans/test/integration/py_requires/python_requires_test.py
index be3aa71db..c2d5bab40 100644
--- a/conans/test/integration/py_requires/python_requires_test.py
+++ b/conans/test/integration/py_requires/python_requires_test.py
@@ -969,3 +969,54 @@ def test_transitive_diamond_python_requires():
            client.out
     assert "consumer/1.0@user/channel: Calling package()\nconsumer/1.0@user/channel: 222, 234" in \
            client.out
+
+
+def test_multiple_reuse():
+    """ test how to enable the multiple code reuse for custom user generators
+        # https://github.com/conan-io/conan/issues/11589
+    """
+
+    c = TestClient()
+    common = textwrap.dedent("""
+        from conan import ConanFile
+        def mycommon():
+            return 42
+        class Common(ConanFile):
+            name = "common"
+            version = "0.1"
+        """)
+    tool = textwrap.dedent("""
+        from conan import ConanFile
+
+        class MyGenerator:
+            common = None
+            def __init__(self, conanfile):
+                self.conanfile = conanfile
+            def generate(self):
+                self.conanfile.output.info("VALUE TOOL: {}!!!".format(MyGenerator.common.mycommon()))
+
+        class Tool(ConanFile):
+            name = "tool"
+            version = "0.1"
+            python_requires = "common/0.1"
+            def init(self):
+                MyGenerator.common = self.python_requires["common"].module
+        """)
+    consumer = textwrap.dedent("""
+        from conan import ConanFile
+        class Consumer(ConanFile):
+            python_requires = "tool/0.1", "common/0.1"
+            def generate(self):
+                mycommon = self.python_requires["common"].module.mycommon
+                self.output.info("VALUE COMMON: {}!!!".format(mycommon()))
+                mygenerator = self.python_requires["tool"].module.MyGenerator(self)
+                mygenerator.generate()
+        """)
+    c.save({"common/conanfile.py": common,
+            "tool/conanfile.py": tool,
+            "consumer/conanfile.py": consumer})
+    c.run("export common")
+    c.run("export tool")
+    c.run("install consumer")
+    assert "VALUE COMMON: 42!!!" in c.out
+    assert "VALUE TOOL: 42!!!" in c.out
diff --git a/conans/test/unittests/client/generators/cmake_test.py b/conans/test/unittests/client/generators/cmake_test.py
index 74e9111b7..9c3bef9be 100644
--- a/conans/test/unittests/client/generators/cmake_test.py
+++ b/conans/test/unittests/client/generators/cmake_test.py
@@ -371,7 +371,7 @@ endmacro()""", macro)
 
         generator = CMakeGenerator(conanfile)
         content = generator.content
-        self.assertIn('find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAME ${_FRAMEWORK} PATHS'
+        self.assertIn('find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAMES ${_FRAMEWORK} PATHS'
                       ' ${CONAN_FRAMEWORK_DIRS${SUFFIX}} CMAKE_FIND_ROOT_PATH_BOTH)', content)
         self.assertIn('set(CONAN_FRAMEWORK_DIRS "dummy_root_folder1/Frameworks"\n'
                       '\t\t\t"dummy_root_folder1/path/to/Frameworks1"\n'