#!/usr/bin/env python # 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 multiprocessing import os 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, '..')) # Environment variables for default settings. DART_USE_ASAN = "DART_USE_ASAN" # Use instead of --asan DART_USE_MSAN = "DART_USE_MSAN" # Use instead of --msan DART_USE_TSAN = "DART_USE_TSAN" # Use instead of --tsan DART_USE_WHEEZY = "DART_USE_WHEEZY" # Use instread of --wheezy DART_USE_TOOLCHAIN = "DART_USE_TOOLCHAIN" # Use instread of --toolchain-prefix DART_USE_SYSROOT = "DART_USE_SYSROOT" # Use instead of --target-sysroot def use_asan(): return DART_USE_ASAN in os.environ def use_msan(): return DART_USE_MSAN in os.environ def use_tsan(): return DART_USE_TSAN in os.environ def use_wheezy(): return DART_USE_WHEEZY in os.environ def toolchain_prefix(args): if args.toolchain_prefix: return args.toolchain_prefix return os.environ.get(DART_USE_TOOLCHAIN) def target_sysroot(args): if args.target_sysroot: return args.target_sysroot return os.environ.get(DART_USE_SYSROOT) def get_out_dir(mode, arch, target_os): return utils.GetBuildRoot(HOST_OS, mode, arch, target_os) def to_command_line(gn_args): def merge(key, value): if type(value) is bool: return '%s=%s' % (key, 'true' if value else 'false') return '%s="%s"' % (key, value) return [merge(x, y) for x, y in gn_args.iteritems()] def host_cpu_for_arch(arch): if arch in ['ia32', 'arm', 'armv6', 'armv5te', 'mips', 'simarm', 'simarmv6', 'simarmv5te', 'simmips', 'simdbc', 'armsimdbc']: return 'x86' if arch in ['x64', 'arm64', 'simarm64', 'simdbc64', 'armsimdbc64']: return 'x64' def target_cpu_for_arch(arch, target_os): if arch in ['ia32', 'simarm', 'simarmv6', 'simarmv5te', 'simmips']: return 'x86' if arch in ['simarm64']: return 'x64' if arch == 'mips': return 'mipsel' if arch == 'simdbc': return 'arm' if target_os == 'android' else 'x86' if arch == 'simdbc64': return 'arm64' if target_os == 'android' else 'x64' if arch == 'armsimdbc': return 'arm' if arch == 'armsimdbc64': return 'arm64' return arch def host_os_for_gn(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 parse_string_map(key, string_map): for m in string_map.split(','): l = m.split('=') if l[0] == key: return l[1] return None def to_gn_args(args, mode, arch, target_os): gn_args = {} host_os = host_os_for_gn(HOST_OS) if target_os == 'host': gn_args['target_os'] = host_os else: gn_args['target_os'] = target_os gn_args['dart_target_arch'] = arch gn_args['target_cpu'] = target_cpu_for_arch(arch, target_os) gn_args['host_cpu'] = host_cpu_for_arch(arch) # See: runtime/observatory/BUILD.gn. # This allows the standalone build of the observatory to fall back on # dart_bootstrap if the prebuilt SDK doesn't work. gn_args['dart_host_pub_exe'] = "" # 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['dart_zlib_path'] = "//runtime/bin/zlib" # Use tcmalloc only when targeting Linux and when not using ASAN. gn_args['dart_use_tcmalloc'] = ((gn_args['target_os'] == 'linux') and not args.asan and not args.msan and not args.tsan) if gn_args['target_os'] == 'linux': if gn_args['target_cpu'] == 'arm': # Force -mfloat-abi=hard and -mfpu=neon for arm on Linux as we're # specifying a gnueabihf compiler in //build/toolchain/linux BUILD.gn. gn_args['arm_arch'] = 'armv7' gn_args['arm_float_abi'] = 'hard' gn_args['arm_use_neon'] = True elif gn_args['target_cpu'] == 'armv6': raise Exception("GN support for armv6 unimplemented") elif gn_args['target_cpu'] == 'armv5te': raise Exception("GN support for armv5te unimplemented") 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'. gn_args['dart_runtime_mode'] = 'develop' # TODO(zra): Investigate using clang with these configurations. # Clang compiles tcmalloc's inline assembly for ia32 on Linux wrong, so we # don't use clang in that configuration. Thus, we use gcc for ia32 *unless* # asan or tsan is specified. has_clang = (host_os != 'win' and args.os not in ['android'] and not gn_args['target_cpu'].startswith('arm') and not gn_args['target_cpu'].startswith('mips') and not ((gn_args['target_os'] == 'linux') and (gn_args['host_cpu'] == 'x86') and not args.asan and not args.msan and not args.tsan)) # Use clang for sanitizer builds. gn_args['is_clang'] = args.clang and has_clang gn_args['is_asan'] = args.asan and gn_args['is_clang'] gn_args['is_msan'] = args.msan and gn_args['is_clang'] gn_args['is_tsan'] = args.tsan and gn_args['is_clang'] # Setup the user-defined sysroot. if gn_args['target_os'] == 'linux' and args.wheezy: gn_args['dart_use_wheezy_sysroot'] = True else: sysroot = target_sysroot(args) if sysroot: gn_args['target_sysroot'] = parse_string_map(arch, sysroot) toolchain = toolchain_prefix(args) if toolchain: gn_args['toolchain_prefix'] = parse_string_map(arch, toolchain) goma_dir = os.environ.get('GOMA_DIR') 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 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 args.debug_opt_level: gn_args['dart_debug_optimization_level'] = args.debug_opt_level gn_args['debug_optimization_level'] = args.debug_opt_level return gn_args def process_os_option(os_name): if os_name == 'host': return HOST_OS return os_name def process_options(args): if args.arch == 'all': args.arch = 'ia32,x64,simarm,simarm64,simmips,simdbc64' if args.mode == 'all': args.mode = 'debug,release,product' if args.os == 'all': args.os = 'host,android' args.mode = args.mode.split(',') args.arch = args.arch.split(',') args.os = args.os.split(',') for mode in args.mode: if not mode in ['debug', 'release', 'product']: print "Unknown mode %s" % mode return False for arch in args.arch: archs = ['ia32', 'x64', 'simarm', 'arm', 'simarmv6', 'armv6', 'simarmv5te', 'armv5te', 'simmips', 'mips', 'simarm64', 'arm64', 'simdbc', 'simdbc64', 'armsimdbc', 'armsimdbc64'] if not arch in archs: print "Unknown arch %s" % arch return False oses = [process_os_option(os_name) for os_name in args.os] for os_name in oses: if not os_name in ['android', 'freebsd', 'linux', 'macos', 'win32']: print "Unknown os %s" % os_name return False if os_name != HOST_OS: if os_name != 'android': print "Unsupported target os %s" % os_name return False if not HOST_OS in ['linux']: 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', 'armv6', 'armv5te', 'arm64', 'mips', 'simdbc', 'simdbc64']: print ("Cross-compilation to %s is not supported for architecture %s." % (os_name, arch)) 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 parse_args(args): args = args[1:] parser = argparse.ArgumentParser( description='A script to run `gn gen`.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) common_group = parser.add_argument_group('Common Arguments') other_group = parser.add_argument_group('Other Arguments') common_group.add_argument('--arch', '-a', type=str, help='Target architectures (comma-separated).', metavar='[all,ia32,x64,simarm,arm,simarmv6,armv6,simarmv5te,armv5te,' 'simmips,mips,simarm64,arm64,simdbc,armsimdbc]', default='x64') common_group.add_argument('--mode', '-m', type=str, help='Build variants (comma-separated).', metavar='[all,debug,release,product]', default='debug') common_group.add_argument('--os', type=str, help='Target OSs (comma-separated).', metavar='[all,host,android]', default='host') common_group.add_argument("-v", "--verbose", help='Verbose output.', default=False, action="store_true") other_group.add_argument('--asan', help='Build with ASAN', default=use_asan(), action='store_true') other_group.add_argument('--no-asan', help='Disable ASAN', dest='asan', action='store_false') other_group.add_argument('--clang', help='Use Clang', default=True, action='store_true') other_group.add_argument('--no-clang', help='Disable Clang', dest='clang', action='store_false') other_group.add_argument('--debug-opt-level', '-d', help='The optimization level to use for debug builds', type=str) other_group.add_argument('--goma', help='Use goma', default=True, action='store_true') other_group.add_argument('--no-goma', help='Disable goma', dest='goma', action='store_false') other_group.add_argument('--ide', help='Generate an IDE file.', default=os_has_ide(HOST_OS), action='store_true') other_group.add_argument('--msan', help='Build with MSAN', default=use_msan(), action='store_true') other_group.add_argument('--no-msan', help='Disable MSAN', dest='msan', action='store_false') other_group.add_argument('--target-sysroot', '-s', type=str, help='Comma-separated list of arch=/path/to/sysroot mappings') other_group.add_argument('--toolchain-prefix', '-t', type=str, help='Comma-separated list of arch=/path/to/toolchain-prefix mappings') other_group.add_argument('--tsan', help='Build with TSAN', default=use_tsan(), action='store_true') other_group.add_argument('--no-tsan', help='Disable TSAN', dest='tsan', action='store_false') other_group.add_argument('--wheezy', help='Use the Debian wheezy sysroot on Linux', default=use_wheezy(), action='store_true') other_group.add_argument('--no-wheezy', help='Disable the Debian wheezy sysroot on Linux', dest='wheezy', action='store_false') other_group.add_argument('--workers', '-w', type=int, help='Number of simultaneous GN invocations', dest='workers', default=multiprocessing.cpu_count()) options = parser.parse_args(args) if not process_options(options): parser.print_help() return None return options def run_command(command): try: subprocess.check_output( command, cwd=DART_ROOT, stderr=subprocess.STDOUT) return 0 except subprocess.CalledProcessError as e: return ("Command failed: " + ' '.join(command) + "\n" + "output: " + e.output) def main(argv): starttime = time.time() args = parse_args(argv) if sys.platform.startswith(('cygwin', 'win')): subdir = 'win' elif sys.platform == 'darwin': subdir = 'mac' elif sys.platform.startswith('linux'): subdir = 'linux64' else: print 'Unknown platform: ' + sys.platform return 1 commands = [] for target_os in args.os: for mode in args.mode: for arch in args.arch: command = [ '%s/buildtools/%s/gn' % (DART_ROOT, subdir), 'gen', '--check' ] gn_args = to_command_line(to_gn_args(args, mode, arch, target_os)) out_dir = get_out_dir(mode, arch, target_os) if args.verbose: print "gn gen --check in %s" % out_dir if args.ide: command.append(ide_switch(HOST_OS)) command.append(out_dir) command.append('--args=%s' % ' '.join(gn_args)) commands.append(command) pool = multiprocessing.Pool(args.workers) results = pool.map(run_command, commands, chunksize=1) for r in results: if r != 0: print r.strip() return 1 endtime = time.time() if args.verbose: print ("GN Time: %.3f seconds" % (endtime - starttime)) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))