| #!/usr/bin/env python3 |
| # Copyright 2016 The Dart project authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import os |
| import platform |
| import subprocess |
| import sys |
| import time |
| import utils |
| |
| HOST_OS = utils.GuessOS() |
| HOST_ARCH = utils.GuessArchitecture() |
| SCRIPT_DIR = os.path.dirname(sys.argv[0]) |
| DART_ROOT = os.path.realpath(os.path.join(SCRIPT_DIR, '..')) |
| AVAILABLE_ARCHS = utils.ARCH_FAMILY.keys() |
| GN = os.path.join(DART_ROOT, 'buildtools', 'gn') |
| |
| # Environment variables for default settings. |
| DART_USE_TOOLCHAIN = "DART_USE_TOOLCHAIN" # Use instread of --toolchain-prefix |
| DART_USE_SYSROOT = "DART_USE_SYSROOT" # Use instead of --target-sysroot |
| DART_USE_CRASHPAD = "DART_USE_CRASHPAD" # Use instead of --use-crashpad |
| # use instead of --platform-sdk |
| DART_MAKE_PLATFORM_SDK = "DART_MAKE_PLATFORM_SDK" |
| |
| DART_GN_ARGS = "DART_GN_ARGS" |
| |
| |
| def ToolchainPrefix(args): |
| if args.toolchain_prefix: |
| return args.toolchain_prefix |
| return os.environ.get(DART_USE_TOOLCHAIN) |
| |
| |
| def TargetSysroot(args): |
| if args.target_sysroot: |
| return args.target_sysroot |
| return os.environ.get(DART_USE_SYSROOT) |
| |
| |
| def MakePlatformSDK(): |
| return DART_MAKE_PLATFORM_SDK in os.environ |
| |
| |
| def GetGNArgs(args): |
| if args.gn_args != None: |
| return args.gn_args |
| args = os.environ.get(DART_GN_ARGS) or "" |
| return args.split() |
| |
| |
| def GetOutDir(mode, arch, target_os, sanitizer): |
| return utils.GetBuildRoot(HOST_OS, mode, arch, target_os, sanitizer) |
| |
| |
| def ToCommandLine(gn_args): |
| |
| def merge(key, value): |
| if type(value) is bool: |
| return '%s=%s' % (key, 'true' if value else 'false') |
| elif type(value) is int: |
| return '%s=%d' % (key, value) |
| return '%s="%s"' % (key, value) |
| |
| return [merge(x, y) for x, y in gn_args.items()] |
| |
| |
| # Runs true if the currently executing python interpreter is running under |
| # Rosetta. I.e., python3 is an x64 executable and we're on an arm64 Mac. |
| def IsRosetta(): |
| if platform.system() == 'Darwin': |
| p = subprocess.Popen(['sysctl', '-in', 'sysctl.proc_translated'], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| output, _ = p.communicate() |
| return output.decode('utf-8').strip() == '1' |
| return False |
| |
| |
| def HostCpuForArch(arch): |
| # Check for Rosetta before checking platform.machine(), as the latter |
| # returns 'x86_64' when running under Rosetta. |
| if IsRosetta(): |
| if arch in ['x64', 'x64c']: |
| # Without this case, we would try to build with |
| # host_cpu="arm64" |
| # target_cpu="x64" |
| # dart_target_arch="x64" |
| # Which requires the VM to use an x64 simulator in the host |
| # arm64 binaries, and this simulator is unimplemented. |
| return 'x64' |
| else: |
| return 'arm64' |
| |
| m = platform.machine() |
| if m == 'aarch64' or m == 'arm64': |
| return 'arm64' |
| if m == 'armv7l': |
| return 'arm' |
| |
| if arch in ['ia32', 'arm', 'simarm', 'simarm_x64', 'riscv32', 'simriscv32']: |
| return 'x86' |
| if arch in [ |
| 'x64', 'arm64', 'simarm64', 'arm_x64', 'x64c', 'arm64c', |
| 'simarm64c', 'riscv64', 'simriscv64' |
| ]: |
| return 'x64' |
| |
| |
| # The C compiler's target. |
| def TargetCpuForArch(arch, target_os): |
| if arch in ['ia32', 'simarm', 'simriscv32']: |
| return 'x86' |
| if arch in [ |
| 'x64', 'simarm64', 'simarm_x64', 'simriscv64', 'x64c', 'simarm64c' |
| ]: |
| return 'x64' |
| if arch == 'arm_x64': |
| return 'arm' |
| if arch == 'arm64c': |
| return 'arm64' |
| return arch |
| |
| |
| # The Dart compiler's target. |
| def DartTargetCpuForArch(arch): |
| if arch in ['ia32']: |
| return 'ia32' |
| if arch in ['x64', 'x64c']: |
| return 'x64' |
| if arch in ['arm', 'simarm', 'simarm_x64', 'arm_x64']: |
| return 'arm' |
| if arch in ['arm64', 'simarm64', 'arm64c', 'simarm64c']: |
| return 'arm64' |
| if arch in ['riscv32', 'simriscv32']: |
| return 'riscv32' |
| if arch in ['riscv64', 'simriscv64']: |
| return 'riscv64' |
| return arch |
| |
| |
| def IsCompressedPointerArch(arch): |
| return arch in ['x64c', 'arm64c', 'simarm64c'] |
| |
| |
| def HostOsForGn(host_os): |
| if host_os.startswith('macos'): |
| return 'mac' |
| if host_os.startswith('win'): |
| return 'win' |
| return host_os |
| |
| |
| # Where string_map is formatted as X1=Y1,X2=Y2 etc. |
| # If key is X1, returns Y1. |
| def ParseStringMap(key, string_map): |
| for m in string_map.split(','): |
| l = m.split('=') |
| if l[0] == key: |
| return l[1] |
| return None |
| |
| |
| def UseSysroot(args, gn_args): |
| # Don't try to use a Linux sysroot if we aren't on Linux. |
| if gn_args['target_os'] != 'linux' and HOST_OS != 'linux': |
| return False |
| # Don't use the sysroot if we're given another sysroot. |
| if TargetSysroot(args): |
| return False |
| # Our Debian Jesse sysroot doesn't work with GCC 9 |
| if not gn_args['is_clang']: |
| return False |
| # Our Debian Jesse sysroot has incorrect annotations on realloc. |
| if gn_args['is_ubsan']: |
| return False |
| # Our Debian Jesse sysroot doesn't support RISCV |
| if gn_args['target_cpu'] in ['riscv32', 'riscv64']: |
| return False |
| # Otherwise use the sysroot. |
| return True |
| |
| |
| def ToGnArgs(args, mode, arch, target_os, sanitizer, verify_sdk_hash): |
| gn_args = {} |
| |
| host_os = HostOsForGn(HOST_OS) |
| if target_os == 'host': |
| gn_args['target_os'] = host_os |
| else: |
| gn_args['target_os'] = target_os |
| |
| gn_args['host_cpu'] = HostCpuForArch(arch) |
| gn_args['target_cpu'] = TargetCpuForArch(arch, target_os) |
| gn_args['dart_target_arch'] = DartTargetCpuForArch(arch) |
| gn_args['dart_use_compressed_pointers'] = IsCompressedPointerArch(arch) |
| |
| # Configure Crashpad library if it is used. |
| gn_args['dart_use_crashpad'] = (args.use_crashpad or |
| DART_USE_CRASHPAD in os.environ) |
| if gn_args['dart_use_crashpad']: |
| # Tell Crashpad's BUILD files which checkout layout to use. |
| gn_args['crashpad_dependencies'] = 'dart' |
| |
| if DartTargetCpuForArch(arch) != HostCpuForArch(arch): |
| # Training an app-jit snapshot under a simulator is slow. Use script |
| # snapshots instead. |
| gn_args['dart_snapshot_kind'] = 'kernel' |
| else: |
| gn_args['dart_snapshot_kind'] = 'app-jit' |
| |
| # We only want the fallback root certs in the standalone VM on |
| # Linux and Windows. |
| if gn_args['target_os'] in ['linux', 'win']: |
| gn_args['dart_use_fallback_root_certificates'] = True |
| |
| gn_args['bssl_use_clang_integrated_as'] = True |
| |
| # Use tcmalloc only when targeting Linux and when not using ASAN. |
| gn_args['dart_use_tcmalloc'] = ((gn_args['target_os'] == 'linux') and |
| (gn_args['target_cpu'] != 'arm') and |
| (gn_args['target_cpu'] != 'riscv32') and |
| (gn_args['target_cpu'] != 'riscv64') and |
| sanitizer == 'none') |
| |
| # Use mallinfo2 if specified on the command line |
| gn_args['dart_use_mallinfo2'] = args.use_mallinfo2 |
| |
| if gn_args['target_os'] == 'linux': |
| if gn_args['target_cpu'] == 'arm': |
| # Default to -mfloat-abi=hard and -mfpu=neon for arm on Linux as we're |
| # specifying a gnueabihf compiler in //build/toolchain/linux/BUILD.gn. |
| floatabi = 'hard' if args.arm_float_abi == '' else args.arm_float_abi |
| gn_args['arm_version'] = 7 |
| gn_args['arm_float_abi'] = floatabi |
| gn_args['arm_use_neon'] = True |
| |
| gn_args['is_debug'] = mode == 'debug' |
| gn_args['is_release'] = mode == 'release' |
| gn_args['is_product'] = mode == 'product' |
| gn_args['dart_debug'] = mode == 'debug' |
| |
| # This setting is only meaningful for Flutter. Standalone builds of the VM |
| # should leave this set to 'develop', which causes the build to defer to |
| # 'is_debug', 'is_release' and 'is_product'. |
| if mode == 'product': |
| gn_args['dart_runtime_mode'] = 'release' |
| else: |
| gn_args['dart_runtime_mode'] = 'develop' |
| |
| gn_args['exclude_kernel_service'] = args.exclude_kernel_service |
| |
| gn_args['is_clang'] = args.clang |
| |
| enable_code_coverage = args.code_coverage and gn_args['is_clang'] |
| gn_args['dart_vm_code_coverage'] = enable_code_coverage |
| |
| gn_args['is_asan'] = sanitizer == 'asan' |
| gn_args['is_lsan'] = sanitizer == 'lsan' |
| gn_args['is_msan'] = sanitizer == 'msan' |
| gn_args['is_tsan'] = sanitizer == 'tsan' |
| gn_args['is_ubsan'] = sanitizer == 'ubsan' |
| gn_args['is_qemu'] = args.use_qemu |
| |
| if not args.platform_sdk: |
| gn_args['dart_platform_sdk'] = args.platform_sdk |
| |
| # We don't support stripping on Windows |
| if host_os != 'win': |
| gn_args['dart_stripped_binary'] = 'exe.stripped/dart' |
| gn_args['dart_precompiled_runtime_stripped_binary'] = ( |
| 'exe.stripped/dart_precompiled_runtime_product') |
| gn_args['gen_snapshot_stripped_binary'] = ( |
| 'exe.stripped/gen_snapshot_product') |
| gn_args['analyze_snapshot_binary'] = ( |
| 'exe.stripped/analyze_snapshot_product') |
| |
| # Setup the user-defined sysroot. |
| if UseSysroot(args, gn_args): |
| gn_args['dart_use_debian_sysroot'] = True |
| else: |
| sysroot = TargetSysroot(args) |
| if sysroot: |
| gn_args['target_sysroot'] = ParseStringMap(arch, sysroot) |
| |
| toolchain = ToolchainPrefix(args) |
| if toolchain: |
| gn_args['toolchain_prefix'] = ParseStringMap(arch, toolchain) |
| |
| goma_dir = os.environ.get('GOMA_DIR') |
| # Search for goma in depot_tools in path |
| goma_depot_tools_dir = None |
| for path in os.environ.get('PATH', '').split(os.pathsep): |
| if os.path.basename(path) == 'depot_tools': |
| cipd_bin = os.path.join(path, '.cipd_bin') |
| if os.path.isfile(os.path.join(cipd_bin, 'gomacc')): |
| goma_depot_tools_dir = cipd_bin |
| break |
| # Otherwise use goma from home directory. |
| # TODO(whesse): Remove support for goma installed in home directory. |
| # Goma will only be distributed through depot_tools. |
| goma_home_dir = os.path.join(os.getenv('HOME', ''), 'goma') |
| if args.goma and goma_dir: |
| gn_args['use_goma'] = True |
| gn_args['goma_dir'] = goma_dir |
| elif args.goma and goma_depot_tools_dir: |
| gn_args['use_goma'] = True |
| gn_args['goma_dir'] = goma_depot_tools_dir |
| elif args.goma and os.path.exists(goma_home_dir): |
| gn_args['use_goma'] = True |
| gn_args['goma_dir'] = goma_home_dir |
| else: |
| gn_args['use_goma'] = False |
| gn_args['goma_dir'] = None |
| |
| if gn_args['target_os'] == 'mac' and gn_args['use_goma']: |
| gn_args['mac_use_goma_rbe'] = True |
| |
| # Code coverage requires -O0 to be set. |
| if enable_code_coverage: |
| gn_args['dart_debug_optimization_level'] = 0 |
| gn_args['debug_optimization_level'] = 0 |
| elif args.debug_opt_level: |
| gn_args['dart_debug_optimization_level'] = args.debug_opt_level |
| gn_args['debug_optimization_level'] = args.debug_opt_level |
| |
| gn_args['verify_sdk_hash'] = verify_sdk_hash |
| |
| return gn_args |
| |
| |
| def ProcessOsOption(os_name): |
| if os_name == 'host': |
| return HOST_OS |
| return os_name |
| |
| |
| def ProcessOptions(args): |
| if args.arch == 'all': |
| args.arch = 'ia32,x64,simarm,simarm64,x64c,simarm64c,simriscv32,simriscv64' |
| if args.mode == 'all': |
| args.mode = 'debug,release,product' |
| if args.os == 'all': |
| args.os = 'host,android,fuchsia' |
| if args.sanitizer == 'all': |
| args.sanitizer = 'none,asan,lsan,msan,tsan,ubsan' |
| args.mode = args.mode.split(',') |
| args.arch = args.arch.split(',') |
| args.os = args.os.split(',') |
| args.sanitizer = args.sanitizer.split(',') |
| for mode in args.mode: |
| if not mode in ['debug', 'release', 'product']: |
| print("Unknown mode %s" % mode) |
| return False |
| for i, arch in enumerate(args.arch): |
| if not arch in AVAILABLE_ARCHS: |
| # Normalise to lower case form to make it less case-picky. |
| arch_lower = arch.lower() |
| if arch_lower in AVAILABLE_ARCHS: |
| args.arch[i] = arch_lower |
| continue |
| print("Unknown arch %s" % arch) |
| return False |
| oses = [ProcessOsOption(os_name) for os_name in args.os] |
| for os_name in oses: |
| if not os_name in [ |
| 'android', 'freebsd', 'linux', 'macos', 'win32', 'fuchsia' |
| ]: |
| print("Unknown os %s" % os_name) |
| return False |
| if os_name == 'android': |
| if not HOST_OS in ['linux', 'macos']: |
| print( |
| "Cross-compilation to %s is not supported on host os %s." % |
| (os_name, HOST_OS)) |
| return False |
| if not arch in [ |
| 'ia32', |
| 'x64', |
| 'arm', |
| 'arm_x64', |
| 'arm64', |
| 'x64c', |
| 'arm64c', |
| ]: |
| print( |
| "Cross-compilation to %s is not supported for architecture %s." |
| % (os_name, arch)) |
| return False |
| elif os_name == 'fuchsia': |
| if not HOST_OS in ['linux', 'macos']: |
| print( |
| "Cross-compilation to %s is not supported on host os %s." % |
| (os_name, HOST_OS)) |
| return False |
| if not arch in ['x64', 'arm64', 'x64c', 'arm64c']: |
| print( |
| "Cross-compilation to %s is not supported for architecture %s." |
| % (os_name, arch)) |
| return False |
| elif os_name != HOST_OS: |
| print("Unsupported target os %s" % os_name) |
| return False |
| if HOST_OS != 'win' and args.use_crashpad: |
| print("Crashpad is only supported on Windows") |
| return False |
| return True |
| |
| |
| def os_has_ide(host_os): |
| return host_os.startswith('win') or host_os.startswith('mac') |
| |
| |
| def ide_switch(host_os): |
| if host_os.startswith('win'): |
| return '--ide=vs' |
| elif host_os.startswith('mac'): |
| return '--ide=xcode' |
| else: |
| return '--ide=json' |
| |
| |
| def AddCommonGnOptionArgs(parser): |
| """Adds arguments that will change the default GN arguments.""" |
| |
| parser.add_argument('--goma', help='Use goma', action='store_true') |
| parser.add_argument('--no-goma', |
| help='Disable goma', |
| dest='goma', |
| action='store_false') |
| parser.set_defaults(goma=True) |
| |
| parser.add_argument('--verify-sdk-hash', |
| help='Enable SDK hash checks (default)', |
| dest='verify_sdk_hash', |
| action='store_true') |
| parser.add_argument('-nvh', |
| '--no-verify-sdk-hash', |
| help='Disable SDK hash checks', |
| dest='verify_sdk_hash', |
| action='store_false') |
| parser.set_defaults(verify_sdk_hash=True) |
| |
| parser.add_argument('--clang', help='Use Clang', action='store_true') |
| parser.add_argument('--no-clang', |
| help='Disable Clang', |
| dest='clang', |
| action='store_false') |
| parser.set_defaults(clang=True) |
| |
| parser.add_argument( |
| '--platform-sdk', |
| help='Directs the create_sdk target to create a smaller "Platform" SDK', |
| default=MakePlatformSDK(), |
| action='store_true') |
| parser.add_argument('--use-crashpad', |
| default=False, |
| dest='use_crashpad', |
| action='store_true') |
| parser.add_argument('--use-qemu', |
| default=False, |
| dest='use_qemu', |
| action='store_true') |
| parser.add_argument('--exclude-kernel-service', |
| help='Exclude the kernel service.', |
| default=False, |
| dest='exclude_kernel_service', |
| action='store_true') |
| parser.add_argument('--arm-float-abi', |
| type=str, |
| help='The ARM float ABI (soft, softfp, hard)', |
| metavar='[soft,softfp,hard]', |
| default='') |
| |
| parser.add_argument('--code-coverage', |
| help='Enable code coverage for the standalone VM', |
| default=False, |
| dest="code_coverage", |
| action='store_true') |
| parser.add_argument('--debug-opt-level', |
| '-d', |
| help='The optimization level to use for debug builds', |
| type=str) |
| parser.add_argument('--gn-args', |
| help='Set extra GN args', |
| dest='gn_args', |
| action='append') |
| parser.add_argument( |
| '--toolchain-prefix', |
| '-t', |
| type=str, |
| help='Comma-separated list of arch=/path/to/toolchain-prefix mappings') |
| parser.add_argument('--ide', |
| help='Generate an IDE file.', |
| default=os_has_ide(HOST_OS), |
| action='store_true') |
| parser.add_argument('--export-compile-commands', |
| help='Export compile_commands.json database file.', |
| default=False, |
| action='store_true') |
| parser.add_argument( |
| '--target-sysroot', |
| '-s', |
| type=str, |
| help='Comma-separated list of arch=/path/to/sysroot mappings') |
| parser.add_argument('--use-mallinfo2', |
| help='Use mallinfo2 to collect malloc stats.', |
| default=False, |
| dest='use_mallinfo2', |
| action='store_true') |
| |
| |
| def AddCommonConfigurationArgs(parser): |
| """Adds arguments that influence which configuration will be built.""" |
| parser.add_argument("-a", |
| "--arch", |
| type=str, |
| help='Target architectures (comma-separated).', |
| metavar='[all,' + ','.join(AVAILABLE_ARCHS) + ']', |
| default=utils.GuessArchitecture()) |
| parser.add_argument('--mode', |
| '-m', |
| type=str, |
| help='Build variants (comma-separated).', |
| metavar='[all,debug,release,product]', |
| default='debug') |
| parser.add_argument('--os', |
| type=str, |
| help='Target OSs (comma-separated).', |
| metavar='[all,host,android,fuchsia]', |
| default='host') |
| parser.add_argument('--sanitizer', |
| type=str, |
| help='Build variants (comma-separated).', |
| metavar='[all,none,asan,lsan,msan,tsan,ubsan]', |
| default='none') |
| |
| |
| def AddOtherArgs(parser): |
| """Adds miscellaneous arguments to the parser.""" |
| parser.add_argument("-v", |
| "--verbose", |
| help='Verbose output.', |
| default=False, |
| action="store_true") |
| |
| |
| def parse_args(args): |
| args = args[1:] |
| parser = argparse.ArgumentParser( |
| description='A script to run `gn gen`.', |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| |
| config_group = parser.add_argument_group('Configuration Related Arguments') |
| AddCommonConfigurationArgs(config_group) |
| |
| gn_group = parser.add_argument_group('GN Related Arguments') |
| AddCommonGnOptionArgs(gn_group) |
| |
| other_group = parser.add_argument_group('Other Arguments') |
| AddOtherArgs(other_group) |
| |
| options = parser.parse_args(args) |
| if not ProcessOptions(options): |
| parser.print_help() |
| return None |
| return options |
| |
| |
| def BuildGnCommand(args, mode, arch, target_os, sanitizer, out_dir): |
| gn = os.path.join(DART_ROOT, 'buildtools', |
| 'gn.exe' if utils.IsWindows() else 'gn') |
| if not os.path.isfile(gn): |
| raise Exception("Couldn't find the gn binary at path: " + gn) |
| |
| # TODO(infra): Re-enable --check. Many targets fail to use |
| # public_deps to re-expose header files to their dependents. |
| # See dartbug.com/32364 |
| command = [gn, 'gen', out_dir] |
| gn_args = ToCommandLine( |
| ToGnArgs(args, mode, arch, target_os, sanitizer, args.verify_sdk_hash)) |
| gn_args += GetGNArgs(args) |
| if args.ide: |
| command.append(ide_switch(HOST_OS)) |
| if args.export_compile_commands: |
| command.append('--export-compile-commands') |
| command.append('--args=%s' % ' '.join(gn_args)) |
| |
| return command |
| |
| |
| def RunGnOnConfiguredConfigurations(args): |
| commands = [] |
| for target_os in args.os: |
| for mode in args.mode: |
| for arch in args.arch: |
| for sanitizer in args.sanitizer: |
| out_dir = GetOutDir(mode, arch, target_os, sanitizer) |
| commands.append( |
| BuildGnCommand(args, mode, arch, target_os, sanitizer, |
| out_dir)) |
| if args.verbose: |
| print("gn gen --check in %s" % out_dir) |
| |
| active_commands = [] |
| |
| def cleanup(command): |
| print("Command failed: " + ' '.join(command)) |
| for (_, process) in active_commands: |
| process.terminate() |
| |
| for command in commands: |
| try: |
| process = subprocess.Popen(command, cwd=DART_ROOT) |
| active_commands.append([command, process]) |
| except Exception as e: |
| print('Error: %s' % e) |
| cleanup(command) |
| return 1 |
| while active_commands: |
| time.sleep(0.1) |
| for active_command in active_commands: |
| (command, process) = active_command |
| if process.poll() is not None: |
| active_commands.remove(active_command) |
| if process.returncode != 0: |
| cleanup(command) |
| return 1 |
| return 0 |
| |
| |
| def Main(argv): |
| starttime = time.time() |
| |
| args = parse_args(argv) |
| if args is None: |
| return 1 |
| |
| result = RunGnOnConfiguredConfigurations(args) |
| |
| if args.verbose: |
| endtime = time.time() |
| print("GN Time: %.3f seconds" % (endtime - starttime)) |
| |
| return result |
| |
| |
| if __name__ == '__main__': |
| sys.exit(Main(sys.argv)) |