Skip to content

[6.2][build] Make it possible to build a cross-compilation toolchain for Android, including Testing #83503

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

Open
wants to merge 4 commits into
base: release/6.2
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions stdlib/cmake/modules/AddSwiftStdlib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2504,6 +2504,8 @@ function(add_swift_target_library name)
list(APPEND swiftlib_link_flags_all "-shared")
# TODO: Instead of `lib${name}.so` find variable or target property which already have this value.
list(APPEND swiftlib_link_flags_all "-Wl,-soname,lib${name}.so")
# Ensure compatibility with Android 15+ devices using 16KB memory pages.
list(APPEND swiftlib_link_flags_all "-Wl,-z,max-page-size=16384")
endif()

if (SWIFTLIB_BACK_DEPLOYMENT_LIBRARY)
Expand Down
1 change: 1 addition & 0 deletions test/SILOptimizer/concat_string_literals.32.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// We have a separate test for 64-bit architectures.
// REQUIRES: PTRSIZE=32
// XFAIL: OS=linux-androideabi

// NOTE: 25185.byteSwapped = 0x62 'a', 0x61 'b'
// CHECK-LABEL: test_ascii_scalar_scalar2
Expand Down
6 changes: 6 additions & 0 deletions utils/build_swift/build_swift/driver_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,12 @@ def create_argument_parser():
"for each cross-compiled toolchain's destdir, useful when building "
"multiple toolchains and can be disabled if only cross-compiling one.")

option('--cross-compile-build-swift-tools', toggle_true,
default=True,
help="Cross-compile the Swift compiler, other host tools from the "
"compiler repository, and various macros for each listed "
"--cross-compile-hosts platform.")

option('--stdlib-deployment-targets', store,
type=argparse.ShellSplitType(),
default=None,
Expand Down
2 changes: 2 additions & 0 deletions utils/build_swift/tests/expected_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
'compiler_vendor': defaults.COMPILER_VENDOR,
'coverage_db': None,
'cross_compile_append_host_target_to_destdir': True,
'cross_compile_build_swift_tools': True,
'cross_compile_deps_path': None,
'cross_compile_hosts': [],
'infer_cross_compile_hosts_on_darwin': False,
Expand Down Expand Up @@ -622,6 +623,7 @@ class BuildScriptImplOption(_BaseOption):
EnableOption('--build-swift-clang-overlays'),
EnableOption('--build-swift-remote-mirror'),
EnableOption('--cross-compile-append-host-target-to-destdir'),
EnableOption('--cross-compile-build-swift-tools'),
EnableOption('--color-in-tests'),
EnableOption('--distcc'),
EnableOption('--sccache'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ def convert_to_impl_arguments(self):
"--cmake-generator", args.cmake_generator,
"--cross-compile-append-host-target-to-destdir", str(
args.cross_compile_append_host_target_to_destdir).lower(),
"--cross-compile-build-swift-tools", str(
args.cross_compile_build_swift_tools).lower(),
"--build-jobs", str(args.build_jobs),
"--lit-jobs", str(args.lit_jobs),
"--common-cmake-options=%s" % ' '.join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def is_verbose(self):
return self.args.verbose_build

def build_with_cmake(self, build_targets, build_type, build_args,
prefer_native_toolchain=False):
prefer_native_toolchain=False, build_llvm=True):
assert self.toolchain.cmake is not None
cmake_build = []
_cmake = cmake.CMake(self.args, self.toolchain,
Expand Down Expand Up @@ -71,9 +71,7 @@ def build_with_cmake(self, build_targets, build_type, build_args,
env=env)

is_llvm = self.product_name() == "llvm"
if (not is_llvm and not self.args.skip_build) or (
is_llvm and self.args._build_llvm
):
if (not is_llvm and not self.args.skip_build) or (is_llvm and build_llvm):
cmake_opts = [self.build_dir, "--config", build_type]

shell.call(
Expand Down
12 changes: 9 additions & 3 deletions utils/swift_build_support/swift_build_support/products/llvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,13 @@ def build(self, host_target):
# space/time efficient than -g on that platform.
llvm_cmake_options.define('LLVM_USE_SPLIT_DWARF:BOOL', 'YES')

if not self.args._build_llvm:
build = True
if not self.args._build_llvm or (not self.args.cross_compile_build_swift_tools
and self.is_cross_compile_target(host_target)):
# Indicating we don't want to build LLVM at all should
# override everything.
build_targets = []
build = False
elif self.args.skip_build or not self.args.build_llvm:
# We can't skip the build completely because the standalone
# build of Swift depends on these.
Expand Down Expand Up @@ -399,7 +402,8 @@ def build(self, host_target):

self._handle_cxx_headers(host_target, platform)

self.build_with_cmake(build_targets, self.args.llvm_build_variant, [])
self.build_with_cmake(build_targets, self.args.llvm_build_variant, [],
build_llvm=build)

# copy over the compiler-rt builtins for iOS/tvOS/watchOS to ensure
# that Swift's stdlib can use compiler-rt builtins when targeting
Expand Down Expand Up @@ -484,7 +488,9 @@ def should_install(self, host_target):
Whether or not this product should be installed with the given
arguments.
"""
return self.args.install_llvm
return self.args.install_llvm and (
self.args.cross_compile_build_swift_tools or
not self.is_cross_compile_target(host_target))

def install(self, host_target):
"""
Expand Down
56 changes: 46 additions & 10 deletions utils/swift_build_support/swift_build_support/products/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def get_linux_target(self, platform, arch):
sysroot_arch, vendor, abi = self.get_linux_target_components(arch)
return '{}-{}-linux-{}'.format(sysroot_arch, vendor, abi)

def generate_linux_toolchain_file(self, platform, arch):
def generate_linux_toolchain_file(self, platform, arch, crosscompiling=True):
"""
Generates a new CMake tolchain file that specifies Linux as a target
platform.
Expand All @@ -402,18 +402,34 @@ def generate_linux_toolchain_file(self, platform, arch):

toolchain_args = {}

toolchain_args['CMAKE_SYSTEM_NAME'] = 'Linux'
toolchain_args['CMAKE_SYSTEM_PROCESSOR'] = arch
if crosscompiling:
if platform == "linux":
toolchain_args['CMAKE_SYSTEM_NAME'] = 'Linux'
toolchain_args['CMAKE_SYSTEM_PROCESSOR'] = arch
elif platform == "android":
toolchain_args['CMAKE_SYSTEM_NAME'] = 'Android'
toolchain_args['CMAKE_SYSTEM_VERSION'] = self.args.android_api_level
toolchain_args['CMAKE_SYSTEM_PROCESSOR'] = arch if not arch == 'armv7' \
else 'armv7-a'
toolchain_args['CMAKE_ANDROID_NDK'] = self.args.android_ndk
toolchain_args['CMAKE_FIND_ROOT_PATH'] = self.args.cross_compile_deps_path
# This is a workaround for a CMake 3.30+ bug,
# https://gitlab.kitware.com/cmake/cmake/-/issues/26154, and can
# be removed once that is fixed.
toolchain_args['CMAKE_SHARED_LINKER_FLAGS'] = '\"\"'

# We only set the actual sysroot if we are actually cross
# compiling. This is important since otherwise cmake seems to change the
# RUNPATH to be a relative rather than an absolute path, breaking
# certain cmark tests (and maybe others).
maybe_sysroot = self.get_linux_sysroot(platform, arch)
if maybe_sysroot is not None:
toolchain_args['CMAKE_SYSROOT'] = maybe_sysroot

target = self.get_linux_target(platform, arch)
if platform == "linux":
maybe_sysroot = self.get_linux_sysroot(platform, arch)
if maybe_sysroot is not None:
toolchain_args['CMAKE_SYSROOT'] = maybe_sysroot

target = self.get_linux_target(platform, arch)
elif platform == "android":
target = '%s-unknown-linux-android%s' % (arch, self.args.android_api_level)
if self.toolchain.cc.endswith('clang'):
toolchain_args['CMAKE_C_COMPILER_TARGET'] = target
if self.toolchain.cxx.endswith('clang++'):
Expand Down Expand Up @@ -459,10 +475,30 @@ def generate_toolchain_file_for_darwin_or_linux(
platform, arch,
macos_deployment_version=override_macos_deployment_version)
self.cmake_options.define('CMAKE_TOOLCHAIN_FILE:PATH', toolchain_file)
elif platform == "linux":
toolchain_file = self.generate_linux_toolchain_file(platform, arch)
elif platform == "linux" or platform == "android":
# Always cross-compile for linux, but not on Android, as a native
# compile on Android does not use the NDK and its CMake config.
cross_compile = platform == "linux" or \
self.is_cross_compile_target(host_target)
toolchain_file = self.generate_linux_toolchain_file(platform, arch,
cross_compile)
self.cmake_options.define('CMAKE_TOOLCHAIN_FILE:PATH', toolchain_file)

if cross_compile and platform == "android":
resource_dir = None
# build-script-impl products build before the install and use
# the Swift stdlib from the compiler build directory instead,
# while products built even before that currently do not support
# cross-compiling Swift.
if not self.is_before_build_script_impl_product() and \
not self.is_build_script_impl_product():
install_path = self.host_install_destdir(host_target) + \
self.args.install_prefix
resource_dir = '%s/lib/swift' % install_path
flags = targets.StdlibDeploymentTarget.get_target_for_name(
host_target).platform.swift_flags(self.args, resource_dir)
self.cmake_options.define('CMAKE_Swift_FLAGS', flags)

return toolchain_file

def get_openbsd_toolchain_file(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,11 @@ def install(self, host_target):
install_prefix = install_destdir + self.args.install_prefix

self.install_with_cmake(['install'], install_prefix)

@classmethod
def is_build_script_impl_product(cls):
return False

@classmethod
def is_before_build_script_impl_product(cls):
return False
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,24 @@ def should_clean(self, host_target):
return True

def should_build(self, host_target):
return True
build_macros = not self.is_cross_compile_target(host_target) or \
self.args.cross_compile_build_swift_tools
if not build_macros:
print("Skipping building Testing Macros for %s, because the host tools "
"are not being built" % host_target)
return build_macros

def should_test(self, host_target):
return False

def should_install(self, host_target):
return self.args.install_swift_testing_macros
install_macros = self.args.install_swift_testing_macros and \
(not self.is_cross_compile_target(host_target) or
self.args.cross_compile_build_swift_tools)
if self.args.install_swift_testing_macros and not install_macros:
print("Skipping installing Testing Macros for %s, because the host tools "
"are not being built" % host_target)
return install_macros

def _cmake_product(self, host_target):
build_root = os.path.dirname(self.build_dir)
Expand Down Expand Up @@ -121,3 +132,11 @@ def install(self, host_target):
install_prefix = install_destdir + self.args.install_prefix

self.install_with_cmake(['install'], install_prefix)

@classmethod
def is_build_script_impl_product(cls):
return False

@classmethod
def is_before_build_script_impl_product(cls):
return False
14 changes: 9 additions & 5 deletions utils/swift_build_support/swift_build_support/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def contains(self, target_name):
return True
return False

def swift_flags(self, args):
def swift_flags(self, args, resource_path=None):
"""
Swift compiler flags for a platform, useful for cross-compiling
"""
Expand Down Expand Up @@ -154,17 +154,21 @@ def uses_host_tests(self):
"""
return True

def swift_flags(self, args):
def swift_flags(self, args, resource_path=None):
flags = '-target %s-unknown-linux-android%s ' % (args.android_arch,
args.android_api_level)

flags += '-resource-dir %s/swift-%s-%s/lib/swift ' % (
args.build_root, self.name, args.android_arch)
if resource_path is not None:
flags += '-resource-dir %s ' % (resource_path)
else:
flags += '-resource-dir %s/swift-%s-%s/lib/swift ' % (
args.build_root, self.name, args.android_arch)

android_toolchain_path = self.ndk_toolchain_path(args)

flags += '-sdk %s/sysroot ' % (android_toolchain_path)
flags += '-tools-directory %s/bin' % (android_toolchain_path)
flags += '-tools-directory %s/bin ' % (android_toolchain_path)
flags += '-Xclang-linker -Wl,-z,max-page-size=16384'
return flags

def cmake_options(self, args):
Expand Down
16 changes: 9 additions & 7 deletions validation-test/BuildSystem/android_cross_compile.test
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# REQUIRES: standalone_build
# UNSUPPORTED: OS=macosx
# UNSUPPORTED: OS=ios
# UNSUPPORTED: OS=tvos
# UNSUPPORTED: OS=watchos

# RUN: %empty-directory(%t)
# RUN: SKIP_XCODE_VERSION_CHECK=1 SWIFT_BUILD_ROOT=%t %swift_src_root/utils/build-script --dry-run --cmake %cmake --libdispatch --cross-compile-hosts=android-aarch64 --skip-local-build --android --android-ndk %t/ndk/ --android-arch aarch64 2>&1 | %FileCheck %s
# RUN: SKIP_XCODE_VERSION_CHECK=1 SWIFT_BUILD_ROOT=%t %swift_src_root/utils/build-script --dry-run --cmake %cmake --swift-testing --swift-testing-macros --install-swift-testing-macros --install-llvm --cross-compile-hosts=android-aarch64 --cross-compile-build-swift-tools=False --android --android-ndk %t/ndk/ --android-arch aarch64 2>&1 | %FileCheck %s

# CHECK: -DCMAKE_Swift_FLAGS{{.*}}-target {{.*}}unknown-linux-android{{.*}} -sdk
# CHECK: -DCMAKE_SYSTEM_NAME=Android {{.*}} -DCMAKE_ANDROID_NDK
# CHECK: pushd {{.*}}/llvm-android-aarch64
# CHECK-NOT: cmake --build {{.*}}/llvm-android-aarch64 --config
# CHECK-NOT: cmake --build {{.*}}/llvm-android-aarch64 {{.*}} install-llvm
# CHECK: cmake {{.*}}-DSWIFT_INCLUDE_TOOLS:BOOL=FALSE{{.*}}/swift
# CHECK: Skipping building Testing Macros for android-aarch64, because the host tools are not being built
# CHECK: Skipping installing Testing Macros for android-aarch64, because the host tools are not being built
# CHECK: cmake {{.*}}-DCMAKE_TOOLCHAIN_FILE:PATH={{.*}}swifttesting-android-aarch64/BuildScriptToolchain.cmake
# CHECK: -DCMAKE_Swift_FLAGS=-target aarch64-unknown-linux-android{{.*}} -sdk
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# REQUIRES: standalone_build
# REQUIRES: OS=macosx

# RUN: %empty-directory(%t)
# RUN: mkdir -p %t
# RUN: SKIP_XCODE_VERSION_CHECK=1 SWIFT_BUILD_ROOT=%t %swift_src_root/utils/build-script --dry-run --install-all --cmake %cmake --skip-build-llvm --skip-build-swift 2>&1 | %FileCheck %s
# RUN: SKIP_XCODE_VERSION_CHECK=1 SWIFT_BUILD_ROOT=%t %swift_src_root/utils/build-script --dry-run --install-all --cmake %cmake --skip-build-llvm --skip-build-swift --cross-compile-hosts=android-aarch64 --skip-local-build --android --android-ndk %t/ndk/ 2>&1 | %FileCheck --check-prefix=ANDROID %s

# CHECK: DRY_RUN! Writing Toolchain file to path:{{.*}}BuildScriptToolchain.cmake
# CHECK: cmake {{.*}}-DCMAKE_TOOLCHAIN_FILE:PATH={{.*}}BuildScriptToolchain.cmake {{.*}}cmark

# ANDROID: DRY_RUN! Writing Toolchain file to path:{{.*}}cmark-android-aarch64/BuildScriptToolchain.cmake
# ANDROID: cmake {{.*}}-DCMAKE_TOOLCHAIN_FILE:PATH={{.*}}cmark-android-aarch64/BuildScriptToolchain.cmake
# ANDROID: -DCMAKE_Swift_FLAGS=-target aarch64-unknown-linux-android