Skip to content

Commit

Permalink
refactor apple_min_version_flag() (#16017)
Browse files Browse the repository at this point in the history
* refactor apple_min_version_flag()

* Refactored all the apple module and where it was being used (AutotoolsToolchain and MesonToolchain for now)

* Fixed bad return

* Fixing tests

* Keeping legacy behavior in apple_min_version_flag function

* Preventing possible breaking change

---------

Co-authored-by: Francisco Ramirez de Anton <franchuti688@gmail.com>
  • Loading branch information
memsharded and franramirez688 authored Apr 8, 2024
1 parent 27a96d4 commit 0f16d0b
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 116 deletions.
84 changes: 50 additions & 34 deletions conan/tools/apple/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ def to_apple_arch(conanfile, default=None):
return _to_apple_arch(arch_, default)


def apple_sdk_path(conanfile):
def apple_sdk_path(conanfile, is_cross_building=True):
sdk_path = conanfile.conf.get("tools.apple:sdk_path")
if not sdk_path:
# XCRun already knows how to extract os.sdk from conanfile.settings
sdk_path = XCRun(conanfile).sdk_path
if not sdk_path and is_cross_building:
raise ConanException(
"Apple SDK path not found. For cross-compilation, you must "
"provide a valid SDK path in 'tools.apple:sdk_path' config."
)
return sdk_path


Expand All @@ -48,7 +53,6 @@ def get_apple_sdk_fullname(conanfile):
os_ = conanfile.settings.get_safe('os')
os_sdk = conanfile.settings.get_safe('os.sdk')
os_sdk_version = conanfile.settings.get_safe('os.sdk_version') or ""

if os_sdk:
return "{}{}".format(os_sdk, os_sdk_version)
elif os_ == "Macos": # it has only a single value for all the architectures
Expand All @@ -57,39 +61,51 @@ def get_apple_sdk_fullname(conanfile):
raise ConanException("Please, specify a suitable value for os.sdk.")


def apple_min_version_flag(os_version, os_sdk, subsystem):
def apple_min_version_flag(conanfile):
"""compiler flag name which controls deployment target"""
if not os_version or not os_sdk:
return ''

# FIXME: This guess seems wrong, nothing has to be guessed, but explicit
flag = ''
if 'macosx' in os_sdk:
flag = '-mmacosx-version-min'
elif 'iphoneos' in os_sdk:
flag = '-mios-version-min'
elif 'iphonesimulator' in os_sdk:
flag = '-mios-simulator-version-min'
elif 'watchos' in os_sdk:
flag = '-mwatchos-version-min'
elif 'watchsimulator' in os_sdk:
flag = '-mwatchos-simulator-version-min'
elif 'appletvos' in os_sdk:
flag = '-mtvos-version-min'
elif 'appletvsimulator' in os_sdk:
flag = '-mtvos-simulator-version-min'
elif 'xros' in os_sdk:
# need early return as string did not fit into the "normal" schema
return f'-target arm64-apple-xros{os_version}'
elif 'xrsimulator' in os_sdk:
# need early return as string did not fit into the "normal" schema
return f'-target arm64-apple-xros{os_version}-simulator'

if subsystem == 'catalyst':
# especial case, despite Catalyst is macOS, it requires an iOS version argument
flag = '-mios-version-min'

return f"{flag}={os_version}" if flag else ''
os_ = conanfile.settings.get_safe('os')
os_sdk = conanfile.settings.get_safe('os.sdk')
os_sdk = os_sdk or ("macosx" if os_ == "Macos" else None)
os_version = conanfile.settings.get_safe("os.version")
if not os_sdk or not os_version:
# Legacy behavior
return ""
if conanfile.settings.get_safe("os.subsystem") == 'catalyst':
os_sdk = "iphoneos"
return {
"macosx": f"-mmacosx-version-min={os_version}",
"iphoneos": f"-mios-version-min={os_version}",
"iphonesimulator": f"-mios-simulator-version-min={os_version}",
"watchos": f"-mwatchos-version-min={os_version}",
"watchsimulator": f"-mwatchos-simulator-version-min={os_version}",
"appletvos": f"-mtvos-version-min={os_version}",
"appletvsimulator": f"-mtvos-simulator-version-min={os_version}",
"xros": f"-target arm64-apple-xros{os_version}",
"xrsimulator": f"-target arm64-apple-xros{os_version}-simulator",
}.get(os_sdk, "")


def resolve_apple_flags(conanfile, is_cross_building=False):
"""
Gets the most common flags in Apple systems. If it's a cross-building context
SDK path is mandatory so if it could raise an exception if SDK is not found.
:param conanfile: <ConanFile> instance.
:param is_cross_building: boolean to indicate if it's a cross-building context.
:return: tuple of Apple flags (apple_min_version_flag, apple_arch, apple_isysroot_flag).
"""
if not is_apple_os(conanfile):
# Keeping legacy defaults
return "", None, None

apple_arch_flag = apple_isysroot_flag = None
if is_cross_building:
arch = to_apple_arch(conanfile)
sdk_path = apple_sdk_path(conanfile, is_cross_building=is_cross_building)
apple_isysroot_flag = f"-isysroot {sdk_path}" if sdk_path else ""
apple_arch_flag = f"-arch {arch}" if arch else ""
min_version_flag = apple_min_version_flag(conanfile)
return min_version_flag, apple_arch_flag, apple_isysroot_flag


class XCRun:
Expand Down
38 changes: 15 additions & 23 deletions conan/tools/gnu/autotoolstoolchain.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from conan.internal import check_duplicated_generator
from conan.internal.internal_tools import raise_on_universal_arch
from conan.tools.apple.apple import apple_min_version_flag, is_apple_os, to_apple_arch, apple_sdk_path
from conan.tools.apple.apple import get_apple_sdk_fullname
from conan.tools.apple.apple import apple_min_version_flag, is_apple_os, to_apple_arch, \
apple_sdk_path, resolve_apple_flags
from conan.tools.build import cmd_args_to_string, save_toolchain_args
from conan.tools.build.cross_building import cross_building
from conan.tools.build.flags import architecture_flag, build_type_flags, cppstd_flag, build_type_link_flags, libcxx_flags
Expand Down Expand Up @@ -59,16 +59,8 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
self._build = self._conanfile.conf.get("tools.gnu:build_triplet")
self._target = None

self.apple_arch_flag = self.apple_isysroot_flag = None

os_sdk = get_apple_sdk_fullname(conanfile)
os_version = conanfile.settings.get_safe("os.version")
subsystem = conanfile.settings.get_safe("os.subsystem")
self.apple_min_version_flag = apple_min_version_flag(os_version, os_sdk, subsystem)

self.sysroot_flag = None

if cross_building(self._conanfile):
is_cross_building = cross_building(self._conanfile)
if is_cross_building:
os_host = conanfile.settings.get_safe("os")
arch_host = conanfile.settings.get_safe("arch")
os_build = conanfile.settings_build.get_safe('os')
Expand All @@ -80,17 +72,6 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
# Build triplet
if not self._build:
self._build = _get_gnu_triplet(os_build, arch_build, compiler=compiler)
# Apple Stuff
if os_build == "Macos" and is_apple_os(conanfile):
# SDK path is mandatory for cross-building
sdk_path = apple_sdk_path(self._conanfile)
if not sdk_path:
raise ConanException("You must provide a valid SDK path for cross-compilation.")
apple_arch = to_apple_arch(self._conanfile)
# https://man.archlinux.org/man/clang.1.en#Target_Selection_Options
self.apple_arch_flag = "-arch {}".format(apple_arch) if apple_arch else None
# -isysroot makes all includes for your library relative to the build directory
self.apple_isysroot_flag = "-isysroot {}".format(sdk_path) if sdk_path else None

sysroot = self._conanfile.conf.get("tools.build:sysroot")
sysroot = sysroot.replace("\\", "/") if sysroot is not None else None
Expand All @@ -101,6 +82,17 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
self._get_triplets()
self.autoreconf_args = self._default_autoreconf_flags()
self.make_args = []
# Apple stuff
is_cross_building_osx = (is_cross_building
and conanfile.settings_build.get_safe('os') == "Macos"
and is_apple_os(conanfile))
min_flag, arch_flag, isysroot_flag = resolve_apple_flags(conanfile,
is_cross_building=is_cross_building_osx)
# https://man.archlinux.org/man/clang.1.en#Target_Selection_Options
self.apple_arch_flag = arch_flag
# -isysroot makes all includes for your library relative to the build directory
self.apple_isysroot_flag = isysroot_flag
self.apple_min_version_flag = min_flag

def _get_msvc_runtime_flag(self):
flag = msvc_runtime_flag(self._conanfile)
Expand Down
21 changes: 6 additions & 15 deletions conan/tools/meson/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from conan.internal import check_duplicated_generator
from conan.internal.internal_tools import raise_on_universal_arch
from conan.tools.apple.apple import to_apple_arch, is_apple_os, apple_min_version_flag, \
apple_sdk_path, get_apple_sdk_fullname
apple_sdk_path, resolve_apple_flags
from conan.tools.build.cross_building import cross_building
from conan.tools.build.flags import libcxx_flags
from conan.tools.env import VirtualBuildEnv
Expand Down Expand Up @@ -307,21 +307,12 @@ def _get_cpp_info_value(name):
def _resolve_apple_flags_and_variables(self, build_env, compilers_by_conf):
if not self._is_apple_system:
return
# SDK path is mandatory for cross-building
sdk_path = apple_sdk_path(self._conanfile)
if not sdk_path and self.cross_build:
raise ConanException(
"Apple SDK path not found. For cross-compilation, you must "
"provide a valid SDK path in 'tools.apple:sdk_path' config."
)
# Calculating the main Apple flags
os_sdk = get_apple_sdk_fullname(self._conanfile)
arch = to_apple_arch(self._conanfile)
self.apple_arch_flag = ["-arch", arch] if arch else []
self.apple_isysroot_flag = ["-isysroot", sdk_path] if sdk_path else []
os_version = self._conanfile.settings.get_safe("os.version")
subsystem = self._conanfile.settings.get_safe("os.subsystem")
self.apple_min_version_flag = [apple_min_version_flag(os_version, os_sdk, subsystem)]
min_flag, arch_flag, isysroot_flag = (
resolve_apple_flags(self._conanfile, is_cross_building=bool(self.cross_build)))
self.apple_arch_flag = arch_flag.split() if arch_flag else []
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
self.objc = compilers_by_conf.get("objc", "clang")
self.objcpp = compilers_by_conf.get("objcpp", "clang++")
Expand Down
56 changes: 56 additions & 0 deletions conans/test/integration/tools/test_apple_xcrun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import platform

import pytest

from conan.tools.apple.apple import XCRun
from conans.test.utils.mocks import ConanFileMock, MockSettings
from conans.util.runners import conan_run


@pytest.mark.skipif(platform.system() != "Darwin", reason="Requires OSX and xcrun tool")
def test_xcrun():
def _common_asserts(xcrun_):
assert xcrun_.cc.endswith('clang')
assert xcrun_.cxx.endswith('clang++')
assert xcrun_.ar.endswith('ar')
assert xcrun_.ranlib.endswith('ranlib')
assert xcrun_.strip.endswith('strip')
assert xcrun_.find('lipo').endswith('lipo')
assert os.path.isdir(xcrun_.sdk_path)

conanfile = ConanFileMock( runner=conan_run)
conanfile.settings = MockSettings(
{"os": "Macos",
"arch": "x86"})
xcrun = XCRun(conanfile)
_common_asserts(xcrun)

conanfile.settings = MockSettings(
{"os": "iOS",
"arch": "x86"})
xcrun = XCRun(conanfile, sdk='macosx')
_common_asserts(xcrun)
# Simulator
assert "iPhoneOS" not in xcrun.sdk_path

conanfile.settings = MockSettings(
{"os": "iOS",
"os.sdk": "iphoneos",
"arch": "armv7"})
xcrun = XCRun(conanfile)
_common_asserts(xcrun)
assert "iPhoneOS" in xcrun.sdk_path

conanfile.settings = MockSettings(
{"os": "watchOS",
"os.sdk": "watchos",
"arch": "armv7"})
xcrun = XCRun(conanfile)
_common_asserts(xcrun)
assert "WatchOS" in xcrun.sdk_path

# Default one
conanfile.settings = MockSettings({})
xcrun = XCRun(conanfile)
_common_asserts(xcrun)
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def test_crossbuild_from_macos_to_non_apple_os():
conanfile.settings = MockSettings({"os": "Android", "arch": "armv8"})
conanfile.settings_build = MockSettings({"os": "Macos", "arch": "armv8"})
be = AutotoolsToolchain(conanfile)
assert be.apple_min_version_flag == ''
assert be.apple_min_version_flag == ""
assert be.apple_arch_flag is None
assert be.apple_isysroot_flag is None

Expand Down
52 changes: 9 additions & 43 deletions conans/test/unittests/util/apple_test.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import platform
import unittest

import pytest

from conan.tools.apple.apple import _to_apple_arch, apple_min_version_flag, \
is_apple_os, XCRun
is_apple_os
from conans.test.utils.mocks import MockSettings, ConanFileMock
from conans.util.runners import conan_run


class FakeSettings(object):
Expand Down Expand Up @@ -48,11 +45,17 @@ class TestApple:
'-mtvos-simulator-version-min=10.1'),
("Macos", "10.1", "macosx", "catalyst", '-mios-version-min=10.1'),
("Solaris", "10.1", None, None, ''),
("Macos", "10.1", None, None, ''),
("Macos", "10.1", None, None, '-mmacosx-version-min=10.1'),
("Macos", None, "macosx", None, '')
])
def test_deployment_target_flag_name(self, os_, version, sdk, subsystem, flag):
assert apple_min_version_flag(version, sdk, subsystem) == flag
conanfile = ConanFileMock()
settings = MockSettings({"os": os_,
"os.version": version,
"os.sdk": sdk,
"os.subsystem": subsystem})
conanfile.settings = settings
assert apple_min_version_flag(conanfile) == flag


class AppleTest(unittest.TestCase):
Expand Down Expand Up @@ -82,40 +85,3 @@ def test_to_apple_arch(self):
self.assertEqual(_to_apple_arch('armv8_32'), 'arm64_32')
self.assertIsNone(_to_apple_arch('mips'))
self.assertEqual(_to_apple_arch('mips', default='mips'), 'mips')

@pytest.mark.skipif(platform.system() != "Darwin", reason="Requires OSX")
def test_xcrun(self):

def _common_asserts(xcrun_):
self.assertTrue(xcrun_.cc.endswith('clang'))
self.assertTrue(xcrun_.cxx.endswith('clang++'))
self.assertTrue(xcrun_.ar.endswith('ar'))
self.assertTrue(xcrun_.ranlib.endswith('ranlib'))
self.assertTrue(xcrun_.strip.endswith('strip'))
self.assertTrue(xcrun_.find('lipo').endswith('lipo'))
self.assertTrue(os.path.isdir(xcrun_.sdk_path))

conanfile = ConanFileMock(settings=FakeSettings('Macos', 'x86'), runner=conan_run)
xcrun = XCRun(conanfile)
_common_asserts(xcrun)

conanfile.settings = FakeSettings('iOS', 'x86')
xcrun = XCRun(conanfile, sdk='macosx')
_common_asserts(xcrun)
# Simulator
self.assertNotIn("iPhoneOS", xcrun.sdk_path)

conanfile.settings = FakeSettings('iOS', 'armv7', os_sdk="iphoneos")
xcrun = XCRun(conanfile)
_common_asserts(xcrun)
self.assertIn("iPhoneOS", xcrun.sdk_path)

conanfile.settings = FakeSettings('watchOS', 'armv7', os_sdk="watchos")
xcrun = XCRun(conanfile)
_common_asserts(xcrun)
self.assertIn("WatchOS", xcrun.sdk_path)

# Default one
conanfile.settings = FakeSettings(None, None)
xcrun = XCRun(conanfile)
_common_asserts(xcrun)

0 comments on commit 0f16d0b

Please sign in to comment.