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

[AutotoolsToolchain] Improve Android cross-compilation UX #17470

Merged
merged 10 commits into from
Dec 18, 2024
Merged
47 changes: 34 additions & 13 deletions conan/tools/gnu/autotoolstoolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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, cstd_flag
from conan.tools.env import Environment
from conan.tools.env import Environment, VirtualBuildEnv
from conan.tools.gnu.get_gnu_triplet import _get_gnu_triplet
from conan.tools.microsoft import VCVars, msvc_runtime_flag, unix_path, check_min_vs, is_msvc
from conans.model.pkg_type import PackageType
Expand Down Expand Up @@ -69,10 +69,8 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
# If cross-building and tools.android:ndk_path is defined, let's try to guess the Android
# cross-building flags
self.android_cross_flags = self._resolve_android_cross_compilation()
# Host triplet
if self.android_cross_flags:
self._host = self.android_cross_flags.pop("host")
Copy link
Contributor Author

@franramirez688 franramirez688 Dec 17, 2024

Choose a reason for hiding this comment

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

elif not self._host:
# If it's not defined the triplet
if not self._host:
os_host = conanfile.settings.get_safe("os")
arch_host = conanfile.settings.get_safe("arch")
self._host = _get_gnu_triplet(os_host, arch_host, compiler=compiler)["triplet"]
Expand Down Expand Up @@ -109,12 +107,26 @@ def _resolve_android_cross_compilation(self):
ret = {}
if not self._is_cross_building or not self._conanfile.settings.get_safe("os") == "Android":
return ret
# User variables have more priority than Conan ones
build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars()
for var in ["CC", "CXX", "LD", "STRIP", "RANLIB", "AS", "AR", "ADDR2LINE", "NM", "OBJCOPY",
"OBJDUMP", "READELF", "ELFEDIT", "host"]:
if build_env.get(var) is not None:
ret[var] = build_env[var]

arch = self._conanfile.settings.get_safe("arch")
android_target = {'armv7': 'armv7a-linux-androideabi',
'armv8': 'aarch64-linux-android',
'x86': 'i686-linux-android',
'x86_64': 'x86_64-linux-android'}.get(arch)
# Setting host if it was not already defined yet
self._host = self._host or android_target
# Automatic guessing made by Conan (need the NDK path variable defined)
ndk_path = self._conanfile.conf.get("tools.android:ndk_path", check_type=str)
if ndk_path:
if self._conanfile.conf.get("tools.build:compiler_executables"):
self._conanfile.output.warning("tools.build:compiler_executables conf has no effect"
" when tools.android:ndk_path is defined too.")
arch = self._conanfile.settings.get_safe("arch")
os_build = self._conanfile.settings_build.get_safe("os")
ndk_os_folder = {
'Macos': 'darwin',
Expand All @@ -131,22 +143,31 @@ def _resolve_android_cross_compilation(self):
ndk_bin = os.path.join(ndk_path, "toolchains", "llvm", "prebuilt",
f"{ndk_os_folder}-x86_64", "bin")
android_api_level = self._conanfile.settings.get_safe("os.api_level")
android_target = {'armv7': 'armv7a-linux-androideabi',
'armv8': 'aarch64-linux-android',
'x86': 'i686-linux-android',
'x86_64': 'x86_64-linux-android'}.get(arch)
os_build = self._conanfile.settings_build.get_safe('os')
ext = ".cmd" if os_build == "Windows" else ""
ret = {
conan_vars = {
"CC": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang{ext}"),
"CXX": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang++{ext}"),
"LD": os.path.join(ndk_bin, "ld"),
"STRIP": os.path.join(ndk_bin, "llvm-strip"),
"RANLIB": os.path.join(ndk_bin, "llvm-ranlib"),
"AS": os.path.join(ndk_bin, f"{android_target}{android_api_level}-clang{ext}"),
"AR": os.path.join(ndk_bin, "llvm-ar"),
"host": android_target
"ADDR2LINE": os.path.join(ndk_bin, "llvm-addr2line"),
"NM": os.path.join(ndk_bin, "llvm-nm"),
"OBJCOPY": os.path.join(ndk_bin, "llvm-objcopy"),
"OBJDUMP": os.path.join(ndk_bin, "llvm-objdump"),
"READELF": os.path.join(ndk_bin, "llvm-readelf"),
"ELFEDIT": os.path.join(ndk_bin, "llvm-elfedit")
}
build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars()
for var in conan_vars:
# User variables have more priority than Conan ones
if build_env.get(var) is not None:
ret[var] = build_env[var]
# Do not take into account any deduced file if it does not exist
elif os.path.exists(conan_vars[var]):
ret.setdefault(var, conan_vars[var])
return ret

def _get_msvc_runtime_flag(self):
Expand All @@ -162,7 +183,7 @@ def _msvc_extra_flags(self):
return []

def _add_msvc_flags(self, flags):
# This is to avoid potential duplicate with users recipes -FS (alreday some in ConanCenter)
# This is to avoid potential duplicate with users recipes -FS (already some in ConanCenter)
return [f for f in self.msvc_extra_flags if f not in flags]

@staticmethod
Expand Down
1 change: 1 addition & 0 deletions conan/tools/meson/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ def _resolve_android_cross_compilation(self):
'x86_64': 'x86_64-linux-android'}.get(arch)
os_build = self._conanfile.settings_build.get_safe('os')
compiler_extension = ".cmd" if os_build == "Windows" else ""
# User has more prio than Conan
self.c = os.path.join(ndk_bin, "{}{}-clang{}".format(android_target, android_api_level, compiler_extension))
self.cpp = os.path.join(ndk_bin, "{}{}-clang++{}".format(android_target, android_api_level, compiler_extension))
self.ar = os.path.join(ndk_bin, "llvm-ar")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import os
import platform
from os import chdir
from unittest.mock import patch, MagicMock

import pytest

from conan.tools.build import load_toolchain_args
from conan.tools.gnu import AutotoolsToolchain
from conan.errors import ConanException
from conans.model.conf import Conf
from conan.test.utils.mocks import ConanFileMock, MockSettings, MockOptions
from conan.test.utils.test_files import temp_folder
from conan.tools.build import load_toolchain_args
from conan.tools.files import save
from conan.tools.gnu import AutotoolsToolchain
from conans.model.conf import Conf


def test_modify_environment():
"""We can alter the environment generated by the toolchain passing the env to the generate"""
f = temp_folder()
chdir(f)
os.chdir(f)
conanfile = ConanFileMock()
conanfile.settings = MockSettings({"os": "Linux", "arch": "x86_64"})
conanfile.settings_build = MockSettings({"os": "Solaris", "arch": "x86"})
Expand All @@ -31,7 +33,7 @@ def test_modify_environment():

def test_target_triple():
f = temp_folder()
chdir(f)
os.chdir(f)
conanfile = ConanFileMock()
conanfile.settings = MockSettings({"os": "Linux", "arch": "x86_64"})
conanfile.settings_build = MockSettings({"os": "Solaris", "arch": "x86"})
Expand Down Expand Up @@ -356,6 +358,7 @@ def test_apple_min_os_flag():
assert expected in env["LDFLAGS"]


@patch("conan.tools.gnu.autotoolstoolchain.VirtualBuildEnv", new=MagicMock(vars={}))
def test_crossbuild_from_macos_to_non_apple_os():
"""Check we are not adding Apple-specific flags
when the os_build is Macos, but we are targetting
Expand All @@ -369,6 +372,40 @@ def test_crossbuild_from_macos_to_non_apple_os():
assert be.apple_isysroot_flag is None


@patch("conan.tools.gnu.autotoolstoolchain.VirtualBuildEnv")
def test_crossbuild_to_android(build_env_mock):
"""
Issue related: https://github.com/conan-io/conan/issues/17441
"""
vars = MagicMock()
vars.vars.return_value = {"CC": "my-clang", "CXX": "my-clang++"}
build_env_mock.return_value = vars

conanfile = ConanFileMock()
conanfile.settings = MockSettings({"os": "Android", "arch": "armv8"})
conanfile.settings_build = MockSettings({"os": "Macos", "arch": "armv8"})
at = AutotoolsToolchain(conanfile)
env_vars = at.vars()
assert env_vars["CC"] == "my-clang"
assert env_vars["CXX"] == "my-clang++"
assert at._host == "aarch64-linux-android"
assert env_vars.get("LD") is None
assert env_vars.get("STRIP") is None

# Defining the ndk_path too
ndk_path = temp_folder()
ld_file = os.path.join(ndk_path, "toolchains", "llvm", "prebuilt", "darwin-x86_64", "bin", "ld")
save(conanfile, ld_file, "")
conanfile.conf.define("tools.android:ndk_path", ndk_path)
at = AutotoolsToolchain(conanfile)
env_vars = at.vars()
assert env_vars["CC"] == "my-clang"
assert env_vars["CXX"] == "my-clang++"
assert at._host == "aarch64-linux-android"
assert env_vars["LD"] == ld_file # exists
assert env_vars.get("STRIP") is None


def test_apple_isysrootflag():
"""Even when no cross building it is adjusted because it could target a Mac version"""
conanfile = ConanFileMock()
Expand Down
Loading