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] Honoring tools.android:ndk_path conf #16502

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 79 additions & 17 deletions conan/tools/gnu/autotoolstoolchain.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from conan.errors import ConanException
from conan.internal import check_duplicated_generator
from conan.internal.internal_tools import raise_on_universal_arch
Expand Down Expand Up @@ -60,18 +62,24 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
self._build = self._conanfile.conf.get("tools.gnu:build_triplet")
self._target = None

self.android_cross_flags = {}
self._is_cross_building = cross_building(self._conanfile)
if self._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')
arch_build = conanfile.settings_build.get_safe('arch')

compiler = self._conanfile.settings.get_safe("compiler")
if not self._host:
# 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")
elif 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"]
# Build triplet
if not self._build:
os_build = conanfile.settings_build.get_safe('os')
arch_build = conanfile.settings_build.get_safe('arch')
self._build = _get_gnu_triplet(os_build, arch_build, compiler=compiler)["triplet"]

sysroot = self._conanfile.conf.get("tools.build:sysroot")
Expand All @@ -96,6 +104,51 @@ def __init__(self, conanfile, namespace=None, prefix="/"):
self.apple_isysroot_flag = isysroot_flag
self.apple_min_version_flag = min_flag

def _resolve_android_cross_compilation(self):
# Issue related: https://github.com/conan-io/conan/issues/13443
ret = {}
if not self._is_cross_building or not self._conanfile.settings.get_safe("os") == "Android":
return ret
ndk_path = self._conanfile.conf.get("tools.android:ndk_path", check_type=str)
if ndk_path:
Copy link
Member

Choose a reason for hiding this comment

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

What if ndk_path is not defined? Isn't it mandatory in other places?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's true that if it's not defined, in other toolchains like Meson or CMake, it is raised as an exception, but do we want to do that here? It'd be a breaking change because people have always included all the needed flags via buildenv.

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',
'iOS': 'darwin',
'watchOS': 'darwin',
'tvOS': 'darwin',
'visionOS': 'darwin',
'FreeBSD': 'linux',
'Linux': 'linux',
'Windows': 'windows',
'WindowsCE': 'windows',
'WindowsStore': 'windows'
}.get(os_build, "linux")
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 = {
"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
}
return ret

def _get_msvc_runtime_flag(self):
flag = msvc_runtime_flag(self._conanfile)
if flag:
Expand Down Expand Up @@ -159,17 +212,26 @@ def defines(self):

def environment(self):
env = Environment()
compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
check_type=dict)
if compilers_by_conf:
compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC",
"rc": "RC"}
for comp, env_var in compilers_mapping.items():
if comp in compilers_by_conf:
compiler = compilers_by_conf[comp]
# https://github.com/conan-io/conan/issues/13780
compiler = unix_path(self._conanfile, compiler)
env.define(env_var, compiler)
# Setting Android cross-compilation flags (if exist)
if self.android_cross_flags:
for env_var, env_value in self.android_cross_flags.items():
unix_env_value = unix_path(self._conanfile, env_value)
env.define(env_var, unix_env_value)
else:
# Setting user custom compiler executables flags
compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables",
default={},
check_type=dict)
if compilers_by_conf:
compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC",
"rc": "RC"}
for comp, env_var in compilers_mapping.items():
if comp in compilers_by_conf:
compiler = compilers_by_conf[comp]
# https://github.com/conan-io/conan/issues/13780
compiler = unix_path(self._conanfile, compiler)
env.define(env_var, compiler)

env.append("CPPFLAGS", ["-D{}".format(d) for d in self.defines])
env.append("CXXFLAGS", self.cxxflags)
env.append("CFLAGS", self.cflags)
Expand Down
45 changes: 45 additions & 0 deletions test/functional/toolchains/gnu/autotools/test_android.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import platform
import textwrap

import pytest

from conan.test.assets.sources import gen_function_cpp, gen_function_h
from conan.test.utils.tools import TestClient
from test.conftest import tools_locations


@pytest.mark.parametrize("arch, expected_arch", [('armv8', 'aarch64'),
('armv7', 'arm'),
('x86', 'i386'),
('x86_64', 'x86_64')
])
@pytest.mark.tool("android_ndk")
@pytest.mark.tool("autotools")
@pytest.mark.skipif(platform.system() != "Darwin", reason="NDK only installed on MAC")
def test_android_meson_toolchain_cross_compiling(arch, expected_arch):
profile_host = textwrap.dedent("""
include(default)

[settings]
os = Android
os.api_level = 21
arch = {arch}

[conf]
tools.android:ndk_path={ndk_path}
""")
ndk_path = tools_locations["android_ndk"]["system"]["path"][platform.system()]
profile_host = profile_host.format(
arch=arch,
ndk_path=ndk_path
)

client = TestClient(path_with_spaces=False)
client.run("new autotools_lib -d name=hello -d version=1.0")
client.save({"profile_host": profile_host})
client.run("build . --profile:build=default --profile:host=profile_host")
libhello = os.path.join("build-release", "src", ".libs", "libhello.a")
# Check binaries architecture
client.run_command('objdump -f "%s"' % libhello)
assert "architecture: %s" % expected_arch in client.out