From e8ce81bf233384f6626418c92413c522642b56e1 Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Wed, 11 Mar 2020 01:23:59 +0100 Subject: [PATCH] Add iOS build script and fix builds with OSXCROSS --- README.md | 29 +- android.py | 21 +- bcl.py | 7 +- desktop.py | 80 +-- .../patches/mono_ios_asl_log_deprecated.diff | 14 + ios.py | 508 ++++++++++++++++++ llvm.py | 12 + options.py | 22 +- os_utils.py | 15 +- patch_mono.py | 3 +- print_env.sh | 13 + 11 files changed, 664 insertions(+), 60 deletions(-) mode change 100644 => 100755 desktop.py create mode 100644 files/patches/mono_ios_asl_log_deprecated.diff create mode 100755 ios.py mode change 100644 => 100755 options.py mode change 100644 => 100755 os_utils.py create mode 100755 print_env.sh diff --git a/README.md b/README.md index 8282e29..a189245 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This repository contains scripts for building the Mono runtime to use with Godot These scripts are based on the Mono [sdks](https://github.com/mono/mono/tree/master/sdks) makefiles, with some changes to work well with Godot. Some platforms or targets depend on files from the `sdks` directory in the Mono source repository. This directory may be missing from tarballs. If that's the case, cloning the git repository may be needed. [This table](https://www.mono-project.com/docs/about-mono/versioning/#mono-source-versioning) can be used to determine the branch for a specific version of Mono. +Some patches need to be applied to the Mono sources before building. This can be done by running `python ./patch_mono.py`. + Run `python SCRIPT.py --help` for the full list of command line options. By default, the scripts will install the resulting files to `$HOME/mono-installs`. @@ -22,7 +24,7 @@ export MONO_SOURCE_ROOT=$HOME/git/mono ### Notes - Python 3.7 or higher is required. -- Cross-compiling for macOS via osxcross is not yet supported. +- OSXCROSS is supported expect for building the Mono cross-compilers. - Building on Windows is not supported. It's possible to use Cygwin or WSL (Windows Subsystem for Linux) but this hasn't been tested. ## Desktop @@ -45,8 +47,6 @@ _AOT cross-compilers for desktop platforms cannot be built with these scripts ye ## Android -Some patches may need to be applied to the Mono sources before building for Android. This can be done by running `./patch_mono.py`. - ```bash # These are the default values. This step can be omitted if SDK and NDK root are in this location. export ANDROID_SDK_ROOT=$HOME/Android/Sdk @@ -67,6 +67,26 @@ export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk-bundle The option `--target=all-runtime` is a shortcut for `--target=armeabi-v7a --target=x86 --target=arm64-v8a --target=x86_64`. The equivalent applies for `all-cross` and `all-cross-win`. +# iOS + +```bash +# Build the runtime for the iPhone simulator. +./ios.py configure --target=x86_64 +./ios.py make --target=x86_64 + +# Build the runtime for the iPhone device. +./ios.py configure --target=arm64 +./ios.py make --target=arm64 + +# Build the AOT cross-compiler targeting the iPhone device. +./ios.py configure --target=cross-arm64 +./ios.py make --target=cross-arm64 +``` + +The runtime can also be built an OSXCROSS iOS toolchain. The `--ios-toolchain` and `--ios-sdk` options +are the equivalent of the Godot SCons options `IPHONEPATH` and `IPHONESDK` respectively. +The cross compiler cannot be built with OSXCROSS yet. + ## WebAssembly Just like with Godot, an active Emscripten SDK is needed for building the Mono WebAssembly runtime. @@ -93,6 +113,9 @@ _AOT cross-compilers for WebAssembly cannot be built with this script yet._ # Build the Android BCL. ./bcl.py make --product=android +# Build the iOS BCL. +./bcl.py make --product=ios + # Build the WebAssembly BCL. ./bcl.py make --product=wasm ``` diff --git a/android.py b/android.py index 7c72643..4eec595 100755 --- a/android.py +++ b/android.py @@ -173,11 +173,16 @@ def setup_android_target_template(env: dict, opts: AndroidOpts, target: str): # 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 opts.with_monodroid else [] + CFLAGS += [ + '-fstack-protector', + '-DMONODROID=1' + ] CFLAGS += ['-D__ANDROID_API__=' + api] if android_new_ndk else [] - CXXFLAGS += ['-fstack-protector'] - CXXFLAGS += ['-DMONODROID=1'] if opts.with_monodroid else [] + + CXXFLAGS += [ + '-fstack-protector', + '-DMONODROID=1' + ] CXXFLAGS += ['-D__ANDROID_API__=' + api] if android_new_ndk else [] CPPFLAGS += ['-I%s/sysroot/usr/include' % toolchain_path] @@ -210,7 +215,7 @@ def setup_android_target_template(env: dict, opts: AndroidOpts, target: str): '--with-btls-android-api=%s' % api, ] - CONFIGURE_FLAGS += ['--enable-monodroid'] if opts.with_monodroid else [] + CONFIGURE_FLAGS += ['--enable-monodroid'] CONFIGURE_FLAGS += ['--with-btls-android-ndk-asm-workaround'] if android_new_ndk else [] CONFIGURE_FLAGS += [ @@ -282,7 +287,7 @@ def get_android_libclang_path(opts): def setup_android_cross_template(env: dict, opts: AndroidOpts, target: str, host_arch: str): def get_host_triple(): if sys.platform == 'darwin': - return '%s-apple-darwin10' % host_arch + return '%s-apple-darwin11' % host_arch elif sys.platform in ['linux', 'linux2']: return '%s-linux-gnu' % host_arch assert False @@ -319,7 +324,7 @@ def setup_android_cross_template(env: dict, opts: AndroidOpts, target: str, host CXXFLAGS = [] CXXFLAGS += ['-DDEBUG_CROSS'] if not opts.release else [] - CXXFLAGS += ['-mmacosx-version-min=10.9 -stdlib=libc++'] if is_darwin else [] + CXXFLAGS += ['-mmacosx-version-min=10.9', '-stdlib=libc++'] if is_darwin else [] env['_android-%s_CXXFLAGS' % target] = CXXFLAGS @@ -469,7 +474,6 @@ def make(opts: AndroidOpts, product: str, target: str): def clean(opts: AndroidOpts, product: str, target: str): rm_rf( - path_join(opts.configure_dir, 'toolchains', '%s-%s' % (product, target)), path_join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration)), path_join(opts.configure_dir, '%s-%s-%s.config.cache' % (product, target, opts.configuration)), path_join(opts.install_dir, '%s-%s-%s' % (product, target, opts.configuration)) @@ -517,7 +521,6 @@ def main(raw_args): 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('--with-monodroid', type=custom_bool, default=True, help=default_help) cmd_utils.add_runtime_arguments(parser, default_help) diff --git a/bcl.py b/bcl.py index 04c280a..d12b3e1 100755 --- a/bcl.py +++ b/bcl.py @@ -11,17 +11,19 @@ from options import * from os_utils import * -product_values = ['desktop', 'desktop-win32', 'android', 'wasm'] +product_values = ['desktop', 'desktop-win32', 'android', 'ios', 'wasm'] profiles_table = { 'desktop': ['net_4_x'], 'desktop-win32': ['net_4_x'], 'android': ['monodroid', 'monodroid_tools'], + 'ios': ['monotouch', 'monotouch_runtime', 'monotouch_tools'], 'wasm': ['wasm', 'wasm_tools'] } test_profiles_table = { 'desktop': [], 'desktop-win32': [], 'android': ['monodroid', 'monodroid_tools'], + 'ios': ['monotouch'], 'wasm': ['wasm'] } @@ -94,9 +96,6 @@ def make_product(opts: BclOpts, product: str): mkdir_p(install_dir) - for profile in profiles: - mkdir_p('%s/%s' % (install_dir, profile)) - make_args = ['-C', build_dir, '-C', 'runtime', 'all-mcs', 'build_profiles=%s' % ' '.join(profiles)] make_args += ['V=1'] if opts.verbose_make else [] diff --git a/desktop.py b/desktop.py old mode 100644 new mode 100755 index 0714a44..bb32b6b --- a/desktop.py +++ b/desktop.py @@ -44,14 +44,10 @@ def is_cross_compiling(target_platform: str) -> bool: (sys.platform in ['linux', 'linux2', 'cygwin'] and target_platform != 'linux') -def get_osxcross_sdk(target, osxcross_bin): - osxcross_sdk = os.environ.get('OSXCROSS_SDK', 14) +def get_osxcross_sdk(osxcross_bin, arch): + osxcross_sdk = os.environ.get('OSXCROSS_SDK', 18) - name_fmt = path_join(osxcross_bin, target + '-apple-darwin%s-%s') - - if not 'OSXCROSS_SDK' in os.environ and not os.path.isfile(name_fmt % (osxcross_sdk, 'ar')): - # Default 14 wasn't it, try 15 - osxcross_sdk = 15 + name_fmt = path_join(osxcross_bin, arch + '-apple-darwin%s-%s') if not os.path.isfile(name_fmt % (osxcross_sdk, 'ar')): raise BuildError('Specify a valid osxcross SDK with the environment variable \'OSXCROSS_SDK\'') @@ -64,16 +60,24 @@ def setup_desktop_template(env: dict, opts: DesktopOpts, product: str, target_pl CONFIGURE_FLAGS = [ '--disable-boehm', - '--disable-iconv', '--disable-mcs-build', - '--disable-nls', - '--enable-dynamic-btls', '--enable-maintainer-mode', - '--with-sigaltstack=yes', '--with-tls=pthread', '--without-ikvm-native' ] + if target_platform == 'windows': + CONFIGURE_FLAGS += [ + '--with-libgdiplus=%s' % opts.mxe_prefix + ] + else: + CONFIGURE_FLAGS += [ + '--disable-iconv', + '--disable-nls', + '--enable-dynamic-btls', + '--with-sigaltstack=yes', + ] + if target_platform == 'windows': mxe_bin = path_join(opts.mxe_prefix, 'bin') @@ -92,27 +96,28 @@ def setup_desktop_template(env: dict, opts: DesktopOpts, product: str, target_pl env['_%s-%s_STRIP' % (product, target)] = name_fmt % 'strip' CONFIGURE_FLAGS += [ - '--enable-static-gcc-libs' + #'--enable-static-gcc-libs' ] - elif target_platform == 'osx' and 'OSXCROSS_ROOT' in os.environ: - osxcross_root = os.environ['OSXCROSS_ROOT'] - osxcross_bin = path_join(osxcross_root, 'target', 'bin') - osxcross_sdk = get_osxcross_sdk(target, osxcross_bin) + elif target_platform == 'osx': + if is_cross_compiling(target_platform): + osxcross_root = os.environ['OSXCROSS_ROOT'] + osxcross_bin = path_join(osxcross_root, 'target', 'bin') + osxcross_sdk = get_osxcross_sdk(osxcross_bin, arch=target) - env['_%s-%s_PATH' % (product, target)] = osxcross_bin + env['_%s-%s_PATH' % (product, target)] = osxcross_bin - name_fmt = path_join(osxcross_bin, target + ('-apple-darwin%s-' % osxcross_sdk) + '%s') + name_fmt = path_join(osxcross_bin, target + ('-apple-darwin%s-' % osxcross_sdk) + '%s') - env['_%s-%s_AR' % (product, target)] = name_fmt % 'ar' - env['_%s-%s_AS' % (product, target)] = name_fmt % 'as' - env['_%s-%s_CC' % (product, target)] = name_fmt % 'cc' - env['_%s-%s_CXX' % (product, target)] = name_fmt % 'c++' - env['_%s-%s_LD' % (product, target)] = name_fmt % 'ld' - env['_%s-%s_RANLIB' % (product, target)] = name_fmt % 'ranlib' - env['_%s-%s_CMAKE' % (product, target)] = name_fmt % 'cmake' - env['_%s-%s_STRIP' % (product, target)] = name_fmt % 'strip' - else: - env['_%s-%s_CC' % (product, target)] = 'cc' + env['_%s-%s_AR' % (product, target)] = name_fmt % 'ar' + env['_%s-%s_AS' % (product, target)] = name_fmt % 'as' + env['_%s-%s_CC' % (product, target)] = name_fmt % 'clang' + env['_%s-%s_CXX' % (product, target)] = name_fmt % 'clang++' + env['_%s-%s_LD' % (product, target)] = name_fmt % 'ld' + env['_%s-%s_RANLIB' % (product, target)] = name_fmt % 'ranlib' + env['_%s-%s_CMAKE' % (product, target)] = name_fmt % 'cmake' + env['_%s-%s_STRIP' % (product, target)] = name_fmt % 'strip' + else: + env['_%s-%s_CC' % (product, target)] = 'cc' env['_%s-%s_CONFIGURE_FLAGS' % (product, target)] = CONFIGURE_FLAGS @@ -122,19 +127,14 @@ def setup_desktop_template(env: dict, opts: DesktopOpts, product: str, target_pl def strip_libs(opts: DesktopOpts, product: str, target_platform: str, target: str): - if is_cross_compiling(target_platform): - if target_platform == 'windows': - mxe_bin = path_join(opts.mxe_prefix, 'bin') - name_fmt = path_join(mxe_bin, target + '-w64-mingw32-%s') - strip = name_fmt % 'strip' - elif target_platform == 'osx': - assert 'OSXCROSS_ROOT' in os.environ - osxcross_root = os.environ['OSXCROSS_ROOT'] - osxcross_bin = path_join(osxcross_bin, 'target', 'bin') - osxcross_sdk = get_osxcross_sdk(target, osxcross_bin) + if target_platform == 'osx': + # 'strip' doesn't support '--strip-unneeded' on macOS + return - name_fmt = path_join(osxcross_bin, target + ('-apple-darwin%s-' % osxcross_sdk) + '%s') - strip = name_fmt % 'strip' + if is_cross_compiling(target_platform) and target_platform == 'windows': + mxe_bin = path_join(opts.mxe_prefix, 'bin') + name_fmt = path_join(mxe_bin, target + '-w64-mingw32-%s') + strip = name_fmt % 'strip' else: strip = 'strip' diff --git a/files/patches/mono_ios_asl_log_deprecated.diff b/files/patches/mono_ios_asl_log_deprecated.diff new file mode 100644 index 0000000..7da99bc --- /dev/null +++ b/files/patches/mono_ios_asl_log_deprecated.diff @@ -0,0 +1,14 @@ +diff --git a/mono/utils/mono-log-darwin.c b/mono/utils/mono-log-darwin.c +index 3cb127bad59..30ff5edc307 100644 +--- a/mono/utils/mono-log-darwin.c ++++ b/mono/utils/mono-log-darwin.c +@@ -5,7 +5,8 @@ + */ + #include + +-#if defined(HOST_WATCHOS) && (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_3_0) ++#if (defined(HOST_WATCHOS) && (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_3_0)) \ ++ || (defined(HOST_IOS) && (__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 100000)) + /* emitted by clang: + * > /Users/lewurm/work/mono-watch4/mono/utils/mono-log-darwin.c:35:2: error: 'asl_log' is \ + * > deprecated: first deprecated in watchOS 3.0 - os_log(3) has replaced \ diff --git a/ios.py b/ios.py new file mode 100755 index 0000000..e457ee4 --- /dev/null +++ b/ios.py @@ -0,0 +1,508 @@ +#!/usr/bin/python + +import os +import os.path +import sys + +from os.path import join as path_join + +from options import * +from os_utils import * +import runtime + + +this_script_dir = os.path.dirname(os.path.realpath(__file__)) + +device_targets = ['armv7', 'arm64'] +sim_targets = ['i386', 'x86_64'] +cross_targets = ['cross-armv7', 'cross-arm64'] + + +def is_cross(target) -> bool: + return target in cross_targets + + +class iOSTargetTable: + archs = { + 'armv7': 'arm', + 'arm64': 'arm64', + 'i386': 'i386', + 'x86_64': 'x86_64' + } + + host_triples = { + 'armv7': 'arm-apple-darwin11', + 'arm64': 'aarch64-apple-darwin11', + 'i386': 'i386-apple-darwin11', + 'x86_64': 'x86_64-apple-darwin11' + } + + osxcross_tool_triples = { + 'armv7': 'arm-apple-darwin11', # TODO: ? + 'arm64': 'arm-apple-darwin11', + 'i386': 'i386-apple-darwin11', # TODO: ? + 'x86_64': 'x86_64-apple-darwin11' + } + + +def setup_ios_device_template(env: dict, opts: iOSOpts, target: str): + ios_sysroot_path = opts.ios_sdk_path + + if not ios_sysroot_path and sys.platform == 'darwin': + # Auto-detect on macOS + ios_sysroot_path = xcrun_find_sdk('iphoneos') + + if not ios_sysroot_path: + raise RuntimeError('Cannot find iOS SDK; specify one manually with \'--ios-sdk\'.') + + sysroot_flags = ['-isysroot', ios_sysroot_path, '-miphoneos-version-min=%s' % opts.ios_version_min] + + arch = iOSTargetTable.archs[target] + host_triple = iOSTargetTable.host_triples[target] + osxcross_tool_triple = iOSTargetTable.osxcross_tool_triples[target] + + tools_path = path_join(opts.ios_toolchain_path, 'usr', 'bin') + + if sys.platform != 'darwin': + name_fmt = path_join(tools_path, osxcross_tool_triple + '-%s') + else: + name_fmt = path_join(tools_path, '%s') + + AR = name_fmt % 'ar' + AS = name_fmt % 'as' + CC = name_fmt % 'clang' + CXX = name_fmt % 'clang++' + LD = name_fmt % 'ld' + RANLIB = name_fmt % 'ranlib' + STRIP = name_fmt % 'strip' + + ccache_path = os.environ.get('CCACHE', '') + if ccache_path: + CC = '%s %s' % (ccache_path, CC) + CXX = '%s %s' % (ccache_path, CXX) + + AC_VARS = [ + 'ac_cv_c_bigendian=no', + 'ac_cv_func_fstatat=no', + 'ac_cv_func_readlinkat=no', + 'ac_cv_func_getpwuid_r=no', + 'ac_cv_func_posix_getpwuid_r=yes', + 'ac_cv_header_curses_h=no', + 'ac_cv_header_localcharset_h=no', + 'ac_cv_header_sys_user_h=no', + 'ac_cv_func_getentropy=no', + 'ac_cv_func_futimens=no', + 'ac_cv_func_utimensat=no', + 'ac_cv_func_shm_open_working_with_mmap=no', + 'mono_cv_sizeof_sunpath=104', + 'mono_cv_uscore=yes' + ] + + bitcode_marker = env.get('ios-%s_BITCODE_MARKER' % target, '') + + CFLAGS = sysroot_flags + [ + '-arch %s' % arch, + '-Wl,-application_extension', + '-fexceptions' + ] + CFLAGS += [bitcode_marker] if bitcode_marker else [] + + CXXFLAGS = sysroot_flags + [ + '-arch %s' % arch, + '-Wl,-application_extension' + ] + CXXFLAGS += [bitcode_marker] if bitcode_marker else [] + + CPPFLAGS = sysroot_flags + [ + '-DMONOTOUCH=1', + '-arch %s' % arch, + '-DSMALL_CONFIG', '-D_XOPEN_SOURCE', '-DHOST_IOS', '-DHAVE_LARGE_FILE_SUPPORT=1' + ] + + LDFLAGS = [ + '-Wl,-no_weak_imports', + '-arch %s' % arch, + '-framework', 'CoreFoundation', + '-lobjc', '-lc++' + ] + + CONFIGURE_FLAGS = [ + '--disable-boehm', + '--disable-btls', + '--disable-executables', + '--disable-icall-tables', + '--disable-iconv', + '--disable-mcs-build', + '--disable-nls', + '--disable-visibility-hidden', + '--enable-dtrace=no', + '--enable-icall-export', + '--enable-maintainer-mode', + '--enable-minimal=ssa,com,interpreter,jit,portability,assembly_remapping,attach,verifier,' + + 'full_messages,appdomains,security,sgen_remset,sgen_marksweep_par,sgen_marksweep_fixed,' + + 'sgen_marksweep_fixed_par,sgen_copying,logging,remoting,shared_perfcounters,gac', + '--enable-monotouch', + # We don't need this. Comment it so we don't have to call 'mono_gc_init_finalizer_thread' from Godot. + #'--with-lazy-gc-thread-creation=yes', + '--with-tls=pthread', + '--without-ikvm-native', + '--without-sigaltstack', + '--disable-cooperative-suspend', + '--disable-hybrid-suspend', + '--disable-crash-reporting' + ] + + env['_ios-%s_AR' % target] = AR + env['_ios-%s_AS' % target] = AS + env['_ios-%s_CC' % target] = CC + env['_ios-%s_CXX' % target] = CXX + env['_ios-%s_LD' % target] = LD + env['_ios-%s_RANLIB' % target] = RANLIB + env['_ios-%s_STRIP' % target] = STRIP + + env['_ios-%s_AC_VARS' % target] = AC_VARS + env['_ios-%s_CFLAGS' % target] = CFLAGS + env['_ios-%s_CXXFLAGS' % target] = CXXFLAGS + env['_ios-%s_CPPFLAGS' % target] = CPPFLAGS + env['_ios-%s_LDFLAGS' % target] = LDFLAGS + env['_ios-%s_CONFIGURE_FLAGS' % target] = CONFIGURE_FLAGS + + # Runtime template + runtime.setup_runtime_template(env, opts, 'ios', target, host_triple) + + +def setup_ios_simulator_template(env: dict, opts: iOSOpts, target: str): + ios_sysroot_path = opts.ios_sdk_path + + if not ios_sysroot_path and sys.platform == 'darwin': + # Auto-detect on macOS + ios_sysroot_path = xcrun_find_sdk('iphonesimulator') + + if not ios_sysroot_path: + raise RuntimeError('Cannot find iOS SDK; specify one manually with \'--ios-sdk\'.') + + sysroot_flags = ['-isysroot', ios_sysroot_path, '-miphoneos-version-min=%s' % opts.ios_version_min] + + arch = iOSTargetTable.archs[target] + host_triple = iOSTargetTable.host_triples[target] + osxcross_tool_triple = iOSTargetTable.osxcross_tool_triples[target] + + tools_path = path_join(opts.ios_toolchain_path, 'usr', 'bin') + + if sys.platform != 'darwin': + name_fmt = path_join(tools_path, osxcross_tool_triple + '-%s') + else: + name_fmt = path_join(tools_path, '%s') + + AR = name_fmt % 'ar' + AS = name_fmt % 'as' + CC = name_fmt % 'clang' + CXX = name_fmt % 'clang++' + LD = name_fmt % 'ld' + RANLIB = name_fmt % 'ranlib' + STRIP = name_fmt % 'strip' + + ccache_path = os.environ.get('CCACHE', '') + if ccache_path: + CC = '%s %s' % (ccache_path, CC) + CXX = '%s %s' % (ccache_path, CXX) + + AC_VARS = [ + 'ac_cv_func_clock_nanosleep=no', + 'ac_cv_func_fstatat=no', + 'ac_cv_func_readlinkat=no', + 'ac_cv_func_system=no', + 'ac_cv_func_getentropy=no', + 'ac_cv_func_futimens=no', + 'ac_cv_func_utimensat=no', + 'ac_cv_func_shm_open_working_with_mmap=no', + 'mono_cv_uscore=yes' + ] + + CFLAGS = sysroot_flags + [ + '-arch %s' % arch, + '-Wl,-application_extension' + ] + + CXXFLAGS = sysroot_flags + [ + '-arch %s' % arch, + '-Wl,-application_extension' + ] + + CPPFLAGS = sysroot_flags + [ + '-DMONOTOUCH=1', + '-arch %s' % arch, + '-Wl,-application_extension', + '-DHOST_IOS' + ] + + LDFLAGS = [] + + CONFIGURE_FLAGS = [ + '--disable-boehm', + '--disable-btls', + '--disable-executables', + '--disable-iconv', + '--disable-mcs-build', + '--disable-nls', + '--disable-visibility-hidden', + '--enable-maintainer-mode', + '--enable-minimal=com,remoting,shared_perfcounters,gac', + '--enable-monotouch', + '--with-tls=pthread', + '--without-ikvm-native', + '--disable-cooperative-suspend', + '--disable-hybrid-suspend', + '--disable-crash-reporting' + ] + + env['_ios-%s_AR' % target] = AR + env['_ios-%s_AS' % target] = AS + env['_ios-%s_CC' % target] = CC + env['_ios-%s_CXX' % target] = CXX + env['_ios-%s_LD' % target] = LD + env['_ios-%s_RANLIB' % target] = RANLIB + env['_ios-%s_STRIP' % target] = STRIP + + env['_ios-%s_AC_VARS' % target] = AC_VARS + env['_ios-%s_CFLAGS' % target] = CFLAGS + env['_ios-%s_CXXFLAGS' % target] = CXXFLAGS + env['_ios-%s_CPPFLAGS' % target] = CPPFLAGS + env['_ios-%s_LDFLAGS' % target] = LDFLAGS + env['_ios-%s_CONFIGURE_FLAGS' % target] = CONFIGURE_FLAGS + + # Runtime template + runtime.setup_runtime_template(env, opts, 'ios', target, host_triple) + + +class iOSCrossTable: + target_triples = { + 'cross-armv7': 'arm-apple-darwin', + 'cross-arm64': 'aarch64-apple-darwin' + } + + device_targets = { + 'cross-armv7': 'armv7', + 'cross-arm64': 'arm64' + } + + # 'darwin10' is hard-coded in 'offsets-tool.py', hence why we use 'darwin10' here + offsets_dumper_abis = { + 'cross-armv7': 'arm-apple-darwin10', + 'cross-arm64': 'aarch64-apple-darwin10' + } + + +def get_osxcross_sdk(osxcross_bin, arch): + osxcross_sdk = os.environ.get('OSXCROSS_SDK', 18) + + name_fmt = path_join(osxcross_bin, arch + '-apple-darwin%s-%s') + + if not os.path.isfile(name_fmt % (osxcross_sdk, 'ar')): + raise BuildError('Specify a valid osxcross SDK with the environment variable \'OSXCROSS_SDK\'') + + return osxcross_sdk + + +def setup_ios_cross_template(env: dict, opts: iOSOpts, target: str, host_arch: str): + target_triple = iOSCrossTable.target_triples[target] + device_target = iOSCrossTable.device_targets[target] + offsets_dumper_abi = iOSCrossTable.offsets_dumper_abis[target] + host_triple = '%s-apple-darwin11' % host_arch + + is_sim = device_target in sim_targets + + ios_sysroot_path = opts.ios_sdk_path + + if not ios_sysroot_path and sys.platform == 'darwin': + # Auto-detect on macOS + ios_sysroot_path = xcrun_find_sdk('iphonesimulator' if is_sim else 'iphoneos') + + if not ios_sysroot_path: + raise RuntimeError('Cannot find iOS SDK; specify one manually with \'--ios-sdk\'.') + + osx_sysroot_path = opts.osx_sdk_path + + if not osx_sysroot_path and sys.platform == 'darwin': + # Auto-detect on macOS + osx_sysroot_path = xcrun_find_sdk('macosx') + + if not osx_sysroot_path: + raise RuntimeError('Cannot find MacOSX SDK; specify one manually with \'--osx-sdk\'.') + + if sys.platform != 'darwin': + if not 'OSXCROSS_ROOT' in os.environ: + raise RuntimeError('The \'OSXCROSS_ROOT\' environment variable is required for cross-compiling to macOS') + + osxcross_root = os.environ['OSXCROSS_ROOT'] + osxcross_bin = path_join(osxcross_root, 'target', 'bin') + osxcross_sdk = get_osxcross_sdk(osxcross_bin, arch=host_arch) + + env['_ios-%s_PATH' % target] = osxcross_bin + + name_fmt = path_join(osxcross_bin, target + ('-apple-darwin%s-' % osxcross_sdk) + '%s') + else: + tools_path = path_join(opts.osx_toolchain_path, 'usr', 'bin') + name_fmt = path_join(tools_path, '%s') + + env['_ios-%s_AR' % target] = name_fmt % 'ar' + env['_ios-%s_AS' % target] = name_fmt % 'as' + env['_ios-%s_CC' % target] = name_fmt % 'clang' + env['_ios-%s_CXX' % target] = name_fmt % 'clang++' + env['_ios-%s_LD' % target] = name_fmt % 'ld' + env['_ios-%s_RANLIB' % target] = name_fmt % 'ranlib' + env['_ios-%s_STRIP' % target] = name_fmt % 'strip' + + libclang = path_join(opts.ios_toolchain_path, 'usr', 'lib', 'libclang.dylib') if sys.platform == 'darwin' else os.environ['LIBCLANG_PATH'] + + env['_ios-%s_OFFSETS_DUMPER_ARGS' % target] = [ + '--libclang=%s' % libclang, + '--sysroot=%s' % ios_sysroot_path + ] + + AC_VARS = ['ac_cv_func_shm_open_working_with_mmap=no'] + + CFLAGS = ['-isysroot', osx_sysroot_path, '-mmacosx-version-min=10.9', '-Qunused-arguments'] + CXXFLAGS = ['-isysroot', osx_sysroot_path, '-mmacosx-version-min=10.9', '-Qunused-arguments', '-stdlib=libc++'] + CPPFLAGS = ['-DMONOTOUCH=1'] + LDFLAGS = ['-stdlib=libc++'] + + CONFIGURE_FLAGS = [ + '--disable-boehm', + '--disable-btls', + '--disable-iconv', + '--disable-libraries', + '--disable-mcs-build', + '--disable-nls', + '--enable-dtrace=no', + '--enable-icall-symbol-map', + '--enable-minimal=com,remoting', + '--enable-monotouch', + '--disable-crash-reporting' + ] + + env['_ios-%s_AC_VARS' % target] = AC_VARS + env['_ios-%s_CFLAGS' % target] = CFLAGS + env['_ios-%s_CXXFLAGS' % target] = CXXFLAGS + env['_ios-%s_CPPFLAGS' % target] = CPPFLAGS + env['_ios-%s_LDFLAGS' % target] = LDFLAGS + env['_ios-%s_CONFIGURE_FLAGS' % target] = CONFIGURE_FLAGS + + # Runtime cross template + runtime.setup_runtime_cross_template(env, opts, 'ios', target, host_triple, target_triple, device_target, 'llvm64', offsets_dumper_abi) + + +def strip_libs(opts: iOSOpts, product: str, target: str): + # 'strip' doesn't support '--strip-unneeded' on macOS + return + + +def configure(opts: iOSOpts, product: str, target: str): + env = {} + + is_sim = target in sim_targets + + if is_cross(target): + import llvm + + llvm.make(opts, 'llvm64') + setup_ios_cross_template(env, opts, target, host_arch='x86_64') + else: + if is_sim: + setup_ios_simulator_template(env, opts, target) + else: + setup_ios_device_template(env, opts, target) + + if not os.path.isfile(path_join(opts.mono_source_root, 'configure')): + runtime.run_autogen(opts) + + runtime.run_configure(env, opts, product, target) + + +def make(opts: iOSOpts, product: str, target: str): + env = {} + + build_dir = path_join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration)) + + make_args = ['-C', build_dir] + make_args += ['V=1'] if opts.verbose_make else [] + + 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 and not is_cross(target): + strip_libs(opts, product, target) + + +def clean(opts: iOSOpts, product: str, target: str): + rm_rf( + path_join(opts.configure_dir, '%s-%s-%s' % (product, target, opts.configuration)), + path_join(opts.configure_dir, '%s-%s-%s.config.cache' % (product, target, opts.configuration)), + 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_shortcuts = { + 'all-device': device_targets, + 'all-sim': sim_targets, + 'all-cross': cross_targets + } + + target_values = device_targets + sim_targets + cross_targets + list(target_shortcuts) + + actions = OrderedDict() + actions['configure'] = configure + actions['make'] = make + actions['clean'] = clean + + parser = cmd_utils.build_arg_parser(description='Builds the Mono runtime for iOS') + + default_help = 'default: %(default)s' + + default_ios_toolchain = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain' + default_osx_toolchain = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain' + default_ios_version_min = '10.0' # Same as Godot + + parser.add_argument('action', choices=['configure', 'make', 'clean']) + parser.add_argument('--target', choices=target_values, action='append', required=True) + parser.add_argument('--ios-toolchain', default=default_ios_toolchain, help=default_help) + parser.add_argument('--ios-sdk', default='', help=default_help) + parser.add_argument('--ios-version-min', default=default_ios_version_min, help=default_help) + parser.add_argument('--osx-toolchain', default=default_osx_toolchain, help=default_help) + parser.add_argument('--osx-sdk', default='', 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 = ios_opts_from_args(args) + + targets = cmd_utils.expand_input_targets(input_targets, target_shortcuts) + + if not os.path.isdir(opts.mono_source_root): + print('Mono sources directory not found: ' + opts.mono_source_root) + sys.exit(1) + + action = actions[input_action] + + try: + for target in targets: + action(opts, 'ios', target) + except BuildError as e: + sys.exit(e.message) + + +if __name__ == '__main__': + from sys import argv + main(argv[1:]) diff --git a/llvm.py b/llvm.py index edb0bdf..59220b6 100755 --- a/llvm.py +++ b/llvm.py @@ -8,6 +8,9 @@ from options import * from os_utils import * +# TODO: OSXCROSS + + target_values = ['llvm32', 'llvm64', 'llvmwin32', 'llvmwin64'] mxe_targets = { 'llvmwin32': {'arch': 'i686', 'mxe': 'mxe-Win32'}, @@ -73,6 +76,15 @@ def make(opts: BaseOpts, target: str): make_args += ['V=1'] if opts.verbose_make else [] + # IMPORTANT: We must specify the jobs count for this Makefile. + # The Makefile itself runs Make as well with the '-j' option, which tells it to spawn as many jobs as possible. + # This can result in errors like 'posix_spawn failed: Resource temporarily unavailable' on macOS due to the process limit. + # The job count seems to be inherited from the parent Make process, so that fixes the issue. + make_args += ['-j', opts.jobs] + + if not find_executable('cmake') and not 'CMAKE' in os.environ: + print('WARNING: Cannot find CMake. Required by the llvm Makefile.') + run_command('make', args=make_args, name='make') touch(stamp_file) diff --git a/options.py b/options.py old mode 100644 new mode 100755 index 2d491c8..efedebb --- a/options.py +++ b/options.py @@ -25,12 +25,20 @@ class AndroidOpts(RuntimeOpts): android_toolchains_prefix: str android_sdk_root: str android_ndk_root: str - with_monodroid: bool android_api_version: str android_cmake_version: str toolchain_name_fmt: str = '%s-api%s-clang' +@dataclass +class iOSOpts(RuntimeOpts): + ios_toolchain_path: str + ios_sdk_path: str + ios_version_min: str + osx_toolchain_path: str + osx_sdk_path: str + + @dataclass class DesktopOpts(RuntimeOpts): with_llvm: bool @@ -72,12 +80,22 @@ def android_opts_from_args(args): 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, android_api_version = args.android_api_version, android_cmake_version = args.android_cmake_version ) +def ios_opts_from_args(args): + return iOSOpts( + **vars(runtime_opts_from_args(args)), + ios_toolchain_path = abspath(args.ios_toolchain), + ios_sdk_path = abspath(args.ios_sdk) if args.ios_sdk else '', + ios_version_min = args.ios_version_min, + osx_toolchain_path = abspath(args.osx_toolchain), + osx_sdk_path = abspath(args.osx_sdk) if args.ios_sdk else '' + ) + + def bcl_opts_from_args(args): return BclOpts( **vars(base_opts_from_args(args)), diff --git a/os_utils.py b/os_utils.py old mode 100644 new mode 100755 index 2068d92..310597d --- a/os_utils.py +++ b/os_utils.py @@ -32,13 +32,17 @@ def run_command(command, args=[], cwd=None, env=None, name='command'): raise BuildError('\'%s\' exited with error code: %s' % (name, e.returncode)) +print_env_sh_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'print_env.sh') + + def source(script: str, cwd=None) -> dict: popen_args = {} if cwd is not None: popen_args['cwd'] = cwd import subprocess - proc = subprocess.Popen('bash -c \'source %s; env -0\'' % script, stdout=subprocess.PIPE, shell=True, **popen_args) + cmd = 'bash -c \'source %s; bash %s\'' % (script, print_env_sh_path) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, **popen_args) output = proc.communicate()[0] return dict(line.split('=', 1) for line in output.decode().split('\x00') if line) @@ -135,3 +139,12 @@ def globs(pathnames, dirpath='.'): for pathname in pathnames: files.extend(glob.glob(os.path.join(dirpath, pathname))) return files + + +def xcrun_find_sdk(sdk_name): + import subprocess + xcrun_output = subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).decode().strip() + if xcrun_output.startswith('xcrun: error: SDK "%s" cannot be located' % sdk_name): + return '' + sdk_path = xcrun_output + return sdk_path diff --git a/patch_mono.py b/patch_mono.py index c906695..6c42672 100755 --- a/patch_mono.py +++ b/patch_mono.py @@ -28,7 +28,8 @@ def main(raw_args): patches = [ 'fix-mono-android-tkill.diff', 'mono-dbg-agent-clear-tls-instead-of-abort.diff', - 'bcl-profile-platform-override.diff' + 'bcl-profile-platform-override.diff', + 'mono_ios_asl_log_deprecated.diff' ] from subprocess import Popen diff --git a/print_env.sh b/print_env.sh new file mode 100755 index 0000000..5ca6edb --- /dev/null +++ b/print_env.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# This is used in python to get the export environment variables from bash's 'source' command. + +# The 'env' command option '-0' separates the 'name=value' results by 'null' instead of line breaks. +# This is required to parse the output because a variable value can contain line breaks as well. +# Unfortunately, the '-0' option is not supported on some platforms like macOS, +# hence why we need this script to print the environment variables instead. + +unset IFS +for var in $(compgen -e); do + printf "$var=${!var}\0" +done