blob: 9338a753ae879dbcabadf6f7ed7cae3eaad67aed [file] [log] [blame]
# 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.
"""Code for parsing, validating, and requesting build options."""
import argparse
import atexit
import os
import subprocess
import sys
import time
from src.build.util import download_package_util_flags
# --target=
_TARGET_NACL_I686 = 'nacl_i686'
_TARGET_NACL_X86_64 = 'nacl_x86_64'
_TARGET_BARE_METAL_I686 = 'bare_metal_i686'
_TARGET_BARE_METAL_ARM = 'bare_metal_arm'
ALLOWED_TARGETS = [_TARGET_NACL_I686,
_TARGET_NACL_X86_64,
_TARGET_BARE_METAL_I686,
_TARGET_BARE_METAL_ARM]
_CONSUMER_SUPPORTED_TARGETS = [_TARGET_NACL_X86_64,
_TARGET_BARE_METAL_I686,
_TARGET_BARE_METAL_ARM]
_TARGET_MAPPING = {'ni': _TARGET_NACL_I686,
'n32': _TARGET_NACL_I686,
'nx': _TARGET_NACL_X86_64,
'n64': _TARGET_NACL_X86_64,
'bi': _TARGET_BARE_METAL_I686,
'ba': _TARGET_BARE_METAL_ARM}
_DEFAULT_TARGET = _TARGET_BARE_METAL_I686
# --chrometype=
_ALLOWED_CHROMETYPES = ['beta',
'dev',
'lkgr',
'prebuilt',
'stable']
_CHROMETYPE_MAPPING = {'b': 'beta',
'v': 'dev',
'l': 'lkgr',
'p': 'prebuilt',
's': 'stable'}
_DEFAULT_CHROMETYPE = 'prebuilt'
# --internal-apks-source=
# prebuilt: use the artifacts previously built by corp builder
# internal: build apk from local checkout. Used by corp builder.
# internal-dev: a self-pilot mode comparing to internal. It cuts a
# almost-always-actually-satisfied dependency from internal apks to framework
# jar to speed up development iteration. In addition, it does not sync git
# repo to DEPS automatically.
_ALLOWED_INTERNAL_APKS_SOURCES = ['internal', # for the corp builder
'internal-dev', # for local development
'prebuilt'] # for everyone else
_DEFAULT_INTERNAL_APKS_SOURCES = 'prebuilt'
# --logging=
_ANSI_FB_LOGGING = 'ansi-fb'
_ANSI_SF_LAYER_LOGGING = 'ansi-sf-layer'
_BIONIC_LOADER_LOGGING = 'bionic-loader'
_EGL_API = 'egl-api'
_EGL_API_TRACING = 'egl-api-tracing'
_GLES_API_LOGGING = 'gles-api'
_GLES_API_TRACING = 'gles-api-tracing'
_GLES_PASSTHROUGH_LOGGING = 'gles-passthrough'
_GLES_PASSTHROUGH_TRACING = 'gles-passthrough-tracing'
_JAVA_METHODS_LOGGING = 'java-methods'
_LIBDVM_DEBUG = 'libdvm-debug'
_LOG_THREAD_IDS = 'thread-ids'
_LOG_TIMESTAMPS = 'timestamps'
_MAKE_TO_NINJA_LOGGING = 'make-to-ninja'
_MEMORY_USAGE = 'memory-usage'
_NINJA_GENERATOR_LOGGING = 'ninja-generator'
_NOTICES_LOGGING = 'notices'
_POSIX_TRANSLATION_DEBUG = 'posix-translation-debug'
_VERBOSE_MEMORY_VIEWER = 'verbose-memory-viewer'
_ALLOWED_LOGGING = [_ANSI_FB_LOGGING,
_ANSI_SF_LAYER_LOGGING,
_BIONIC_LOADER_LOGGING,
_EGL_API,
_EGL_API_TRACING,
_GLES_API_LOGGING,
_GLES_API_TRACING,
_GLES_PASSTHROUGH_LOGGING,
_GLES_PASSTHROUGH_TRACING,
_JAVA_METHODS_LOGGING,
_LIBDVM_DEBUG,
_LOG_THREAD_IDS,
_LOG_TIMESTAMPS,
_MAKE_TO_NINJA_LOGGING,
_MEMORY_USAGE,
_NINJA_GENERATOR_LOGGING,
_NOTICES_LOGGING,
_POSIX_TRANSLATION_DEBUG,
_VERBOSE_MEMORY_VIEWER]
# -W options
_ALLOWED_WARNING_LEVEL = ['all',
'yes', # all except -Wunused-*.
'no']
class _Options(object):
def __init__(self):
self._loggers = {}
self._goma_ctl_process = None
self._goma_dir = None
self._system_packages = []
self._values = {}
self.parsed = False
def __getattr__(self, name):
"""Provides getters for values originated from the command line options.
For example, OPTIONS.weird() returns True if --weird is passed from the
command line.
"""
if name in self._values:
return lambda: self._values[name]
raise AttributeError("'_Options' object has no attribute '" + name + "'")
def get_target_bitsize(self):
return 64 if self.target().endswith('_x86_64') else 32
def is_arm(self):
return self.target().endswith('_arm')
def is_x86(self):
return self.is_i686() or self.is_x86_64()
def is_i686(self):
return self.target().endswith('_i686')
def is_x86_64(self):
return self.target().endswith('_x86_64')
def is_bare_metal_arm(self):
return self.target() == _TARGET_BARE_METAL_ARM
def is_bare_metal_i686(self):
return self.target() == _TARGET_BARE_METAL_I686
def is_bare_metal_build(self):
return self.target().startswith('bare_metal_')
def is_nacl_build(self):
return _Options._is_nacl_target(self.target())
def is_nacl_i686(self):
return self.target() == _TARGET_NACL_I686
def is_nacl_x86_64(self):
return self.target() == _TARGET_NACL_X86_64
def is_consumer_supported_target(self):
return self.target() in _CONSUMER_SUPPORTED_TARGETS
def is_debug_info_enabled(self):
return not self.disable_debug_info()
def is_debug_code_enabled(self):
return not self.disable_debug_code()
def is_optimized_build(self):
return self.opt()
def is_lkgr_chrome(self):
return self.chrometype() == 'lkgr'
def is_official_chrome(self):
return self.chrometype() in ['dev', 'beta', 'stable']
def is_ansi_fb_logging(self):
return _ANSI_FB_LOGGING in self._loggers
def is_ansi_sf_layer_logging(self):
return _ANSI_SF_LAYER_LOGGING in self._loggers
def is_bionic_loader_logging(self):
return _BIONIC_LOADER_LOGGING in self._loggers
def is_egl_api_logging(self):
return _EGL_API in self._loggers
def is_egl_api_tracing(self):
return _EGL_API_TRACING in self._loggers
def is_gles_api_logging(self):
return _GLES_API_LOGGING in self._loggers
def is_gles_api_tracing(self):
return _GLES_API_TRACING in self._loggers
def is_gles_passthrough_logging(self):
return _GLES_PASSTHROUGH_LOGGING in self._loggers
def is_gles_passthrough_tracing(self):
return _GLES_PASSTHROUGH_TRACING in self._loggers
def is_java_methods_logging(self):
return _JAVA_METHODS_LOGGING in self._loggers
def is_libdvm_debug(self):
return _LIBDVM_DEBUG in self._loggers
def is_make_to_ninja_logging(self):
return _MAKE_TO_NINJA_LOGGING in self._loggers
def is_memory_usage_logging(self):
return _MEMORY_USAGE in self._loggers
def is_ninja_generator_logging(self):
return _NINJA_GENERATOR_LOGGING in self._loggers
def is_notices_logging(self):
return _NOTICES_LOGGING in self._loggers
def is_posix_translation_debug(self):
return _POSIX_TRANSLATION_DEBUG in self._loggers
def log_thread_ids(self):
return _LOG_THREAD_IDS in self._loggers
def log_timestamps(self):
return _LOG_TIMESTAMPS in self._loggers
def internal_apks_source_is_internal(self):
return self.internal_apks_source().startswith('internal')
def use_verbose_memory_viewer(self):
return _VERBOSE_MEMORY_VIEWER in self._loggers
def get_warning_suppression_cflags(self):
if self._show_warnings == 'all':
return [] # no suppression
elif self._show_warnings == 'yes':
return ['-Wno-unused-but-set-variable',
'-Wno-unused-function',
'-Wno-unused-variable']
return ['-w']
def get_system_packages(self):
return self._system_packages
@staticmethod
def _is_nacl_target(target):
return target.startswith('nacl_')
@staticmethod
def _parse_target(value):
if value in ALLOWED_TARGETS:
return value
elif value in _TARGET_MAPPING.keys():
return _TARGET_MAPPING[value]
raise argparse.ArgumentTypeError('Invalid target')
@staticmethod
def _parse_chrometype(value):
if value in _ALLOWED_CHROMETYPES:
return value
elif value in _CHROMETYPE_MAPPING.keys():
return _CHROMETYPE_MAPPING[value]
raise argparse.ArgumentTypeError('Invalid chrometype')
@staticmethod
def _print_mapping(label, mapping):
lines = (' % 3s: %s' % (k, v) for k, v in mapping.iteritems())
return '%s:\n%s\n' % (label, '\n'.join(lines))
@staticmethod
def _help_epilog():
str = _Options._print_mapping('Target type shortcuts', _TARGET_MAPPING)
str += _Options._print_mapping('Chrometype shortcuts', _CHROMETYPE_MAPPING)
return str
def _apply_args(self, args):
args.logging = args.logging.split(',') if args.logging else []
if args.weird:
if _Options._is_nacl_target(args.target):
args.enable_touch_overlay = True
args.logging.extend([
# TODO(crbug.com/342652): Re-enable _LIBDVM_DEBUG.
# _LIBDVM_DEBUG,
_LOG_THREAD_IDS,
_LOG_TIMESTAMPS,
_MEMORY_USAGE,
_POSIX_TRANSLATION_DEBUG,
_VERBOSE_MEMORY_VIEWER
])
else:
# TODO(yusukes|hamaji): Configure Bare Metal i686 weird builder.
pass
if args.official_build:
args.opt = True
args.disable_debug_code = True
args.regen_build_prop = True
return args
def parse(self, args):
parser = argparse.ArgumentParser(
usage=os.path.basename(sys.argv[0]) + ' <options>',
epilog=_Options._help_epilog(),
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--cc-wrapper', metavar='[cc-wrapper]',
help='Compiler wrapper used by goma')
parser.add_argument('--chrometype', '-c', choices=_ALLOWED_CHROMETYPES,
default=_DEFAULT_CHROMETYPE,
type=_Options._parse_chrometype,
help='Indicates Chrome type.')
parser.add_argument('--configure-jobs', '-j', default=None, type=int,
metavar='jobs',
help='Max number of parellel jobs run during '
'configure. Defaults to number of CPUs. Set to 0 to '
'force configure to run in a single process which can '
'aid in diagnosing failures.')
parser.add_argument('--disable-art-aot', action='store_false',
dest='enable_art_aot', help='Disable ART boot image '
'and AOT compilation.')
parser.add_argument('--disable-config-cache', action='store_false',
dest='enable_config_cache',
help='Disable configuration cache.')
parser.add_argument('--disable-debug-info', action='store_true',
help='Do not generate debug information. ')
parser.add_argument('--disable-debug-code', action='store_true',
help='Skip debug logging / assertions.')
parser.add_argument('--disable-goma', action='store_true',
help='Do not use goma to build.')
parser.add_argument('--disable-hwui', action='store_true',
help='Disable the use of hardware accelerated '
'rendering in the Android UI code.')
parser.add_argument('--disable-method-whitelist', action='store_false',
dest='enable_method_whitelist',
help='Disable method whitelist for boot.oat. '
'This also changes the compiler filter to '
'"everything", which will require adjustments to the '
'executable section address to be able to run.')
parser.add_argument('--enable-aacenc', action='store_true',
help='Build libraries to support AAC encoding.')
parser.add_argument('--enable-atrace', action='store_true',
help='Enable Android trace events through Chromium')
parser.add_argument('--enable-binder', action='store_true',
help='Enable Binder calls for all services.')
parser.add_argument('--enable-jemalloc-debug', action='store_true',
help='Enable jemalloc debug mode. This fills all '
'memory returned from malloc() and all memory passed '
'to free() with garbage.')
parser.add_argument('--enable-touch-overlay', action='store_true',
help='[EXPERIMENTAL] Overlay touch spots on the '
'screen in the plugin after the app renders.')
parser.add_argument('--enable-valgrind', action='store_true',
help='Run unit tests under Valgrind.')
parser.add_argument('--goma-dir', help='The directory for goma.')
parser.add_argument('--java-dir',
default='/usr/lib/jvm/java-7-openjdk-amd64',
help='The directory for Java. The path '
'points to a directory, which usually JAVA_HOME env '
'variable points to. E.g. '
'/usr/lib/jvm/java-7-openjdk-amd64 for Ubuntu.')
parser.add_argument('--logging', metavar=str(_ALLOWED_LOGGING),
help='A comma-separated list of logging to enable on '
'build.')
parser.add_argument('--notest', action='store_false', dest='run_tests',
help='Disable automatic running of unit tests during '
'build.')
parser.add_argument('--official-build', action='store_true',
help='Set configure options for an official Runtime '
'build.')
parser.add_argument('--opt', '-O', action='store_true',
help='Enable optimizations.')
parser.add_argument('--internal-apks-source',
choices=_ALLOWED_INTERNAL_APKS_SOURCES,
default=_DEFAULT_INTERNAL_APKS_SOURCES,
help='Source of play-services and '
'GoogleContactsSyncAdapter APKs. \'prebuilt\' is the '
'default and it requires production server access.')
parser.add_argument('--regen-build-prop', action='store_true',
help='Forces regeneration of the build.prop file '
'which contains git HEAD information for release '
'purposes. Pass this option to make sure the file is '
'up to date. Note: requires rebuilding the rootfs.')
parser.add_argument('--restart-goma', action='store_true',
help='Restart goma. This is mainly for buildbots.')
parser.add_argument('--show-warnings', metavar=str(_ALLOWED_WARNING_LEVEL),
default='no',
help='By default, most of third_party code is '
'compiled with -w (ignore all wanings). This option '
'removes the compiler flag.')
parser.add_argument('--strip-runtime-binaries', action='store_true',
help='Strip binaries in ARC. Files in '
'out/target/<target>/lib will not be stripped. '
'This is useful for remote debugging.')
parser.add_argument('--system-packages',
help='A comma-separated list of APK files that should '
'be added as system apps.')
parser.add_argument('--target', '-t', choices=ALLOWED_TARGETS,
default=_DEFAULT_TARGET, type=_Options._parse_target,
help='Target type to build')
parser.add_argument('--verbose', '-v', action='store_true',
help='Show verbose messages while configure runs.')
parser.add_argument('--weird', action='store_true',
help='Automatically sets configuration values used by '
'the weird builder.')
download_package_util_flags.add_extra_flags(parser)
parsed_args = None
# Note we override any options in the .configure file with options passed on
# the command line.
parsed_args = parser.parse_args(args, parsed_args)
parsed_args = self._apply_args(parsed_args)
self._values = vars(parsed_args)
for name in parsed_args.logging:
if name in _ALLOWED_LOGGING:
self._loggers[name] = True
else:
print 'Unknown logging:', name
parser.print_help()
return -1
if parsed_args.show_warnings:
if parsed_args.show_warnings in _ALLOWED_WARNING_LEVEL:
self._show_warnings = parsed_args.show_warnings
else:
print 'Unknown warning level:', parsed_args.show_warnings
parser.print_help()
return -1
if parsed_args.system_packages:
self._system_packages = parsed_args.system_packages.split(',')
# ARM Chrome OS does not have a lot of storage and it takes a lot
# of time to transfer binaries. Note that you still have debug
# symbols in out/target/<target>/lib.
if self.is_arm():
self._values['strip_runtime_binaries'] = True
# NaCl i686 does not support ART AOT and support is not planned in the
# future.
if self.is_nacl_i686():
self._values['enable_art_aot'] = False
error = self._check_args(parsed_args)
if error:
parser.print_help()
print '\n', error
return -1
self.parsed = True
return 0
def _check_args(self, args):
if args.enable_valgrind and not self.is_bare_metal_i686():
return '--enable-valgrind works only on Bare Metal i686 target.'
if args.enable_valgrind and args.opt:
return ('--enable-valgrind memcheck suppression patterns do not work ' +
'with optimized code.')
# Java method logging does not work properly with ART AOT enabled.
if 'java-methods' in args.logging and args.enable_art_aot:
return ('java-methods logging cannot be used with ART AOT enabled. '
'Use --disable-art-aot')
return None
@staticmethod
def _is_goma_path(dirname):
for goma_file in ['goma_ctl.py', 'gomacc', 'compiler_proxy',
'gcc', 'g++', 'clang', 'clang++']:
if not os.path.exists(os.path.join(dirname, goma_file)):
return False
return True
def _find_goma_path(self):
if self.goma_dir():
if not self._is_goma_path(self.goma_dir()):
print 'goma is not in ' + self.goma_dir()
sys.exit(1)
return self.goma_dir()
goma_home = os.path.join(os.getenv('HOME'), 'goma')
paths = os.getenv('PATH').split(':') + [goma_home]
for path in paths:
if _Options._is_goma_path(path):
self._goma_dir = path
return path
return None
def _get_goma_ensure_start_command(self):
return 'restart' if self.restart_goma() else 'ensure_start'
def wait_for_goma_ctl(self):
"""Waits for goma_ctl process to be finished."""
if self._goma_ctl_process is None:
return
sleep_count = 0
while self._goma_ctl_process.poll() is None:
time.sleep(0.1)
sleep_count += 1
if sleep_count > 50:
print 'killing goma_ctl because it took too long at shutdown'
self._goma_ctl_process.kill()
return
# Note that it is safe to wait a subprocess multiple times.
if self._goma_ctl_process.wait():
print self._goma_ctl_process.stdout.read()
print 'goma_ctl %s failed!' % self._get_goma_ensure_start_command()
sys.exit(1)
def set_up_goma(self):
"""Checks the path of goma.
If it's found, this function runs compiler_proxy and returns True.
"""
if self.disable_goma():
return False
goma_path = self._find_goma_path()
# We honor --cc-wrapper if it is set explicitly.
if self.cc_wrapper():
self._values['goma'] = False
print '%s is used instead of Goma.' % self.cc_wrapper()
return False
if not goma_path:
# Do not use Goma as it is not installed.
self._values['goma'] = False
return False
self._values['goma'] = True
self._values['cc_wrapper'] = os.path.join(goma_path, 'gomacc')
if not self._goma_ctl_process:
goma_ctl_command = [os.path.join(goma_path, 'goma_ctl.py'),
self._get_goma_ensure_start_command()]
# It takes about 2 seconds to run goma_ctl.py ensure_start. To
# reduce the total time for ./configure, we run this in background
# and check the exit status of it in the atexit handler.
self._goma_ctl_process = subprocess.Popen(goma_ctl_command,
stdout=subprocess.PIPE)
atexit.register(self.wait_for_goma_ctl)
return True
def write_configure_file(self, output_file=None):
options_file = output_file or self.get_configure_options_file()
output_dir = os.path.dirname(options_file)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
new_configuration_options = ' '.join(sys.argv[1:]) + '\n'
try:
with open(options_file, 'r') as f:
old_configuration_options = f.read()
except IOError:
old_configuration_options = ''
if new_configuration_options != old_configuration_options:
with open(options_file, 'w') as f:
f.write(new_configuration_options)
# Parse the configure.options file. This is useful for standalone tools that
# are not run at configure time but rely on configured details (e.g.,
# launch_chrome.py needs the path to Chrome which changes based on
# chrometype).
def parse_configure_file(self, input_file=None):
options_file = input_file or self.get_configure_options_file()
if os.path.exists(options_file):
with open(options_file) as f:
self.parse(f.read().split())
else:
raise IOError('File ' + options_file + ' does not exist.')
def get_configure_options_file(self):
return os.path.join('out', 'configure.options')
def check_access(self, name):
"""Called by AccessControlProxy to check attribute access."""
if (not self.parsed and
name not in ('parse', 'parsed', 'parse_configure_file')):
raise AttributeError(
'Attempted to access uninitialized OPTIONS values. Please consider '
'calling OPTIONS.parse_configure_file() in the very beginning of '
'your script.')
class _AccessControlProxy(object):
"""Wraps an arbitrary object to add attribute access check."""
__slots__ = ['__target', '__check_access']
def __init__(self, target, check_access):
self.__target = target
self.__check_access = check_access
def __getattr__(self, name):
self.__check_access(name)
return getattr(self.__target, name)
_real_options = _Options()
OPTIONS = _AccessControlProxy(_real_options, _real_options.check_access)