Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cross-build vars in CMakeToolchain #10856

Merged
merged 17 commits into from Mar 30, 2022
123 changes: 59 additions & 64 deletions conan/tools/cmake/toolchain/blocks.py
Expand Up @@ -6,7 +6,7 @@
from jinja2 import Template

from conan.tools._compilers import architecture_flag
from conan.tools.apple.apple import is_apple_os
from conan.tools.apple.apple import is_apple_os, to_apple_arch
from conan.tools.build import build_jobs
from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME
from conan.tools.cmake.utils import is_multi_configuration
Expand Down Expand Up @@ -319,12 +319,6 @@ def context(self):

class AppleSystemBlock(Block):
template = textwrap.dedent("""
{% if CMAKE_SYSTEM_NAME is defined %}
set(CMAKE_SYSTEM_NAME {{ CMAKE_SYSTEM_NAME }})
{% endif %}
{% if CMAKE_SYSTEM_VERSION is defined %}
set(CMAKE_SYSTEM_VERSION {{ CMAKE_SYSTEM_VERSION }})
{% endif %}
# Set the architectures for which to build.
set(CMAKE_OSX_ARCHITECTURES {{ CMAKE_OSX_ARCHITECTURES }} CACHE STRING "" FORCE)
# Setting CMAKE_OSX_SYSROOT SDK, when using Xcode generator the name is enough
Expand All @@ -336,17 +330,6 @@ class AppleSystemBlock(Block):
{% endif %}
""")

def _get_architecture(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"
arch = self._conanfile.settings.get_safe("arch")
return {"x86": "i386",
"x86_64": "x86_64",
"armv8": "arm64",
"armv8_32": "arm64_32"}.get(arch, arch)

def _apple_sdk_name(self):
"""
Returns the value for the SDKROOT with this preference:
Expand Down Expand Up @@ -377,20 +360,18 @@ def context(self):
if os_ not in ['Macos', 'iOS', 'watchOS', 'tvOS']:
return None

host_architecture = self._get_architecture()
arch = self._conanfile.settings.get_safe("arch")
host_architecture = to_apple_arch(arch)
host_os_version = self._conanfile.settings.get_safe("os.version")
host_sdk_name = self._apple_sdk_name()

ctxt_toolchain = {}
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 os_ in ('iOS', "watchOS", "tvOS"):
ctxt_toolchain["CMAKE_SYSTEM_NAME"] = os_
ctxt_toolchain["CMAKE_SYSTEM_VERSION"] = host_os_version

if host_os_version:
# https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html
# Despite the OSX part in the variable name(s) they apply also to other SDKs than
Expand Down Expand Up @@ -581,7 +562,6 @@ class GenericSystemBlock(Block):
set(CMAKE_SYSTEM_NAME {{ cmake_system_name }})
{% endif %}
{% if cmake_system_version %}
# Cross building
set(CMAKE_SYSTEM_VERSION {{ cmake_system_version }})
{% endif %}
{% if cmake_system_processor %}
Expand Down Expand Up @@ -670,46 +650,6 @@ def _get_generator_platform(self, generator):
"armv8": "ARM64"}.get(arch)
return None

def _get_cross_build(self):
user_toolchain = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_toolchain")
if user_toolchain is not None:
return None, None, None # Will be provided by user_toolchain

system_name = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_name")
system_version = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_version")
system_processor = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_processor")

settings = self._conanfile.settings
if hasattr(self._conanfile, "settings_build"):
os_ = settings.get_safe("os")
arch = settings.get_safe("arch")
settings_build = self._conanfile.settings_build
os_build = settings_build.get_safe("os")
arch_build = settings_build.get_safe("arch")

if system_name is None: # Try to deduce
# Handled by AppleBlock, or AndroidBlock, not here
if os_ not in ('Macos', 'iOS', 'watchOS', 'tvOS', 'Android'):
cmake_system_name_map = {"Neutrino": "QNX",
"": "Generic",
None: "Generic"}
if os_ != os_build:
system_name = cmake_system_name_map.get(os_, os_)
elif arch is not None and arch != arch_build:
if not ((arch_build == "x86_64") and (arch == "x86") or
(arch_build == "sparcv9") and (arch == "sparc") or
(arch_build == "ppc64") and (arch == "ppc32")):
system_name = cmake_system_name_map.get(os_, os_)

if system_name is not None and system_version is None:
system_version = settings.get_safe("os.version")

if system_name is not None and system_processor is None:
if arch != arch_build:
system_processor = arch

return system_name, system_version, system_processor

def _get_compiler(self, generator):
compiler = self._conanfile.settings.get_safe("compiler")
os_ = self._conanfile.settings.get_safe("os")
Expand All @@ -726,6 +666,61 @@ def _get_compiler(self, generator):

return compiler_c, compiler_cpp, compiler_rc

def _get_generic_system_name(self):
os_host = self._conanfile.settings.get_safe("os")
os_build = self._conanfile.settings_build.get_safe("os")
arch_host = self._conanfile.settings.get_safe("arch")
arch_build = self._conanfile.settings_build.get_safe("arch")
cmake_system_name_map = {"Neutrino": "QNX",
"": "Generic",
None: "Generic"}
if os_host != os_build:
return cmake_system_name_map.get(os_host, os_host)
elif arch_host is not None and arch_host != arch_build:
if not ((arch_build == "x86_64") and (arch_host == "x86") or
(arch_build == "sparcv9") and (arch_host == "sparc") or
(arch_build == "ppc64") and (arch_host == "ppc32")):
return cmake_system_name_map.get(os_host, os_host)

def _is_apple_cross_building(self):
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")
return os_host in ('iOS', 'watchOS', 'tvOS') or (os_host == 'Macos' and arch_host != arch_build)

def _get_cross_build(self):
user_toolchain = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_toolchain")
if user_toolchain is not None:
return None, None, None # Will be provided by user_toolchain

system_name = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_name")
system_version = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_version")
system_processor = self._conanfile.conf.get("tools.cmake.cmaketoolchain:system_processor")

if hasattr(self._conanfile, "settings_build"):
os_host = self._conanfile.settings.get_safe("os")
arch_host = self._conanfile.settings.get_safe("arch")
if system_name is None: # Try to deduce
_system_version = None
_system_processor = None
if self._is_apple_cross_building():
# cross-build in Macos also for M1
system_name = {'Macos': 'Darwin'}.get(os_host, os_host)
# CMAKE_SYSTEM_VERSION for Apple sets the sdk version, not the os version
_system_version = self._conanfile.settings.get_safe("os.sdk_version")
_system_processor = to_apple_arch(arch_host)
elif os_host != 'Android':
system_name = self._get_generic_system_name()
_system_version = self._conanfile.settings.get_safe("os.version")
_system_processor = arch_host

if system_name is not None and system_version is None:
system_version = _system_version
if system_name is not None and system_processor is None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the logic changed a bit here, before we were only setting system_processor in the case that if arch != arch_build: but I think we should set this variable anyway if we activated the CMake cross-compiling setting CMAKE_SYSTEM_NAME manually.

system_processor = _system_processor

return system_name, system_version, system_processor

def context(self):
# build_type (Release, Debug, etc) is only defined for single-config generators
generator = self._toolchain.generator
Expand Down
9 changes: 8 additions & 1 deletion conans/test/assets/cmake.py
Expand Up @@ -5,7 +5,8 @@

def gen_cmakelists(language="CXX", verify=True, project="project", libname="mylibrary",
libsources=None, appname="myapp", appsources=None, cmake_version="3.15",
install=False, find_package=None, libtype="", deps=None, public_header=None):
install=False, find_package=None, libtype="", deps=None, public_header=None,
custom_content=None):
"""
language: C, C++, C/C++
project: the project name
Expand Down Expand Up @@ -81,6 +82,11 @@ def gen_cmakelists(language="CXX", verify=True, project="project", libname="myli
install(TARGETS {{libname}})
{% endif %}
{% endif %}

{% if custom_content %}
{{custom_content}}
{% endif %}

""")

t = Template(cmake, trim_blocks=True, lstrip_blocks=True)
Expand All @@ -97,4 +103,5 @@ def gen_cmakelists(language="CXX", verify=True, project="project", libname="myli
"libtype": libtype,
"public_header": public_header,
"deps": deps,
"custom_content": custom_content
})
Expand Up @@ -227,4 +227,3 @@ def test_install_output_directories():
layout_folder = "cmake-build-release" if platform.system() != "Windows" else "build"
toolchain = client.load(os.path.join(b_folder, layout_folder, "conan", "conan_toolchain.cmake"))
assert 'set(CMAKE_INSTALL_LIBDIR "mylibs")' in toolchain

Expand Up @@ -29,7 +29,10 @@ def test_m1(op_system):
client.run("create . --profile:build=default --profile:host=m1 -tf None")

main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
cmakelists = gen_cmakelists(find_package=["hello"], appname="main", appsources=["main.cpp"])
custom_content = 'message("CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") \n' \
'message("CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") \n'
cmakelists = gen_cmakelists(find_package=["hello"], appname="main", appsources=["main.cpp"],
custom_content=custom_content)

conanfile = textwrap.dedent("""
from conans import ConanFile
Expand All @@ -56,6 +59,9 @@ def build(self):
"m1": profile}, clean_first=True)
client.run("install . --profile:build=default --profile:host=m1")
client.run("build .")
system_name = 'Darwin' if op_system == 'Macos' else 'iOS'
assert "CMAKE_SYSTEM_NAME: {}".format(system_name) in client.out
assert "CMAKE_SYSTEM_PROCESSOR: arm64" in client.out
main_path = "./cmake-build-release/main.app/main" if op_system == "iOS" \
else "./cmake-build-release/main"
client.run_command(main_path, assert_error=True)
Expand Down
66 changes: 66 additions & 0 deletions conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1,6 +1,9 @@
import os
import platform
import textwrap

import pytest

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient

Expand Down Expand Up @@ -229,3 +232,66 @@ def generate(self):
with open(os.path.join(client.current_folder, "conan_toolchain.cmake")) as f:
contents = f.read()
assert "/path/to/builddir" in contents


@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX")
def test_cmaketoolchain_cmake_system_processor_cross_apple():
"""
https://github.com/conan-io/conan/pull/10434
CMAKE_SYSTEM_PROCESSOR was not set when cross-building in Mac
"""
client = TestClient()
client.save({"hello.py": GenConanfile().with_name("hello")
.with_version("1.0")
.with_settings("os", "arch", "compiler", "build_type")})
profile_ios = textwrap.dedent("""
include(default)
[settings]
os=iOS
os.version=15.4
os.sdk=iphoneos
os.sdk_version=15.0
arch=armv8
""")
client.save({"profile_ios": profile_ios})
client.run("install hello.py -pr:h=./profile_ios -pr:b=default -g CMakeToolchain")
toolchain = client.load("conan_toolchain.cmake")
assert "set(CMAKE_SYSTEM_NAME iOS)" in toolchain
assert "set(CMAKE_SYSTEM_VERSION 15.0)" in toolchain
assert "set(CMAKE_SYSTEM_PROCESSOR arm64)" in toolchain


@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX")
def test_apple_vars_overwrite_user_conf():
"""
tools.cmake.cmaketoolchain:system_name and tools.cmake.cmaketoolchain:system_version
will be overwritten by the apple block
"""
client = TestClient()
client.save({"hello.py": GenConanfile().with_name("hello")
.with_version("1.0")
.with_settings("os", "arch", "compiler", "build_type")})
profile_ios = textwrap.dedent("""
include(default)
[settings]
os=iOS
os.version=15.4
os.sdk=iphoneos
os.sdk_version=15.0
arch=armv8
""")
client.save({"profile_ios": profile_ios})
client.run("install hello.py -pr:h=./profile_ios -pr:b=default -g CMakeToolchain "
"-c tools.cmake.cmaketoolchain:system_name=tvOS "
"-c tools.cmake.cmaketoolchain:system_version=15.1 "
"-c tools.cmake.cmaketoolchain:system_processor=x86_64 ")

toolchain = client.load("conan_toolchain.cmake")

# should set the conf values but system/version are overwritten by the apple block
assert "CMAKE_SYSTEM_NAME tvOS" in toolchain
assert "CMAKE_SYSTEM_NAME iOS" not in toolchain
assert "CMAKE_SYSTEM_VERSION 15.1" in toolchain
assert "CMAKE_SYSTEM_VERSION 15.0" not in toolchain
assert "CMAKE_SYSTEM_PROCESSOR x86_64" in toolchain
assert "CMAKE_SYSTEM_PROCESSOR armv8" not in toolchain