From 18c9e17ec12f3b7c59980f0f498e6243436c5db6 Mon Sep 17 00:00:00 2001 From: czoido Date: Tue, 27 Feb 2024 16:53:42 +0100 Subject: [PATCH 01/17] superbasic support for universal --- conan/tools/apple/apple.py | 6 + conan/tools/cmake/toolchain/blocks.py | 19 ++- conans/model/settings.py | 12 +- .../cmake/test_universal_binaries.py | 112 ++++++++++++++++++ 4 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 conans/test/functional/toolchains/cmake/test_universal_binaries.py diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py index 39dafb69df1..b78777c3904 100644 --- a/conan/tools/apple/apple.py +++ b/conan/tools/apple/apple.py @@ -30,6 +30,12 @@ def to_apple_arch(conanfile, default=None): return _to_apple_arch(arch_, default) +def _to_apple_archs(conanfile, default=None): + """converts conan-style architectures into Apple-style archs + with support for Universal binaries""" + arch_ = conanfile.settings.get_safe("arch") + return ";".join([_to_apple_arch(arch, default) for arch in arch_.split("-")]) + def apple_sdk_path(conanfile): sdk_path = conanfile.conf.get("tools.apple:sdk_path") if not sdk_path: diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 99612fc09ec..7b2789c0c86 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -5,7 +5,7 @@ from jinja2 import Template -from conan.tools.apple.apple import get_apple_sdk_fullname +from conan.tools.apple.apple import get_apple_sdk_fullname, _to_apple_archs from conan.tools.android.utils import android_abi from conan.tools.apple.apple import is_apple_os, to_apple_arch from conan.tools.build import build_jobs @@ -300,7 +300,7 @@ def context(self): class AppleSystemBlock(Block): template = textwrap.dedent(""" # Set the architectures for which to build. - set(CMAKE_OSX_ARCHITECTURES {{ cmake_osx_architectures }} CACHE STRING "" FORCE) + set(CMAKE_OSX_ARCHITECTURES "{{ cmake_osx_architectures }}" CACHE STRING "" FORCE) # Setting CMAKE_OSX_SYSROOT SDK, when using Xcode generator the name is enough # but full path is necessary for others set(CMAKE_OSX_SYSROOT {{ cmake_osx_sysroot }} CACHE STRING "" FORCE) @@ -358,7 +358,7 @@ def context(self): # check valid combinations of architecture - os ? # for iOS a FAT library valid for simulator and device can be generated # if multiple archs are specified "-DCMAKE_OSX_ARCHITECTURES=armv7;armv7s;arm64;i386;x86_64" - host_architecture = to_apple_arch(self._conanfile) + host_architectures = _to_apple_archs(self._conanfile) host_os_version = self._conanfile.settings.get_safe("os.version") host_sdk_name = self._conanfile.conf.get("tools.apple:sdk_path") or get_apple_sdk_fullname(self._conanfile) @@ -380,8 +380,8 @@ def context(self): if host_sdk_name: ctxt_toolchain["cmake_osx_sysroot"] = host_sdk_name # this is used to initialize the OSX_ARCHITECTURES property on each target as it is created - if host_architecture: - ctxt_toolchain["cmake_osx_architectures"] = host_architecture + if host_architectures: + ctxt_toolchain["cmake_osx_architectures"] = host_architectures if host_os_version: # https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html @@ -814,7 +814,14 @@ def _get_generic_system_name(self): (arch_build == "ppc64") and (arch_host == "ppc32")): return cmake_system_name_map.get(os_host, os_host) + def _is_apple_universal(self): + return "-" in self._conanfile.settings.get_safe("arch") + def _is_apple_cross_building(self): + + if self._is_apple_universal(): + return False + os_host = self._conanfile.settings.get_safe("os") arch_host = self._conanfile.settings.get_safe("arch") arch_build = self._conanfile.settings_build.get_safe("arch") @@ -829,7 +836,7 @@ def _get_cross_build(self): system_version = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_version") system_processor = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_processor") - if not user_toolchain: # try to detect automatically + if not user_toolchain and not self._is_apple_universal(): # try to detect automatically os_host = self._conanfile.settings.get_safe("os") arch_host = self._conanfile.settings.get_safe("arch") if arch_host == "armv8": diff --git a/conans/model/settings.py b/conans/model/settings.py index c7b3837aa3b..bff7ed7f755 100644 --- a/conans/model/settings.py +++ b/conans/model/settings.py @@ -96,9 +96,19 @@ def __delattr__(self, item): child_setting = self._get_child(self._value) delattr(child_setting, item) + def is_composed(self, value, valid_definitions): + parts = value.split('-') + for part in parts: + if part not in valid_definitions: + return False + return True + def _validate(self, value): value = str(value) if value is not None else None - if "ANY" not in self._definition and value not in self._definition: + # FIXME: limit this to only architecture values in Macos that are valid for + # universal binaries, maybe move to another better place + is_composed = self.is_composed(value, self._definition) + if "ANY" not in self._definition and value not in self._definition and not is_composed: raise ConanException(bad_value_msg(self._name, value, self._definition)) return value diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py new file mode 100644 index 00000000000..c8d691bc46f --- /dev/null +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -0,0 +1,112 @@ +import platform +import textwrap + +import pytest + +from conans.test.utils.tools import TestClient + + +@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") +#@pytest.mark.tool("cmake") +def test_create_universal_binary(): + client = TestClient() + + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + + + class mylibraryRecipe(ConanFile): + name = "mylibrary" + version = "1.0" + package_type = "library" + + # Optional metadata + license = "" + author = " " + url = "" + description = "" + topics = ("", "", "") + + # Binary configuration + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + + # Sources are located in the same place as this recipe, copy them to the recipe + exports_sources = "CMakeLists.txt", "src/*", "include/*" + + def config_options(self): + if self.settings.os == "Windows": + self.options.rm_safe("fPIC") + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + self.run("lipo -info libmylibrary.a") + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["mylibrary"] + """) + + test_conanfile = textwrap.dedent(""" + import os + + from conan import ConanFile + from conan.tools.cmake import CMake, cmake_layout + from conan.tools.build import can_run + + + class mylibraryTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def layout(self): + cmake_layout(self) + + def test(self): + # leaving can_run here to test the real case + if can_run(self): + exe = os.path.join(self.cpp.build.bindir, "example") + self.run(exe, env="conanrun") + self.run(f"lipo {exe} -info", env="conanrun") + """) + + client.run("new cmake_lib -d name=mylibrary -d version=1.0") + client.save({"conanfile.py": conanfile, "test_package/conanfile.py": test_conanfile}) + + client.run('create . -s="arch=x86_64-armv8" --build=missing -tf=""') + + assert "libmylibrary.a are: x86_64 arm64" in client.out + + client.run('test test_package mylibrary/1.0 -s="arch=x86_64-armv8" ' + '-c tools.build.cross_building:can_run=True') + + assert "example are: x86_64 arm64" in client.out + From 4e1ff17e4d95ed5288d655dc92fe251dd1317fca Mon Sep 17 00:00:00 2001 From: czoido Date: Tue, 27 Feb 2024 17:22:35 +0100 Subject: [PATCH 02/17] wip --- .../cmake/test_universal_binaries.py | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index c8d691bc46f..4454d56687c 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -13,22 +13,15 @@ def test_create_universal_binary(): conanfile = textwrap.dedent(""" from conan import ConanFile - from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + from conan.tools.cmake import CMake, cmake_layout class mylibraryRecipe(ConanFile): name = "mylibrary" version = "1.0" package_type = "library" + generators = "CMakeToolchain" - # Optional metadata - license = "" - author = " " - url = "" - description = "" - topics = ("", "", "") - - # Binary configuration settings = "os", "compiler", "build_type", "arch" options = {"shared": [True, False], "fPIC": [True, False]} default_options = {"shared": False, "fPIC": True} @@ -36,23 +29,9 @@ class mylibraryRecipe(ConanFile): # Sources are located in the same place as this recipe, copy them to the recipe exports_sources = "CMakeLists.txt", "src/*", "include/*" - def config_options(self): - if self.settings.os == "Windows": - self.options.rm_safe("fPIC") - - def configure(self): - if self.options.shared: - self.options.rm_safe("fPIC") - def layout(self): cmake_layout(self) - def generate(self): - deps = CMakeDeps(self) - deps.generate() - tc = CMakeToolchain(self) - tc.generate() - def build(self): cmake = CMake(self) cmake.configure() @@ -101,12 +80,12 @@ def test(self): client.run("new cmake_lib -d name=mylibrary -d version=1.0") client.save({"conanfile.py": conanfile, "test_package/conanfile.py": test_conanfile}) - client.run('create . -s="arch=x86_64-armv8" --build=missing -tf=""') + client.run('create . -s="arch=x86_64-armv8-armv8.3" --build=missing -tf=""') - assert "libmylibrary.a are: x86_64 arm64" in client.out + assert "libmylibrary.a are: x86_64 arm64 arm64e" in client.out - client.run('test test_package mylibrary/1.0 -s="arch=x86_64-armv8" ' + client.run('test test_package mylibrary/1.0 -s="arch=x86_64-armv8-armv8.3" ' '-c tools.build.cross_building:can_run=True') - assert "example are: x86_64 arm64" in client.out + assert "example are: x86_64 arm64 arm64e" in client.out From 127e09120e020d4820f4882132cd312bc0be394b Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 16:15:08 +0100 Subject: [PATCH 03/17] minor changes --- conan/tools/apple/apple.py | 13 ++++++++++++- conan/tools/cmake/toolchain/blocks.py | 18 +++++++++--------- conans/model/settings.py | 14 +++----------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py index b78777c3904..a33d339f2aa 100644 --- a/conan/tools/apple/apple.py +++ b/conan/tools/apple/apple.py @@ -32,10 +32,21 @@ def to_apple_arch(conanfile, default=None): def _to_apple_archs(conanfile, default=None): """converts conan-style architectures into Apple-style archs - with support for Universal binaries""" + also supports multiple architectures separated by -""" arch_ = conanfile.settings.get_safe("arch") return ";".join([_to_apple_arch(arch, default) for arch in arch_.split("-")]) + +def _is_universal_arch(settings_value, valid_definitions): + valid_macos_values = [val for val in valid_definitions + if "arm" in val or "x86" in val and "-" not in val] + parts = settings_value.split('-') + for part in parts: + if part not in valid_macos_values: + return False + return True + + def apple_sdk_path(conanfile): sdk_path = conanfile.conf.get("tools.apple:sdk_path") if not sdk_path: diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 7b2789c0c86..6fcdc934dbe 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -5,7 +5,7 @@ from jinja2 import Template -from conan.tools.apple.apple import get_apple_sdk_fullname, _to_apple_archs +from conan.tools.apple.apple import get_apple_sdk_fullname, _to_apple_archs, _is_universal_arch from conan.tools.android.utils import android_abi from conan.tools.apple.apple import is_apple_os, to_apple_arch from conan.tools.build import build_jobs @@ -358,7 +358,7 @@ def context(self): # check valid combinations of architecture - os ? # for iOS a FAT library valid for simulator and device can be generated # if multiple archs are specified "-DCMAKE_OSX_ARCHITECTURES=armv7;armv7s;arm64;i386;x86_64" - host_architectures = _to_apple_archs(self._conanfile) + host_architecture = _to_apple_archs(self._conanfile) host_os_version = self._conanfile.settings.get_safe("os.version") host_sdk_name = self._conanfile.conf.get("tools.apple:sdk_path") or get_apple_sdk_fullname(self._conanfile) @@ -380,8 +380,8 @@ def context(self): if host_sdk_name: ctxt_toolchain["cmake_osx_sysroot"] = host_sdk_name # this is used to initialize the OSX_ARCHITECTURES property on each target as it is created - if host_architectures: - ctxt_toolchain["cmake_osx_architectures"] = host_architectures + if host_architecture: + ctxt_toolchain["cmake_osx_architectures"] = host_architecture if host_os_version: # https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html @@ -814,12 +814,10 @@ def _get_generic_system_name(self): (arch_build == "ppc64") and (arch_host == "ppc32")): return cmake_system_name_map.get(os_host, os_host) - def _is_apple_universal(self): - return "-" in self._conanfile.settings.get_safe("arch") - def _is_apple_cross_building(self): - if self._is_apple_universal(): + if _is_universal_arch(self._conanfile.settings.get_safe("arch"), + self._conanfile.settings.possible_values()): return False os_host = self._conanfile.settings.get_safe("os") @@ -836,7 +834,9 @@ def _get_cross_build(self): system_version = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_version") system_processor = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_processor") - if not user_toolchain and not self._is_apple_universal(): # try to detect automatically + # try to detect automatically + if not user_toolchain and not _is_universal_arch(self._conanfile.settings.get_safe("arch"), + self._conanfile.settings.possible_values()): os_host = self._conanfile.settings.get_safe("os") arch_host = self._conanfile.settings.get_safe("arch") if arch_host == "armv8": diff --git a/conans/model/settings.py b/conans/model/settings.py index bff7ed7f755..fdd386cfbd1 100644 --- a/conans/model/settings.py +++ b/conans/model/settings.py @@ -1,5 +1,6 @@ import yaml +from conan.tools.apple.apple import _is_universal_arch from conans.errors import ConanException @@ -96,19 +97,10 @@ def __delattr__(self, item): child_setting = self._get_child(self._value) delattr(child_setting, item) - def is_composed(self, value, valid_definitions): - parts = value.split('-') - for part in parts: - if part not in valid_definitions: - return False - return True - def _validate(self, value): value = str(value) if value is not None else None - # FIXME: limit this to only architecture values in Macos that are valid for - # universal binaries, maybe move to another better place - is_composed = self.is_composed(value, self._definition) - if "ANY" not in self._definition and value not in self._definition and not is_composed: + is_universal = _is_universal_arch(value, self._definition) if self._name == "settings.arch" else False + if "ANY" not in self._definition and value not in self._definition and not is_universal: raise ConanException(bad_value_msg(self._name, value, self._definition)) return value From e4b24f418dffe2c125a0b1db067ba86a49aaf1e8 Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 16:16:08 +0100 Subject: [PATCH 04/17] minor changes --- conan/tools/cmake/toolchain/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 6fcdc934dbe..b3f06b2a0e2 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -300,7 +300,7 @@ def context(self): class AppleSystemBlock(Block): template = textwrap.dedent(""" # Set the architectures for which to build. - set(CMAKE_OSX_ARCHITECTURES "{{ cmake_osx_architectures }}" CACHE STRING "" FORCE) + set(CMAKE_OSX_ARCHITECTURES {{ cmake_osx_architectures }} CACHE STRING "" FORCE) # Setting CMAKE_OSX_SYSROOT SDK, when using Xcode generator the name is enough # but full path is necessary for others set(CMAKE_OSX_SYSROOT {{ cmake_osx_sysroot }} CACHE STRING "" FORCE) From a204784f24cb1df5af31c3e18512ae35959bfaa4 Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 16:17:24 +0100 Subject: [PATCH 05/17] add mark --- .../test/functional/toolchains/cmake/test_universal_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 4454d56687c..81912890787 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -7,7 +7,7 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -#@pytest.mark.tool("cmake") +@pytest.mark.tool("cmake") def test_create_universal_binary(): client = TestClient() From 5e23e05759ccf51f7e0851b95d74a9d122a038fb Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 16:20:08 +0100 Subject: [PATCH 06/17] minor changes --- .../toolchains/cmake/test_universal_binaries.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 81912890787..9c4a79effff 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -7,26 +7,19 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -@pytest.mark.tool("cmake") +#@pytest.mark.tool("cmake") def test_create_universal_binary(): client = TestClient() conanfile = textwrap.dedent(""" from conan import ConanFile from conan.tools.cmake import CMake, cmake_layout - - class mylibraryRecipe(ConanFile): - name = "mylibrary" - version = "1.0" package_type = "library" generators = "CMakeToolchain" - settings = "os", "compiler", "build_type", "arch" options = {"shared": [True, False], "fPIC": [True, False]} default_options = {"shared": False, "fPIC": True} - - # Sources are located in the same place as this recipe, copy them to the recipe exports_sources = "CMakeLists.txt", "src/*", "include/*" def layout(self): @@ -48,12 +41,10 @@ def package_info(self): test_conanfile = textwrap.dedent(""" import os - from conan import ConanFile from conan.tools.cmake import CMake, cmake_layout from conan.tools.build import can_run - class mylibraryTestConan(ConanFile): settings = "os", "compiler", "build_type", "arch" generators = "CMakeDeps", "CMakeToolchain" @@ -80,7 +71,8 @@ def test(self): client.run("new cmake_lib -d name=mylibrary -d version=1.0") client.save({"conanfile.py": conanfile, "test_package/conanfile.py": test_conanfile}) - client.run('create . -s="arch=x86_64-armv8-armv8.3" --build=missing -tf=""') + client.run('create . --name=mylibrary --version=1.0 ' + '-s="arch=x86_64-armv8-armv8.3" --build=missing -tf=""') assert "libmylibrary.a are: x86_64 arm64 arm64e" in client.out From 476d7222e44974911e47654d9a266c0754c438b2 Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 16:45:05 +0100 Subject: [PATCH 07/17] improve function --- conan/tools/apple/apple.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py index a33d339f2aa..64bd130d055 100644 --- a/conan/tools/apple/apple.py +++ b/conan/tools/apple/apple.py @@ -38,13 +38,14 @@ def _to_apple_archs(conanfile, default=None): def _is_universal_arch(settings_value, valid_definitions): - valid_macos_values = [val for val in valid_definitions - if "arm" in val or "x86" in val and "-" not in val] + if settings_value is None: + return False + + valid_macos_values = [val for val in valid_definitions if + ("arm" in val or "x86" in val) and "-" not in val] + parts = settings_value.split('-') - for part in parts: - if part not in valid_macos_values: - return False - return True + return all(part in valid_macos_values for part in parts) def apple_sdk_path(conanfile): From c3fc983a5d83f993a83bf379dce9165d2020089e Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 17:32:53 +0100 Subject: [PATCH 08/17] minor changes --- .../test/functional/toolchains/cmake/test_universal_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 9c4a79effff..d084afa8624 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -7,7 +7,7 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -#@pytest.mark.tool("cmake") +@pytest.mark.tool("cmake") def test_create_universal_binary(): client = TestClient() From efdc76b8000e7df4659b7ccfda0a06e5ac302da7 Mon Sep 17 00:00:00 2001 From: czoido Date: Wed, 28 Feb 2024 17:35:27 +0100 Subject: [PATCH 09/17] wip --- .../test/functional/toolchains/cmake/test_universal_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index d084afa8624..7e6cf751865 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -7,7 +7,7 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -@pytest.mark.tool("cmake") +@pytest.mark.tool("cmake", "3.23") def test_create_universal_binary(): client = TestClient() From 433b61820f801eb9a2076f70ecc6c422e92c4f36 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 29 Feb 2024 12:57:56 +0100 Subject: [PATCH 10/17] wip --- conan/tools/apple/apple.py | 19 +++++++++---- .../cmake/test_universal_binaries.py | 12 +++------ .../unittests/tools/apple/test_apple_tools.py | 27 ++++++++++++++++++- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py index 64bd130d055..840604f2c57 100644 --- a/conan/tools/apple/apple.py +++ b/conan/tools/apple/apple.py @@ -6,6 +6,9 @@ from conan.errors import ConanException +_universal_arch_separator = '|' + + def is_apple_os(conanfile): """returns True if OS is Apple one (Macos, iOS, watchOS, tvOS or visionOS)""" os_ = conanfile.settings.get_safe("os") @@ -31,20 +34,26 @@ def to_apple_arch(conanfile, default=None): def _to_apple_archs(conanfile, default=None): - """converts conan-style architectures into Apple-style archs - also supports multiple architectures separated by -""" + f"""converts conan-style architectures into Apple-style archs + also supports multiple architectures separated by '{_universal_arch_separator}'""" arch_ = conanfile.settings.get_safe("arch") - return ";".join([_to_apple_arch(arch, default) for arch in arch_.split("-")]) + if arch_ is not None: + return ";".join([_to_apple_arch(arch, default) for arch in arch_.split(_universal_arch_separator)]) def _is_universal_arch(settings_value, valid_definitions): if settings_value is None: return False + parts = settings_value.split(_universal_arch_separator) + + if parts != sorted(parts): + raise ConanException(f"Architectures must be in alphabetical order separated by " + f"{_universal_arch_separator}") + valid_macos_values = [val for val in valid_definitions if - ("arm" in val or "x86" in val) and "-" not in val] + ("arm" in val or "x86" in val)] - parts = settings_value.split('-') return all(part in valid_macos_values for part in parts) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 7e6cf751865..4749c2f518e 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -61,23 +61,19 @@ def layout(self): cmake_layout(self) def test(self): - # leaving can_run here to test the real case - if can_run(self): - exe = os.path.join(self.cpp.build.bindir, "example") - self.run(exe, env="conanrun") - self.run(f"lipo {exe} -info", env="conanrun") + exe = os.path.join(self.cpp.build.bindir, "example") + self.run(f"lipo {exe} -info", env="conanrun") """) client.run("new cmake_lib -d name=mylibrary -d version=1.0") client.save({"conanfile.py": conanfile, "test_package/conanfile.py": test_conanfile}) client.run('create . --name=mylibrary --version=1.0 ' - '-s="arch=x86_64-armv8-armv8.3" --build=missing -tf=""') + '-s="arch=armv8|armv8.3|x86_64" --build=missing -tf=""') assert "libmylibrary.a are: x86_64 arm64 arm64e" in client.out - client.run('test test_package mylibrary/1.0 -s="arch=x86_64-armv8-armv8.3" ' - '-c tools.build.cross_building:can_run=True') + client.run('test test_package mylibrary/1.0 -s="arch=armv8|armv8.3|x86_64"') assert "example are: x86_64 arm64 arm64e" in client.out diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py index 50e410e3756..adebb186e54 100644 --- a/conans/test/unittests/tools/apple/test_apple_tools.py +++ b/conans/test/unittests/tools/apple/test_apple_tools.py @@ -2,10 +2,12 @@ import pytest import textwrap +from conans.errors import ConanException from conans.test.utils.mocks import ConanFileMock, MockSettings, MockOptions from conans.test.utils.test_files import temp_folder from conan.tools.apple import is_apple_os, to_apple_arch, fix_apple_shared_install_name, XCRun -from conan.tools.apple.apple import _get_dylib_install_name # testing private function +from conan.tools.apple.apple import _get_dylib_install_name, _is_universal_arch + def test_tools_apple_is_apple_os(): conanfile = ConanFileMock() @@ -51,6 +53,7 @@ def test_xcrun_public_settings(): assert settings.os == "watchOS" + def test_get_dylib_install_name(): # https://github.com/conan-io/conan/issues/13014 single_arch = textwrap.dedent(""" @@ -70,3 +73,25 @@ def test_get_dylib_install_name(): mock_output_runner.return_value = mock_output install_name = _get_dylib_install_name("otool", "/path/to/libwebp.7.dylib") assert "/absolute/path/lib/libwebp.7.dylib" == install_name + + +valid_definitions = ["arm64", "x86_64", "armv7", "x86"] + + +@pytest.mark.parametrize("settings_value,result", [ + ("arm64|x86_64", True), + ("x86_64|arm64", None), + ("armv7|x86", True), + ("x86|armv7", None), + (None, False), + ("arm64|armv7|x86_64", True), + ("x86|arm64", None), + ("arm64|ppc32", False), +]) +# None is for the exception case +def test_is_universal_arch(settings_value, result): + if result is None: + with pytest.raises(ConanException): + _is_universal_arch(settings_value, valid_definitions) + else: + assert _is_universal_arch(settings_value, valid_definitions) == result From 7fba92d75f6b6c958527f7ae12755683a89f606f Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 29 Feb 2024 14:41:16 +0100 Subject: [PATCH 11/17] wip --- .../cmake/test_universal_binaries.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 4749c2f518e..396e270ba37 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -1,3 +1,4 @@ +import os import platform import textwrap @@ -77,3 +78,23 @@ def test(self): assert "example are: x86_64 arm64 arm64e" in client.out + client.run('new cmake_exe -d name=foo -d version=1.0 -d requires=mylibrary/1.0 --force') + + client.run('install . -s="arch=armv8|armv8.3|x86_64"') + + client.run_command("cmake --preset conan-release") + client.run_command("cmake --build --preset conan-release") + client.run_command("lipo -info ./build/Release/foo") + + assert "foo are: x86_64 arm64 arm64e" in client.out + + client.run('install . -s="arch=armv8|armv8.3|x86_64" ' + '-c tools.cmake.cmake_layout:build_folder_vars=\'["settings.arch"]\'') + + client.run_command("cmake --preset conan-release") + client.run_command("cmake --build --preset conan-release") + client.run_command("lipo -info ./build/Release/foo") + + assert "foo are: x86_64 arm64 arm64e" in client.out + + assert os.path.exists(os.path.join(client.current_folder, "build", "armv8|armv8.3|x86_64")) From 0449bf7da9ec70042bb7235030e05a7d37422ca0 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 29 Feb 2024 15:34:51 +0100 Subject: [PATCH 12/17] minor changes --- conans/test/unittests/tools/apple/test_apple_tools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py index adebb186e54..25b315f447b 100644 --- a/conans/test/unittests/tools/apple/test_apple_tools.py +++ b/conans/test/unittests/tools/apple/test_apple_tools.py @@ -75,9 +75,6 @@ def test_get_dylib_install_name(): assert "/absolute/path/lib/libwebp.7.dylib" == install_name -valid_definitions = ["arm64", "x86_64", "armv7", "x86"] - - @pytest.mark.parametrize("settings_value,result", [ ("arm64|x86_64", True), ("x86_64|arm64", None), @@ -90,6 +87,7 @@ def test_get_dylib_install_name(): ]) # None is for the exception case def test_is_universal_arch(settings_value, result): + valid_definitions = ["arm64", "x86_64", "armv7", "x86"] if result is None: with pytest.raises(ConanException): _is_universal_arch(settings_value, valid_definitions) From 3bdc8a28f2d9a564046b2efd3391480829459ac5 Mon Sep 17 00:00:00 2001 From: czoido Date: Thu, 29 Feb 2024 15:39:56 +0100 Subject: [PATCH 13/17] fix test --- .../toolchains/cmake/test_universal_binaries.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index 396e270ba37..edf079ac9fb 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -5,10 +5,11 @@ import pytest from conans.test.utils.tools import TestClient +from conans.util.files import rmdir @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -@pytest.mark.tool("cmake", "3.23") +#@pytest.mark.tool("cmake", "3.23") def test_create_universal_binary(): client = TestClient() @@ -88,13 +89,13 @@ def test(self): assert "foo are: x86_64 arm64 arm64e" in client.out + rmdir(os.path.join(client.current_folder, "build")) + client.run('install . -s="arch=armv8|armv8.3|x86_64" ' '-c tools.cmake.cmake_layout:build_folder_vars=\'["settings.arch"]\'') - client.run_command("cmake --preset conan-release") - client.run_command("cmake --build --preset conan-release") - client.run_command("lipo -info ./build/Release/foo") + client.run_command("cmake --preset \"conan-armv8|armv8.3|x86_64-release\" ") + client.run_command("cmake --build --preset \"conan-armv8|armv8.3|x86_64-release\" ") + client.run_command("lipo -info './build/armv8|armv8.3|x86_64/Release/foo'") assert "foo are: x86_64 arm64 arm64e" in client.out - - assert os.path.exists(os.path.join(client.current_folder, "build", "armv8|armv8.3|x86_64")) From c76c19f87afa667be6951bec6df609f727def408 Mon Sep 17 00:00:00 2001 From: czoido Date: Fri, 1 Mar 2024 08:32:16 +0100 Subject: [PATCH 14/17] put mark again --- .../test/functional/toolchains/cmake/test_universal_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/cmake/test_universal_binaries.py b/conans/test/functional/toolchains/cmake/test_universal_binaries.py index edf079ac9fb..b4743c93030 100644 --- a/conans/test/functional/toolchains/cmake/test_universal_binaries.py +++ b/conans/test/functional/toolchains/cmake/test_universal_binaries.py @@ -9,7 +9,7 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX") -#@pytest.mark.tool("cmake", "3.23") +@pytest.mark.tool("cmake", "3.23") def test_create_universal_binary(): client = TestClient() From 5cdc3e4361583da2f22a2747b76054d6264673b3 Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 4 Mar 2024 13:23:53 +0100 Subject: [PATCH 15/17] review --- conan/internal/internal_tools.py | 18 +++++++++++++ conan/tools/apple/apple.py | 27 ------------------- conan/tools/cmake/toolchain/blocks.py | 22 ++++++++++----- conans/model/settings.py | 4 +-- .../unittests/tools/apple/test_apple_tools.py | 7 ++--- 5 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 conan/internal/internal_tools.py diff --git a/conan/internal/internal_tools.py b/conan/internal/internal_tools.py new file mode 100644 index 00000000000..93da985150a --- /dev/null +++ b/conan/internal/internal_tools.py @@ -0,0 +1,18 @@ +from conans.errors import ConanException + +universal_arch_separator = '|' + + +def is_universal_arch(settings_value, valid_definitions): + if settings_value is None: + return False + + parts = settings_value.split(universal_arch_separator) + + if parts != sorted(parts): + raise ConanException(f"Architectures must be in alphabetical order separated by " + f"{universal_arch_separator}") + + valid_macos_values = [val for val in valid_definitions if ("arm" in val or "x86" in val)] + + return all(part in valid_macos_values for part in parts) diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py index 840604f2c57..39dafb69df1 100644 --- a/conan/tools/apple/apple.py +++ b/conan/tools/apple/apple.py @@ -6,9 +6,6 @@ from conan.errors import ConanException -_universal_arch_separator = '|' - - def is_apple_os(conanfile): """returns True if OS is Apple one (Macos, iOS, watchOS, tvOS or visionOS)""" os_ = conanfile.settings.get_safe("os") @@ -33,30 +30,6 @@ def to_apple_arch(conanfile, default=None): return _to_apple_arch(arch_, default) -def _to_apple_archs(conanfile, default=None): - f"""converts conan-style architectures into Apple-style archs - also supports multiple architectures separated by '{_universal_arch_separator}'""" - arch_ = conanfile.settings.get_safe("arch") - if arch_ is not None: - return ";".join([_to_apple_arch(arch, default) for arch in arch_.split(_universal_arch_separator)]) - - -def _is_universal_arch(settings_value, valid_definitions): - if settings_value is None: - return False - - parts = settings_value.split(_universal_arch_separator) - - if parts != sorted(parts): - raise ConanException(f"Architectures must be in alphabetical order separated by " - f"{_universal_arch_separator}") - - valid_macos_values = [val for val in valid_definitions if - ("arm" in val or "x86" in val)] - - return all(part in valid_macos_values for part in parts) - - def apple_sdk_path(conanfile): sdk_path = conanfile.conf.get("tools.apple:sdk_path") if not sdk_path: diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 44c71f1e997..c1c68a4de9f 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -5,7 +5,8 @@ from jinja2 import Template -from conan.tools.apple.apple import get_apple_sdk_fullname, _to_apple_archs, _is_universal_arch +from conan.internal.internal_tools import universal_arch_separator, is_universal_arch +from conan.tools.apple.apple import get_apple_sdk_fullname, _to_apple_arch from conan.tools.android.utils import android_abi from conan.tools.apple.apple import is_apple_os, to_apple_arch from conan.tools.build import build_jobs @@ -355,10 +356,19 @@ def context(self): if not is_apple_os(self._conanfile): return None + def to_apple_archs(conanfile, default=None): + f"""converts conan-style architectures into Apple-style archs + to be used by CMake also supports multiple architectures + separated by '{universal_arch_separator}'""" + arch_ = conanfile.settings.get_safe("arch") if conanfile else None + if arch_ is not None: + return ";".join([_to_apple_arch(arch, default) for arch in + arch_.split(universal_arch_separator)]) + # check valid combinations of architecture - os ? # for iOS a FAT library valid for simulator and device can be generated # if multiple archs are specified "-DCMAKE_OSX_ARCHITECTURES=armv7;armv7s;arm64;i386;x86_64" - host_architecture = _to_apple_archs(self._conanfile) + host_architecture = to_apple_archs(self._conanfile) host_os_version = self._conanfile.settings.get_safe("os.version") host_sdk_name = self._conanfile.conf.get("tools.apple:sdk_path") or get_apple_sdk_fullname(self._conanfile) @@ -816,8 +826,8 @@ def _get_generic_system_name(self): def _is_apple_cross_building(self): - if _is_universal_arch(self._conanfile.settings.get_safe("arch"), - self._conanfile.settings.possible_values()): + if is_universal_arch(self._conanfile.settings.get_safe("arch"), + self._conanfile.settings.possible_values().get("arch")): return False os_host = self._conanfile.settings.get_safe("os") @@ -835,8 +845,8 @@ def _get_cross_build(self): system_processor = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_processor") # try to detect automatically - if not user_toolchain and not _is_universal_arch(self._conanfile.settings.get_safe("arch"), - self._conanfile.settings.possible_values()): + if not user_toolchain and not is_universal_arch(self._conanfile.settings.get_safe("arch"), + self._conanfile.settings.possible_values().get("arch")): os_host = self._conanfile.settings.get_safe("os") arch_host = self._conanfile.settings.get_safe("arch") if arch_host == "armv8": diff --git a/conans/model/settings.py b/conans/model/settings.py index fdd386cfbd1..d9cf3e8ed7b 100644 --- a/conans/model/settings.py +++ b/conans/model/settings.py @@ -1,6 +1,6 @@ import yaml -from conan.tools.apple.apple import _is_universal_arch +from conan.internal.internal_tools import is_universal_arch from conans.errors import ConanException @@ -99,7 +99,7 @@ def __delattr__(self, item): def _validate(self, value): value = str(value) if value is not None else None - is_universal = _is_universal_arch(value, self._definition) if self._name == "settings.arch" else False + is_universal = is_universal_arch(value, self._definition) if self._name == "settings.arch" else False if "ANY" not in self._definition and value not in self._definition and not is_universal: raise ConanException(bad_value_msg(self._name, value, self._definition)) return value diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py index 25b315f447b..9807156511d 100644 --- a/conans/test/unittests/tools/apple/test_apple_tools.py +++ b/conans/test/unittests/tools/apple/test_apple_tools.py @@ -2,11 +2,12 @@ import pytest import textwrap +from conan.internal.internal_tools import is_universal_arch from conans.errors import ConanException from conans.test.utils.mocks import ConanFileMock, MockSettings, MockOptions from conans.test.utils.test_files import temp_folder from conan.tools.apple import is_apple_os, to_apple_arch, fix_apple_shared_install_name, XCRun -from conan.tools.apple.apple import _get_dylib_install_name, _is_universal_arch +from conan.tools.apple.apple import _get_dylib_install_name def test_tools_apple_is_apple_os(): @@ -90,6 +91,6 @@ def test_is_universal_arch(settings_value, result): valid_definitions = ["arm64", "x86_64", "armv7", "x86"] if result is None: with pytest.raises(ConanException): - _is_universal_arch(settings_value, valid_definitions) + is_universal_arch(settings_value, valid_definitions) else: - assert _is_universal_arch(settings_value, valid_definitions) == result + assert is_universal_arch(settings_value, valid_definitions) == result From 2d8961ce59a549028b1a5481782551a7eb638cf9 Mon Sep 17 00:00:00 2001 From: czoido Date: Mon, 4 Mar 2024 14:50:59 +0100 Subject: [PATCH 16/17] protect for None arch --- conan/internal/internal_tools.py | 2 +- .../unittests/tools/apple/test_apple_tools.py | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/conan/internal/internal_tools.py b/conan/internal/internal_tools.py index 93da985150a..03a4cc0c01b 100644 --- a/conan/internal/internal_tools.py +++ b/conan/internal/internal_tools.py @@ -4,7 +4,7 @@ def is_universal_arch(settings_value, valid_definitions): - if settings_value is None: + if settings_value is None or valid_definitions is None or universal_arch_separator not in settings_value: return False parts = settings_value.split(universal_arch_separator) diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py index 9807156511d..0b797bc1d2c 100644 --- a/conans/test/unittests/tools/apple/test_apple_tools.py +++ b/conans/test/unittests/tools/apple/test_apple_tools.py @@ -76,19 +76,20 @@ def test_get_dylib_install_name(): assert "/absolute/path/lib/libwebp.7.dylib" == install_name -@pytest.mark.parametrize("settings_value,result", [ - ("arm64|x86_64", True), - ("x86_64|arm64", None), - ("armv7|x86", True), - ("x86|armv7", None), - (None, False), - ("arm64|armv7|x86_64", True), - ("x86|arm64", None), - ("arm64|ppc32", False), +@pytest.mark.parametrize("settings_value,valid_definitions,result", [ + ("arm64|x86_64", ["arm64", "x86_64", "armv7", "x86"], True), + ("x86_64|arm64", ["arm64", "x86_64", "armv7", "x86"], None), + ("armv7|x86", ["arm64", "x86_64", "armv7", "x86"], True), + ("x86|armv7", ["arm64", "x86_64", "armv7", "x86"], None), + (None, ["arm64", "x86_64", "armv7", "x86"], False), + ("arm64|armv7|x86_64", ["arm64", "x86_64", "armv7", "x86"], True), + ("x86|arm64", ["arm64", "x86_64", "armv7", "x86"], None), + ("arm64|ppc32", None, False), + (None, None, False), + ("armv7|x86", None, False), ]) # None is for the exception case -def test_is_universal_arch(settings_value, result): - valid_definitions = ["arm64", "x86_64", "armv7", "x86"] +def test_is_universal_arch(settings_value, valid_definitions, result): if result is None: with pytest.raises(ConanException): is_universal_arch(settings_value, valid_definitions) From 79abacfc0220b1142a2d84e3151683e230739d49 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 4 Mar 2024 16:34:33 +0100 Subject: [PATCH 17/17] Update conans/test/unittests/tools/apple/test_apple_tools.py --- conans/test/unittests/tools/apple/test_apple_tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conans/test/unittests/tools/apple/test_apple_tools.py b/conans/test/unittests/tools/apple/test_apple_tools.py index 0b797bc1d2c..ee8586b713f 100644 --- a/conans/test/unittests/tools/apple/test_apple_tools.py +++ b/conans/test/unittests/tools/apple/test_apple_tools.py @@ -87,6 +87,7 @@ def test_get_dylib_install_name(): ("arm64|ppc32", None, False), (None, None, False), ("armv7|x86", None, False), + ("arm64", ["arm64", "x86_64"], False), ]) # None is for the exception case def test_is_universal_arch(settings_value, valid_definitions, result):