commit 1d00c2b489990374c0c192089c7f0208326be97b Author: Ignacio Etcheverry Date: Thu Jul 4 17:54:14 2019 +0200 Add README, LICENSE and Android build script diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2bfa55d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Godot Engine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2454bf --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Mono build scripts for Godot +This repository contains scripts for building the Mono runtime to use with Godot Engine + diff --git a/build_mono_android.py b/build_mono_android.py new file mode 100755 index 0000000..1645202 --- /dev/null +++ b/build_mono_android.py @@ -0,0 +1,597 @@ +#!/usr/bin/python + +from os import environ +from os.path import exists as path_exists, join as path_join, isfile, isdir, abspath +from sys import exit + + +class MonoBuildError(Exception): + '''Generic exception for custom build errors''' + def __init__(self, msg): + super(MonoBuildError, self).__init__(msg) + self.message = msg + + +def run_command(command, args=[], custom_env=None, name='command'): + def cmd_args_to_str(cmd_args): + return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args]) + + assert isinstance(command, str) and isinstance(args, list) + args = [command] + args + + import subprocess + try: + print('Running command \'%s\': %s' % (name, cmd_args_to_str(args))) + if custom_env is None: + subprocess.check_call(args) + else: + subprocess.check_call(args, env=custom_env) + print('Command \'%s\' completed successfully' % name) + except subprocess.CalledProcessError as e: + raise MonoBuildError('\'%s\' exited with error code: %s' % (name, e.returncode)) + + +# Creates the directory if no other file or directory with the same path exists +def mkdir_p(path): + from os import makedirs + if not path_exists(path): + print('creating directory: ' + path) + makedirs(path) + + +def chdir(path): + from os import chdir as os_chdir + print('entering directory: ' + path) + os_chdir(path) + + +# Remove files and/or directories recursively +def rm_rf(*paths): + from os import remove + from shutil import rmtree + for path in paths: + if isfile(path): + print('removing file: ' + path) + remove(path) + elif isdir(path): + print('removing directory and its contents: ' + path) + rmtree(path) + + +def globs(pathnames, dirpath='.'): + import glob + files = [] + for pathname in pathnames: + files.extend(glob.glob(path_join(dirpath, pathname))) + return files + + +TOOLCHAIN_NAME_FMT = '%s-api%s-clang' + +CONFIGURATION = None +RELEASE = None + +ANDROID_TOOLCHAINS_PREFIX = None + +ANDROID_SDK_ROOT = None +ANDROID_NDK_ROOT = None + +WITH_MONODROID = None +ENABLE_CXX = None +VERBOSE_MAKE = None +STRIP_LIBS = None + +CONFIGURE_DIR = None +INSTALL_DIR = None + +MONO_SOURCE_ROOT = None + +_ANDROID_API_VERSION = None +_ANDROID_CMAKE_VERSION = None + + +class AndroidTargetInfo: + archs = { + 'armeabi-v7a': 'arm', + 'arm64-v8a': 'arm64', + 'x86': 'x86', + 'x86_64': 'x86_64' + } + + abi_names = { + 'armeabi-v7a': 'arm-linux-androideabi', + 'arm64-v8a': 'aarch64-linux-android', + 'x86': 'i686-linux-android', + 'x86_64': 'x86_64-linux-android' + } + + host_triples = { + 'armeabi-v7a': 'armv5-linux-androideabi', + 'arm64-v8a': 'aarch64-linux-android', + 'x86': 'i686-linux-android', + 'x86_64': 'x86_64-linux-android' + } + + +def android_autodetect_cmake(): + from distutils.version import StrictVersion + from os import listdir + + sdk_cmake_basedir = path_join(ANDROID_SDK_ROOT, 'cmake') + versions = [] + + for entry in listdir(sdk_cmake_basedir): + if isdir(path_join(sdk_cmake_basedir, entry)): + try: + version = StrictVersion(entry) + versions += [version] + except ValueError: + continue # Not a version folder + + if len(versions) == 0: + raise MonoBuildError('Cannot auto-detect Android CMake version') + + lattest_version = str(sorted(versions)[-1]) + print('Auto-detected Android CMake version: ' + lattest_version) + + return lattest_version + + +def get_api_version_or_min(target): + min_versions = { 'arm64-v8a': '21', 'x86_64': '21' } + if target in min_versions and int(_ANDROID_API_VERSION) < int(min_versions[target]): + print('WARNING: %s is less than minimum platform for %s; using %s' % (_ANDROID_API_VERSION, target, min_versions[target])) + return min_versions[target] + return _ANDROID_API_VERSION + + +def get_android_cmake_version(): + return _ANDROID_CMAKE_VERSION if _ANDROID_CMAKE_VERSION != 'autodetect' else android_autodetect_cmake() + + +def setup_runtime_template(env, product, target, host_triple): + BITNESS = '' + if any(s in host_triple for s in ['i686', 'i386']): + BITNESS = '-m32' + elif 'x86_64' in host_triple: + BITNESS = '-m64' + + CFLAGS = [] + CFLAGS += ['-O2', '-g'] if RELEASE else ['-O0', '-ggdb3', '-fno-omit-frame-pointer'] + CFLAGS += env.get('_%s-%s_CFLAGS' % (product, target), []) + CFLAGS += env.get('%s-%s_CFLAGS' % (product, target), []) + CFLAGS += [BITNESS] if BITNESS else [] + + CXXFLAGS = [] + CXXFLAGS += ['-O2', '-g'] if RELEASE else ['-O0', '-ggdb3', '-fno-omit-frame-pointer'] + CXXFLAGS += env.get('_%s-%s_CXXFLAGS' % (product, target), []) + CXXFLAGS += env.get('%s-%s_CXXFLAGS' % (product, target), []) + CXXFLAGS += [BITNESS] if BITNESS else [] + + CPPFLAGS = [] + CPPFLAGS += ['-O2', '-g'] if RELEASE else ['-O0', '-ggdb3', '-fno-omit-frame-pointer'] + CPPFLAGS += env.get('_%s-%s_CPPFLAGS' % (product, target), []) + CPPFLAGS += env.get('%s-%s_CPPFLAGS' % (product, target), []) + CPPFLAGS += [BITNESS] if BITNESS else [] + + CXXCPPFLAGS = [] + CXXCPPFLAGS += ['-O2', '-g'] if RELEASE else ['-O0', '-ggdb3', '-fno-omit-frame-pointer'] + CXXCPPFLAGS += env.get('_%s-%s_CXXCPPFLAGS' % (product, target), []) + CXXCPPFLAGS += env.get('%s-%s_CXXCPPFLAGS' % (product, target), []) + CXXCPPFLAGS += [BITNESS] if BITNESS else [] + + LDFLAGS = [] + LDFLAGS += env.get('_%s-%s_LDFLAGS' % (product, target), []) + LDFLAGS += env.get('%s-%s_LDFLAGS' % (product, target), []) + + AC_VARS = [] + AC_VARS += env.get('_%s-%s_AC_VARS' % (product, target), []) + AC_VARS += env.get('%s-%s_AC_VARS' % (product, target), []) + + CONFIGURE_ENVIRONMENT = {} + + def append_product_env_var(var_name): + val = env.get('_%s-%s_%s' % (product, target, var_name), '') + if val: + CONFIGURE_ENVIRONMENT[var_name] = val + + append_product_env_var('AR') + append_product_env_var('AS') + append_product_env_var('CC') + append_product_env_var('CPP') + append_product_env_var('CXX') + append_product_env_var('CXXCPP') + append_product_env_var('DLLTOOL') + append_product_env_var('LD') + append_product_env_var('OBJDUMP') + append_product_env_var('RANLIB') + append_product_env_var('CMAKE') + append_product_env_var('STRIP') + + CONFIGURE_ENVIRONMENT['CFLAGS'] = CFLAGS + CONFIGURE_ENVIRONMENT['CXXFLAGS'] = CXXFLAGS + CONFIGURE_ENVIRONMENT['CPPFLAGS'] = CPPFLAGS + CONFIGURE_ENVIRONMENT['CXXCPPFLAGS'] = CXXCPPFLAGS + CONFIGURE_ENVIRONMENT['LDFLAGS'] = LDFLAGS + + CONFIGURE_ENVIRONMENT.update(env.get('_%s-%s_CONFIGURE_ENVIRONMENT' % (product, target), {})) + CONFIGURE_ENVIRONMENT.update(env.get('%s-%s_CONFIGURE_ENVIRONMENT' % (product, target), {})) + + CONFIGURE_FLAGS = [] + CONFIGURE_FLAGS += ['--host=%s' % host_triple] if host_triple else [] + CONFIGURE_FLAGS += ['--cache-file=%s/%s-%s-%s.config.cache' % (CONFIGURE_DIR, product, target, CONFIGURATION)] + CONFIGURE_FLAGS += ['--prefix=%s/%s-%s-%s' % (INSTALL_DIR, product, target, CONFIGURATION)] + CONFIGURE_FLAGS += ['--enable-cxx'] if ENABLE_CXX else [] + # CONFIGURE_FLAGS += env['_cross-runtime_%s-%s_CONFIGURE_FLAGS' % (product, target)] + CONFIGURE_FLAGS += env.get('_%s-%s_CONFIGURE_FLAGS' % (product, target), []) + CONFIGURE_FLAGS += env.get('%s-%s_CONFIGURE_FLAGS' % (product, target), []) + + env['_runtime_%s-%s_AC_VARS' % (product, target)] = AC_VARS + env['_runtime_%s-%s_CONFIGURE_ENVIRONMENT' % (product, target)] = CONFIGURE_ENVIRONMENT + env['_runtime_%s-%s_CONFIGURE_FLAGS' % (product, target)] = CONFIGURE_FLAGS + + +def setup_android_target_template(env, target): + extra_target_envs = { + 'armeabi-v7a': { + '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'] + }, + 'arm64-v8a': { + '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]) + + android_new_ndk = True + + with open(path_join(ANDROID_NDK_ROOT, 'source.properties')) as file: + for line in file: + line = line.strip() + if line.startswith('Pkg.Revision ') or line.startswith('Pkg.Revision='): + pkg_revision = line.split('=')[1].strip() + mayor = int(pkg_revision.split('.')[0]) + android_new_ndk = mayor >= 18 + break + + arch = AndroidTargetInfo.archs[target] + abi_name = AndroidTargetInfo.abi_names[target] + host_triple = AndroidTargetInfo.host_triples[target] + api = env['ANDROID_API_VERSION'] + + toolchain_path = path_join(ANDROID_TOOLCHAINS_PREFIX, TOOLCHAIN_NAME_FMT % (target, api)) + + tools_path = path_join(toolchain_path, 'bin') + name_fmt = abi_name + '-%s' + + sdk_cmake_dir = path_join(ANDROID_SDK_ROOT, 'cmake', get_android_cmake_version()) + if not isdir(sdk_cmake_dir): + print('Android CMake directory \'%s\' not found' % sdk_cmake_dir) + + AR = path_join(tools_path, name_fmt % 'ar') + AS = path_join(tools_path, name_fmt % 'as') + CC = path_join(tools_path, name_fmt % 'clang') + CXX = path_join(tools_path, name_fmt % 'clang++') + DLLTOOL = '' + LD = path_join(tools_path, name_fmt % 'ld') + OBJDUMP = path_join(tools_path, name_fmt % 'objdump') + RANLIB = path_join(tools_path, name_fmt % 'ranlib') + CMAKE = path_join(sdk_cmake_dir, 'bin', 'cmake') + STRIP = path_join(tools_path, name_fmt % 'strip') + + CPP = path_join(tools_path, name_fmt % 'cpp') + if not isfile(CPP): + CPP = path_join(tools_path, (name_fmt % 'clang')) + CPP += ' -E' + + CXXCPP = path_join(tools_path, name_fmt % 'cpp') + if not isfile(CXXCPP): + CXXCPP = path_join(tools_path, (name_fmt % 'clang++')) + CXXCPP += ' -E' + + ccache_path = 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'] + CFLAGS += ['-DMONODROID=1'] if WITH_MONODROID else [] + CFLAGS += ['-D__ANDROID_API__=' + api] if android_new_ndk else [] + CXXFLAGS += ['-fstack-protector'] + CXXFLAGS += ['-DMONODROID=1'] if WITH_MONODROID else [] + CXXFLAGS += ['-D__ANDROID_API__=' + api] if android_new_ndk else [] + + CPPFLAGS += ['-I%s/sysroot/usr/include' % toolchain_path] + CXXCPPFLAGS += ['-I%s/sysroot/usr/include' % toolchain_path] + + path_link = '%s/platforms/android-%s/arch-%s/usr/lib' % (ANDROID_NDK_ROOT, api, arch) + + LDFLAGS += [ + '-z', 'now', '-z', 'relro', '-z', 'noexecstack', + '-ldl', '-lm', '-llog', '-lc', '-lgcc', + '-Wl,-rpath-link=%s,-dynamic-linker=/system/bin/linker' % path_link, + '-L' + path_link + ] + + # 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' % ANDROID_NDK_ROOT, + '--with-btls-android-api=%s' % api, + ] + + CONFIGURE_FLAGS += ['--enable-monodroid'] if WITH_MONODROID else [] + CONFIGURE_FLAGS += ['--with-btls-android-ndk-asm-workaround'] if android_new_ndk else [] + + CONFIGURE_FLAGS += [ + '--with-btls-android-cmake-toolchain=%s/build/cmake/android.toolchain.cmake' % ANDROID_NDK_ROOT, + '--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 + + setup_runtime_template(env, 'android', target, host_triple) + + +def make_standalone_toolchain(target, api): + install_dir = path_join(ANDROID_TOOLCHAINS_PREFIX, TOOLCHAIN_NAME_FMT % (target, api)) + if isdir(path_join(install_dir, 'bin')): + return # Looks like it's already there, so no need to re-create it + command = path_join(ANDROID_NDK_ROOT, 'build', 'tools', 'make_standalone_toolchain.py') + args = ['--verbose', '--force', '--api=' + api, '--arch=' + AndroidTargetInfo.archs[target], + '--install-dir=' + install_dir] + run_command(command, args=args, name='make_standalone_toolchain') + + +def strip_libs(product, target, api): + toolchain_path = path_join(ANDROID_TOOLCHAINS_PREFIX, TOOLCHAIN_NAME_FMT % (target, api)) + + tools_path = path_join(toolchain_path, 'bin') + name_fmt = AndroidTargetInfo.abi_names[target] + '-%s' + + STRIP = path_join(tools_path, name_fmt % 'strip') + + install_dir = '%s/%s-%s-%s' % (INSTALL_DIR, product, target, CONFIGURATION) + out_libs_dir = 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(product, target): + env = { 'ANDROID_API_VERSION': get_api_version_or_min(target) } + + make_standalone_toolchain(target, env['ANDROID_API_VERSION']) + + setup_android_target_template(env, target) + + chdir(MONO_SOURCE_ROOT) + autogen_env = environ.copy() + autogen_env['NOCONFIGURE'] = '1' + run_command(path_join(MONO_SOURCE_ROOT, 'autogen.sh'), custom_env=autogen_env, name='autogen') + + build_dir = CONFIGURE_DIR + '/%s-%s-%s' % (product, target, CONFIGURATION) + mkdir_p(build_dir) + chdir(build_dir) + + def str_dict_val(val): + if isinstance(val, list): + return ' '.join(val) # No need for quotes + return val + + ac_vars = env['_runtime_%s-%s_AC_VARS' % (product, target)] + configure_env = env['_runtime_%s-%s_CONFIGURE_ENVIRONMENT' % (product, target)] + configure_env = [('%s=%s' % (key, str_dict_val(value))) for (key, value) in configure_env.items()] + configure_flags = env['_runtime_%s-%s_CONFIGURE_FLAGS' % (product, target)] + + command = path_join(MONO_SOURCE_ROOT, 'configure') + configure_args = ac_vars + configure_env + configure_flags + + run_command(command, args=configure_args, name='configure') + + +def make(product, target): + env = { 'ANDROID_API_VERSION': get_api_version_or_min(target) } + + build_dir = CONFIGURE_DIR + '/%s-%s-%s' % (product, target, CONFIGURATION) + chdir(build_dir) + + make_args = ['V=1'] if VERBOSE_MAKE else [] + + run_command('make', args=make_args, name='make') + run_command('make', args=['install'], name='make install') + + if STRIP_LIBS: + strip_libs(product, target, env['ANDROID_API_VERSION']) + + +def clean(product, target): + rm_rf( + CONFIGURE_DIR + '/toolchains/%s-%s' % (product, target), + CONFIGURE_DIR + '/%s-%s-%s' % (product, target, CONFIGURATION), + CONFIGURE_DIR + '/%s-%s-%s.config.cache' % (product, target, CONFIGURATION), + INSTALL_DIR + '/%s-%s-%s' % (product, target, CONFIGURATION) + ) + + +def set_arguments(args): + global CONFIGURATION, RELEASE, ANDROID_TOOLCHAINS_PREFIX, ANDROID_SDK_ROOT, ANDROID_NDK_ROOT, \ + _ANDROID_API_VERSION, _ANDROID_CMAKE_VERSION, WITH_MONODROID, ENABLE_CXX, \ + VERBOSE_MAKE, STRIP_LIBS, CONFIGURE_DIR, INSTALL_DIR, MONO_SOURCE_ROOT, CONFIGURATION + + # Need to make paths absolute as we change cwd later + + CONFIGURATION = args.configuration + RELEASE = (CONFIGURATION == 'release') + ANDROID_TOOLCHAINS_PREFIX = abspath(args.toolchains_prefix) + ANDROID_SDK_ROOT = abspath(args.android_sdk) + ANDROID_NDK_ROOT = abspath(args.android_ndk) + WITH_MONODROID = args.with_monodroid + ENABLE_CXX = args.enable_cxx + VERBOSE_MAKE = args.verbose_make + STRIP_LIBS = args.strip_libs + CONFIGURE_DIR = abspath(args.configure_dir) + INSTALL_DIR = abspath(args.install_dir) + MONO_SOURCE_ROOT = abspath(args.mono_sources) + + _ANDROID_API_VERSION = args.android_api_version + _ANDROID_CMAKE_VERSION = args.android_cmake_version + + +def main(raw_args): + import argparse + + from collections import OrderedDict + from textwrap import dedent + + target_indiv_values = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] + target_values = target_indiv_values + ['all'] + + actions = OrderedDict() + actions['configure'] = configure + actions['make'] = make + actions['clean'] = clean + + parser = argparse.ArgumentParser( + description='Builds the Mono runtime for Android', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=dedent('''\ + environment variables: + ANDROID_SDK_ROOT: Overrides default value for --android-sdk + ANDROID_NDK_ROOT: Overrides default value for --android-ndk + MONO_SOURCE_ROOT: Overrides default value for --mono-sources + ANDROID_HOME: Same as ANDROID_SDK_ROOT + ''') + ) + + def custom_bool(val): + if isinstance(val, bool): + return val + if val.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif val.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + + home = environ.get('HOME') + android_sdk_default = environ.get('ANDROID_HOME', environ.get('ANDROID_SDK_ROOT', path_join(home, 'Android/Sdk'))) + android_ndk_default = environ.get('ANDROID_NDK_ROOT', path_join(android_sdk_default, 'ndk-bundle')) + mono_sources_default = environ.get('MONO_SOURCE_ROOT', '') + + default_help = dedent('default: %(default)s') + + parser.add_argument('action', choices=['configure', 'make', 'clean']) + parser.add_argument('--target', choices=target_values, action='append', required=True) + parser.add_argument('--configuration', choices=['release', 'debug'], default='release', help=default_help) + parser.add_argument('--toolchains-prefix', default=path_join(home, 'android-toolchains'), help=default_help) + parser.add_argument('--android-sdk', default=android_sdk_default, help=default_help) + parser.add_argument('--android-ndk', default=android_ndk_default, help=default_help) + parser.add_argument('--android-api-version', default='18', help=default_help) + parser.add_argument('--android-cmake-version', default='autodetect', help=default_help) + parser.add_argument('--enable-cxx', action='store_true', default=False, help=default_help) + parser.add_argument('--verbose-make', action='store_true', default=False, help=default_help) + parser.add_argument('--strip-libs', type=custom_bool, default=True, help=default_help) + parser.add_argument('--with-monodroid', type=custom_bool, default=True, help=default_help) + parser.add_argument('--configure-dir', default=path_join(home, 'mono-configs'), help=default_help) + parser.add_argument('--install-dir', default=path_join(home, 'mono-installs'), help=default_help) + + if mono_sources_default: + parser.add_argument('--mono-sources', default=mono_sources_default, help=default_help) + else: + parser.add_argument('--mono-sources', required=True) + + args = parser.parse_args(raw_args) + + action = args.action + targets = args.target + + set_arguments(args) + + if not isdir(MONO_SOURCE_ROOT): + print('Mono sources directory not found: ' + MONO_SOURCE_ROOT) + exit(1) + + android_targets = [] + + if 'all' in targets: + android_targets = target_indiv_values[:] + else: + for target in targets: + if not target in android_targets: + android_targets += [target] + + action_fn = actions[action] + + try: + for target in android_targets: + action_fn('android', target) + except MonoBuildError as e: + exit(e.message) + + +if __name__ == '__main__': + from sys import argv + main(argv[1:])