godot-mono-builds/android.py
2022-07-08 10:30:28 +01:00

329 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import os.path
import subprocess
import sys
from options import *
from os_utils import *
import runtime
DEFAULT_NDK_VERSION = '23.2.8568313'
DEFAULT_CMAKE_VERSION = '3.18.1'
targets = ['armv7', 'arm64v8', 'x86', 'x86_64']
def get_min_api_version(target) -> str:
# Minimum API version should be in sync with Godot's platform/android/detect.py.
# Note: The minimum API version for arm64v8 and x86_64 is '21'
min_versions = {
'armv7': '19',
'arm64v8': '21',
'x86': '19',
'x86_64': '21',
}
return min_versions[target]
def check_for_android_ndk(opts: AndroidOpts):
if not os.path.exists(os.path.join(opts.android_sdk_root, 'ndk', opts.android_ndk_version)):
print("Attempting to install Android NDK version %s" % (opts.android_ndk_version))
sdkmanager = opts.android_sdk_root + "/cmdline-tools/latest/bin/sdkmanager"
if os.path.exists(sdkmanager):
sdk_args = "ndk;" + opts.android_ndk_version
subprocess.check_call([sdkmanager, sdk_args])
else:
print("ERROR: Cannot find %s. Please ensure ANDROID_SDK_ROOT is correct and cmdline-tools are installed" % (sdkmanager))
sys.exit(1)
def check_for_cmake(opts: AndroidOpts):
if not os.path.exists(os.path.join(opts.android_sdk_root, 'cmake', opts.android_cmake_version)):
print("Attempting to install CMake version %s" % (opts.android_cmake_version))
sdkmanager = opts.android_sdk_root + "/cmdline-tools/latest/bin/sdkmanager"
if os.path.exists(sdkmanager):
sdk_args = "cmake;" + opts.android_cmake_version
subprocess.check_call([sdkmanager, sdk_args])
else:
print("ERROR: Cannot find %s. Please ensure ANDROID_SDK_ROOT is correct and cmdline-tools are installed" % (sdkmanager))
sys.exit(1)
def get_api_version_or_min(opts: AndroidOpts, target: str) -> str:
min_api_version = get_min_api_version(target)
if int(opts.android_api_version) < int(min_api_version):
print('WARNING: API version %s is less than the minimum for platform %s; using %s' % (opts.android_api_version, target, min_api_version))
return min_api_version
return opts.android_api_version
def setup_android_target_template(env: dict, opts: AndroidOpts, target: str):
extra_target_envs = {
'armv7': {
'android-armeabi-v7a_CFLAGS': ['-D__POSIX_VISIBLE=201002', '-DSK_RELEASE', '-DNDEBUG', '-UDEBUG', '-fpic', '-march=armv7-a', '-mtune=cortex-a8', '-mfpu=vfp', '-mfloat-abi=softfp'],
'android-armeabi-v7a_CXXFLAGS': ['-D__POSIX_VISIBLE=201002', '-DSK_RELEASE', '-DNDEBUG', '-UDEBUG', '-fpic', '-march=armv7-a', '-mtune=cortex-a8', '-mfpu=vfp', '-mfloat-abi=softfp'],
'android-armeabi-v7a_LDFLAGS': ['-Wl,--fix-cortex-a8']
},
'arm64v8': {
'android-arm64-v8a_CFLAGS': ['-D__POSIX_VISIBLE=201002', '-DSK_RELEASE', '-DNDEBUG', '-UDEBUG', '-fpic', '-DL_cuserid=9', '-DANDROID64'],
'android-arm64-v8a_CXXFLAGS': ['-D__POSIX_VISIBLE=201002', '-DSK_RELEASE', '-DNDEBUG', '-UDEBUG', '-fpic', '-DL_cuserid=9', '-DANDROID64']
},
'x86': {},
'x86_64': {
'android-x86_64_CFLAGS': ['-DL_cuserid=9'],
'android-x86_64_CXXFLAGS': ['-DL_cuserid=9']
}
}
env.update(extra_target_envs[target])
if target == "armv7":
target_triple = "armv7a-linux-androideabi"
bin_utils = "arm-linux-androideabi"
elif target == "arm64v8":
target_triple = "aarch64-linux-android"
bin_utils = target_triple
elif target == "x86":
target_triple = "i686-linux-android"
bin_utils = target_triple
elif target == "x86_64":
target_triple = "x86_64-linux-android"
bin_utils = target_triple
if sys.platform.startswith("linux"):
host_subpath = "linux-x86_64"
elif sys.platform.startswith("darwin"):
host_subpath = "darwin-x86_64"
elif sys.platform.startswith("win"):
if platform.machine().endswith("64"):
host_subpath = "windows-x86_64"
else:
host_subpath = "windows"
cmake_path = os.path.join(opts.android_sdk_root, 'cmake', opts.android_cmake_version, 'bin')
ndk_path = os.path.join(opts.android_sdk_root, 'ndk', opts.android_ndk_version)
toolchain_path = os.path.join(ndk_path, 'toolchains/llvm/prebuilt', host_subpath)
compiler_path = os.path.join(toolchain_path, 'bin')
compiler_wrapper = target_triple + env['ANDROID_API_VERSION'] + '-'
bin_utils_path = os.path.join(toolchain_path, bin_utils, 'bin')
android_api = env['ANDROID_API_VERSION']
AR = os.path.join(compiler_path, 'llvm-ar')
AS = os.path.join(bin_utils_path, 'as')
CC = os.path.join(compiler_path, compiler_wrapper + 'clang')
CXX = os.path.join(compiler_path, compiler_wrapper + 'clang++')
LD = os.path.join(compiler_path, 'ld')
DLLTOOL = ''
OBJDUMP = os.path.join(compiler_path, 'llvm-objdump')
RANLIB = os.path.join(compiler_path, 'llvm-ranlib')
CMAKE = os.path.join(cmake_path, 'cmake')
STRIP = os.path.join(compiler_path, 'llvm-strip')
CPP = CC + ' -E'
CXXCPP = CXX + ' -E'
ccache_path = os.environ.get('CCACHE', '')
if ccache_path:
CC = '%s %s' % (ccache_path, CC)
CXX = '%s %s' % (ccache_path, CXX)
CPP = '%s %s' % (ccache_path, CPP)
CXXCPP = '%s %s' % (ccache_path, CXXCPP)
AC_VARS = [
'mono_cv_uscore=yes',
'ac_cv_func_sched_getaffinity=no',
'ac_cv_func_sched_setaffinity=no',
'ac_cv_func_shm_open_working_with_mmap=no'
]
CFLAGS, CXXFLAGS, CPPFLAGS, CXXCPPFLAGS, LDFLAGS = [], [], [], [], []
# On Android we use 'getApplicationInfo().nativeLibraryDir' as the libdir where Mono will look for shared objects.
# This path looks something like this: '/data/app-lib/{package_name-{num}}'. However, Mono does some relocation
# and the actual path it will look at will be '/data/app-lib/{package_name}-{num}/../lib', which doesn't exist.
# Cannot use '/data/data/{package_name}/lib' either, as '/data/data/{package_name}/lib/../lib' may result in
# permission denied. Therefore we just override 'MONO_RELOC_LIBDIR' here to avoid the relocation.
CPPFLAGS += ['-DMONO_RELOC_LIBDIR=\\\".\\\"']
CFLAGS += [
'-fstack-protector',
'-DMONODROID=1'
'-D__ANDROID_API__=' + android_api
]
CXXFLAGS += [
'-fstack-protector',
'-DMONODROID=1'
'-D__ANDROID_API__=' + android_api,
]
LDFLAGS += [
'-z', 'now', '-z', 'relro', '-z', 'noexecstack',
'-ldl', '-lm', '-llog', '-lc'
]
# Fixes this error: DllImport unable to load library 'dlopen failed: empty/missing DT_HASH in "libmono-native.so" (built with --hash-style=gnu?)'.
LDFLAGS += ['-Wl,--hash-style=both']
CONFIGURE_FLAGS = [
'--disable-boehm',
'--disable-executables',
'--disable-iconv',
'--disable-mcs-build',
'--disable-nls',
'--enable-dynamic-btls',
'--enable-maintainer-mode',
'--enable-minimal=ssa,portability,attach,verifier,full_messages,sgen_remset'
',sgen_marksweep_par,sgen_marksweep_fixed,sgen_marksweep_fixed_par'
',sgen_copying,logging,security,shared_handles,interpreter',
'--with-btls-android-ndk=%s' % ndk_path,
'--with-btls-android-api=%s' % android_api,
]
CONFIGURE_FLAGS += ['--enable-monodroid']
CONFIGURE_FLAGS += ['--with-btls-android-ndk-asm-workaround']
CONFIGURE_FLAGS += [
'--with-btls-android-cmake-toolchain=%s/build/cmake/android.toolchain.cmake' % ndk_path,
'--with-sigaltstack=yes',
'--with-tls=pthread',
'--without-ikvm-native',
'--disable-cooperative-suspend',
'--disable-hybrid-suspend',
'--disable-crash-reporting'
]
env['_android-%s_AR' % target] = AR
env['_android-%s_AS' % target] = AS
env['_android-%s_CC' % target] = CC
env['_android-%s_CXX' % target] = CXX
env['_android-%s_CPP' % target] = CPP
env['_android-%s_CXXCPP' % target] = CXXCPP
env['_android-%s_DLLTOOL' % target] = DLLTOOL
env['_android-%s_LD' % target] = LD
env['_android-%s_OBJDUMP' % target] = OBJDUMP
env['_android-%s_RANLIB' % target] = RANLIB
env['_android-%s_CMAKE' % target] = CMAKE
env['_android-%s_STRIP' % target] = STRIP
env['_android-%s_AC_VARS' % target] = AC_VARS
env['_android-%s_CFLAGS' % target] = CFLAGS
env['_android-%s_CXXFLAGS' % target] = CXXFLAGS
env['_android-%s_CPPFLAGS' % target] = CPPFLAGS
env['_android-%s_CXXCPPFLAGS' % target] = CXXCPPFLAGS
env['_android-%s_LDFLAGS' % target] = LDFLAGS
env['_android-%s_CONFIGURE_FLAGS' % target] = CONFIGURE_FLAGS
# Runtime template
runtime.setup_runtime_template(env, opts, 'android', target, target_triple)
def strip_libs(opts: AndroidOpts, product: str, target: str):
ndk_path = os.path.join(opts.android_sdk_root, 'ndk', opts.android_ndk_version)
toolchain_path = os.path.join(ndk_path, 'toolchains/llvm/prebuilt/linux-x86_64')
strip = os.path.join(toolchain_path, 'bin', 'llvm-strip')
install_dir = os.path.join(opts.install_dir, '%s-%s-%s' % (product, target, opts.configuration))
out_libs_dir = os.path.join(install_dir, 'lib')
lib_files = globs(('*.a', '*.so'), dirpath=out_libs_dir)
if len(lib_files):
run_command(strip, args=['--strip-unneeded'] + lib_files, name='strip')
def configure(opts: AndroidOpts, product: str, target: str):
env = { 'ANDROID_API_VERSION': get_api_version_or_min(opts, target) }
setup_android_target_template(env, opts, target)
if not os.path.isfile(os.path.join(opts.mono_source_root, 'configure')):
runtime.run_autogen(opts)
runtime.run_configure(env, opts, product, target)
def make(opts: AndroidOpts, product: str, target: str):
env = { 'ANDROID_API_VERSION': get_api_version_or_min(opts, target) }
build_dir = os.path.join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration))
make_args = make_default_args(opts)
make_args += ['-C', build_dir]
run_command('make', args=make_args, name='make')
run_command('make', args=['-C', '%s/mono' % build_dir, 'install'], name='make install mono')
run_command('make', args=['-C', '%s/support' % build_dir, 'install'], name='make install support')
run_command('make', args=['-C', '%s/data' % build_dir, 'install'], name='make install data')
if opts.strip_libs:
strip_libs(opts, product, target)
def clean(opts: AndroidOpts, product: str, target: str):
rm_rf(
os.path.join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration)),
os.path.join(opts.configure_dir, '%s-%s-%s.config.cache' % (product, target, opts.configuration)),
os.path.join(opts.install_dir, '%s-%s-%s' % (product, target, opts.configuration))
)
def main(raw_args):
import cmd_utils
from cmd_utils import custom_bool
from collections import OrderedDict
from typing import Callable
target_choices = targets + ['all-targets']
actions = OrderedDict()
actions['configure'] = configure
actions['make'] = make
actions['clean'] = clean
parser = cmd_utils.build_arg_parser(
description='Builds the Mono runtime for Android',
env_vars={ 'ANDROID_SDK_ROOT': 'Overrides default value for --android-sdk' }
)
home = os.environ.get('HOME')
android_sdk_default = os.environ.get('ANDROID_SDK_ROOT', os.path.join(home, 'Android/Sdk'))
default_help = 'default: %(default)s'
parser.add_argument('action', choices=['configure', 'make', 'clean'])
parser.add_argument('--target', choices=target_choices, action='append', required=True)
parser.add_argument('--android-sdk', default=android_sdk_default, help=default_help)
parser.add_argument('--android-ndk-version', default=DEFAULT_NDK_VERSION, help=default_help)
parser.add_argument('--android-api-version', default=get_min_api_version(targets[0]), help=default_help)
parser.add_argument('--android-cmake-version', default=DEFAULT_CMAKE_VERSION, help=default_help)
cmd_utils.add_runtime_arguments(parser, default_help)
args = parser.parse_args(raw_args)
input_action = args.action
input_targets = args.target
opts = android_opts_from_args(args)
if not os.path.isdir(opts.mono_source_root):
print('Mono sources directory not found: ' + opts.mono_source_root)
sys.exit(1)
check_for_android_ndk(opts)
check_for_cmake(opts)
build_targets = cmd_utils.expand_input_targets(input_targets, { 'all-targets': targets })
action = actions[input_action]
try:
for target in build_targets:
action(opts, 'android', target)
except BuildError as e:
sys.exit(e.message)
if __name__ == '__main__':
from sys import argv
main(argv[1:])