blob: e7bda9373d3a2bb6f926be89f2335ffa852febdf [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A simple script to make building/testing Mojo components easier."""
import argparse
from copy import deepcopy
import logging
from multiprocessing import cpu_count
import os
import subprocess
import sys
from get_test_list import GetTestList
from mopy.config import Config
from mopy.paths import Paths
from mopy.gn import GNArgsForConfig, ParseGNConfig, CommandLineForGNArgs
from mopy.log import InitLogging
_logger = logging.getLogger()
_verbose_count = 0
def _args_to_config(args):
# Default to host OS.
target_os = None
if args.android:
target_os = Config.OS_ANDROID
elif args.ios:
target_os = Config.OS_IOS
target_cpu = args.target_cpu
additional_args = {}
if 'clang' in args:
additional_args['is_clang'] = args.clang
if 'asan' in args and args.asan:
additional_args['sanitizer'] = Config.SANITIZER_ASAN
# Additional non-standard config entries:
if 'goma' in args:
goma_dir = os.environ.get('GOMA_DIR')
goma_home_dir = os.path.join(os.getenv('HOME', ''), 'goma')
if args.goma and goma_dir:
additional_args['use_goma'] = True
additional_args['goma_dir'] = goma_dir
elif args.goma and os.path.exists(goma_home_dir):
additional_args['use_goma'] = True
additional_args['goma_dir'] = goma_home_dir
else:
additional_args['use_goma'] = False
additional_args['goma_dir'] = None
if 'nacl' in args:
additional_args['use_nacl'] = args.nacl
if not ('asan' in args and args.asan):
go_dir = os.path.join(Paths().src_root, 'third_party', 'go', 'tool')
if args.android:
additional_args['mojo_use_go'] = True
additional_args['go_build_tool'] = os.path.join(
go_dir, 'android_arm', 'bin', 'go')
elif target_os is None and Config.GetHostOS() == Config.OS_LINUX:
additional_args['mojo_use_go'] = True
additional_args['go_build_tool'] = os.path.join(
go_dir, 'linux_amd64', 'bin', 'go')
if 'dry_run' in args:
additional_args['dry_run'] = args.dry_run
if 'builder_name' in args:
additional_args['builder_name'] = args.builder_name
if 'build_number' in args:
additional_args['build_number'] = args.build_number
if 'master_name' in args:
additional_args['master_name'] = args.master_name
if 'test_results_server' in args:
additional_args['test_results_server'] = args.test_results_server
if 'gn_args' in args:
additional_args['gn_args'] = args.gn_args
is_debug = args.debug and not args.official
return Config(target_os=target_os, target_cpu=target_cpu,
is_debug=is_debug, is_official_build=args.official,
dcheck_always_on=args.dcheck_always_on,
is_simulator=args.simulator, **additional_args)
def _get_out_dir(config):
"""Gets the build output directory (e.g., out/Debug), relative to src, for the
given config."""
paths = Paths(config)
return paths.SrcRelPath(paths.build_dir)
def _sync(config): # pylint: disable=W0613
"""Runs gclient sync for the given config."""
_logger.debug('_sync()')
return subprocess.call(['gclient', 'sync'])
def _gn(config):
"""Runs gn gen for the given config."""
_logger.debug('_gn()')
command = ['gn', 'gen', '--check']
gn_args = CommandLineForGNArgs(GNArgsForConfig(config))
out_dir = _get_out_dir(config)
command.append(out_dir)
command.append('--args=%s' % ' '.join(gn_args))
print 'Running %s %s ...' % (command[0],
' '.join('\'%s\'' % x for x in command[1:]))
return subprocess.call(command)
def _build(config):
"""Builds for the given config."""
_logger.debug('_build()')
out_dir = _get_out_dir(config)
gn_args = ParseGNConfig(out_dir)
print 'Building in %s ...' % out_dir
if gn_args.get('use_goma'):
# Use the configured goma directory.
local_goma_dir = gn_args.get('goma_dir')
print 'Ensuring goma (in %s) started ...' % local_goma_dir
command = ['python',
os.path.join(local_goma_dir, 'goma_ctl.py'),
'ensure_start']
exit_code = subprocess.call(command)
if exit_code:
return exit_code
# Goma allows us to run many more jobs in parallel, say 32 per core/thread
# (= 1024 on a 16-core, 32-thread Z620). Limit the load average to 4 per
# core/thread (= 128 on said Z620).
jobs = cpu_count() * 32
limit = cpu_count() * 4
return subprocess.call(['ninja', '-j', str(jobs), '-l', str(limit),
'-C', out_dir])
else:
return subprocess.call(['ninja', '-C', out_dir])
def _run_tests(config, test_types):
"""Runs the tests of the given type(s) for the given config."""
assert isinstance(test_types, list)
config = deepcopy(config)
config.values['test_types'] = test_types
test_list = GetTestList(config, verbose_count=_verbose_count)
dry_run = config.values.get('dry_run')
final_exit_code = 0
failure_list = []
for entry in test_list:
print 'Running: %s' % entry['name']
print 'Command: %s' % ' '.join(entry['command'])
if dry_run:
continue
_logger.info('Starting: %s' % ' '.join(entry['command']))
exit_code = subprocess.call(entry['command'])
_logger.info('Completed: %s' % ' '.join(entry['command']))
if exit_code:
if not final_exit_code:
final_exit_code = exit_code
failure_list.append(entry['name'])
print 72 * '='
print 'SUMMARY:',
if dry_run:
print 'Dry run: no tests run'
elif not failure_list:
assert not final_exit_code
print 'All tests passed'
else:
assert final_exit_code
print 'The following had failures:', ', '.join(failure_list)
return final_exit_code
def _test(config):
_logger.debug('_test()')
return _run_tests(config, [Config.TEST_TYPE_DEFAULT])
def _perftest(config):
_logger.debug('_perftest()')
return _run_tests(config, [Config.TEST_TYPE_PERF])
def _pytest(config):
_logger.debug('_pytest()')
return _run_tests(config, ['python'])
def main():
os.chdir(Paths().src_root)
parser = argparse.ArgumentParser(description='A script to make building'
'/testing Mojo components easier.')
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--verbose',
help='Be verbose (multiple times for more)',
default=0, dest='verbose_count', action='count')
parent_parser.add_argument('--asan', help='Use Address Sanitizer',
action='store_true')
parent_parser.add_argument('--dcheck_always_on',
help='DCHECK and MOJO_DCHECK are fatal even in '
'release builds',
action='store_true')
debug_group = parent_parser.add_mutually_exclusive_group()
debug_group.add_argument('--debug', help='Debug build (default)',
default=True, action='store_true')
debug_group.add_argument('--release', help='Release build', default=False,
dest='debug', action='store_false')
# The official build is a release build suitable for distribution, with a
# different package name.
debug_group.add_argument('--official', help='Official build', default=False,
dest='official', action='store_true')
os_group = parent_parser.add_mutually_exclusive_group()
os_group.add_argument('--android', help='Build for Android',
action='store_true')
os_group.add_argument('--ios', help='Build for iOS',
action='store_true')
parent_parser.add_argument('--simulator',
help='Build for a simulator of the target',
action='store_true')
parent_parser.add_argument('--target-cpu',
help='CPU architecture to build for.',
choices=['x64', 'x86', 'arm'])
subparsers = parser.add_subparsers()
sync_parser = subparsers.add_parser('sync', parents=[parent_parser],
help='Sync using gclient (does not run gn).')
sync_parser.set_defaults(func=_sync)
gn_parser = subparsers.add_parser('gn', parents=[parent_parser],
help='Run gn for mojo (does not sync).')
gn_parser.set_defaults(func=_gn)
gn_parser.add_argument('--args', help='Specify extra args',
default=None, dest='gn_args')
# Note: no default, if nothing is specified on the command line GN decides.
gn_parser.add_argument('--nacl', help='Add in NaCl', action='store_true',
default=argparse.SUPPRESS)
gn_parser.add_argument('--no-nacl', help='Remove NaCl', action='store_false',
default=argparse.SUPPRESS, dest='nacl')
clang_group = gn_parser.add_mutually_exclusive_group()
clang_group.add_argument('--clang', help='Use Clang (default)', default=None,
action='store_true')
clang_group.add_argument('--gcc', help='Use GCC',
dest='clang', action='store_false')
goma_group = gn_parser.add_mutually_exclusive_group()
goma_group.add_argument('--goma',
help='Use Goma (if $GOMA_DIR is set or $HOME/goma '
'exists; default)',
default=True,
action='store_true')
goma_group.add_argument('--no-goma', help='Don\'t use Goma', default=False,
dest='goma', action='store_false')
build_parser = subparsers.add_parser('build', parents=[parent_parser],
help='Build')
build_parser.set_defaults(func=_build)
test_parser = subparsers.add_parser('test', parents=[parent_parser],
help='Run unit tests (does not build).')
test_parser.set_defaults(func=_test)
test_parser.add_argument('--dry-run',
help='Print instead of executing commands',
default=False, action='store_true')
perftest_parser = subparsers.add_parser('perftest', parents=[parent_parser],
help='Run perf tests (does not build).')
perftest_parser.set_defaults(func=_perftest)
pytest_parser = subparsers.add_parser('pytest', parents=[parent_parser],
help='Run Python unit tests (does not build).')
pytest_parser.set_defaults(func=_pytest)
args = parser.parse_args()
global _verbose_count
_verbose_count = args.verbose_count
InitLogging(_verbose_count)
if args.simulator and not args.ios:
sys.exit("Currently, the simulator target is only configured for iOS")
return args.func(_args_to_config(args))
if __name__ == '__main__':
sys.exit(main())