| # Copyright 2013 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. |
| |
| import re |
| |
| from recipe_engine import recipe_api |
| from recipe_engine import util as recipe_util |
| |
| class TestLauncherFilterFileInputPlaceholder(recipe_util.InputPlaceholder): |
| def __init__(self, api, tests): |
| self.raw = api.m.raw_io.input('\n'.join(tests)) |
| super(TestLauncherFilterFileInputPlaceholder, self).__init__() |
| |
| def render(self, test): |
| result = self.raw.render(test) |
| if not test.enabled: # pragma: no cover |
| result[0] = '--test-launcher-filter-file=%s' % result[0] |
| return result |
| |
| def cleanup(self, test_enabled): |
| self.raw.cleanup(test_enabled) |
| |
| |
| class ChromiumApi(recipe_api.RecipeApi): |
| def __init__(self, *args, **kwargs): |
| super(ChromiumApi, self).__init__(*args, **kwargs) |
| self._build_properties = None |
| |
| def get_config_defaults(self): |
| defaults = { |
| 'HOST_PLATFORM': self.m.platform.name, |
| 'HOST_ARCH': self.m.platform.arch, |
| 'HOST_BITS': self.m.platform.bits, |
| |
| 'TARGET_PLATFORM': self.m.platform.name, |
| 'TARGET_ARCH': self.m.platform.arch, |
| 'TARGET_CROS_BOARD': None, |
| |
| # NOTE: This is replicating logic which lives in |
| # chrome/trunk/src/build/common.gypi, which is undesirable. The desired |
| # end-state is that all the configuration logic lives in one place |
| # (in chromium/config.py), and the buildside gypfiles are as dumb as |
| # possible. However, since the recipes need to accurately contain |
| # {TARGET,HOST}_{BITS,ARCH,PLATFORM}, for use across many tools (of which |
| # gyp is one tool), we're taking a small risk and replicating the logic |
| # here. |
| 'TARGET_BITS': ( |
| 32 if self.m.platform.name == 'win' |
| else self.m.platform.bits), |
| |
| 'BUILD_CONFIG': self.m.properties.get('build_config', 'Release'), |
| |
| 'CHECKOUT_PATH': self.m.path['checkout'], |
| } |
| |
| # Fallback in case build path is not available. |
| # TODO(phajdan.jr): get rid of the need for BUILD_PATH in config. |
| try: |
| defaults['BUILD_PATH'] = self.m.path['build'] |
| except KeyError: |
| defaults['BUILD_PATH'] = self.package_repo_resource() |
| |
| return defaults |
| |
| def get_env(self): |
| ret = {} |
| if self.c.env.PATH: |
| ret['PATH'] = self.m.path.pathsep.join( |
| map(str, self.c.env.PATH) + ['%(PATH)s']) |
| if self.c.env.ADB_VENDOR_KEYS: |
| ret['ADB_VENDOR_KEYS'] = self.c.env.ADB_VENDOR_KEYS |
| if self.c.env.LLVM_FORCE_HEAD_REVISION: |
| ret['LLVM_FORCE_HEAD_REVISION'] = self.c.env.LLVM_FORCE_HEAD_REVISION |
| if self.c.env.GOMA_STUBBY_PROXY_IP_ADDRESS: |
| ret['GOMA_STUBBY_PROXY_IP_ADDRESS'] = \ |
| self.c.env.GOMA_STUBBY_PROXY_IP_ADDRESS |
| ret['GOMA_SERVICE_ACCOUNT_JSON_FILE'] = \ |
| self.m.goma.service_account_json_path |
| if self.c.env.FORCE_MAC_TOOLCHAIN: |
| ret['FORCE_MAC_TOOLCHAIN'] = self.c.env.FORCE_MAC_TOOLCHAIN |
| return ret |
| |
| @property |
| def build_properties(self): |
| return self._build_properties |
| |
| @property |
| def output_dir(self): |
| """Return the path to the built executable directory.""" |
| return self.c.build_dir.join(self.c.build_config_fs) |
| |
| @property |
| def version(self): |
| """Returns a version dictionary (after get_version()), e.g. |
| |
| { 'MAJOR'": '51', 'MINOR': '0', 'BUILD': '2704', 'PATCH': '0' } |
| """ |
| text = self._version |
| output = {} |
| for line in text.splitlines(): |
| [k,v] = line.split('=', 1) |
| output[k] = v |
| return output |
| |
| def get_version(self): |
| self._version = self.m.file.read( |
| 'get version', |
| self.m.path['checkout'].join('chrome', 'VERSION'), |
| test_data=("MAJOR=51\nMINOR=0\nBUILD=2704\nPATCH=0\n")) |
| return self.version |
| |
| def set_build_properties(self, props): |
| self._build_properties = props |
| |
| def configure_bot(self, builders_dict, additional_configs=None): |
| """Sets up the configurations and gclient to be ready for bot update. |
| |
| builders_dict is a dict of mastername -> 'builders' -> buildername -> |
| bot_config. |
| |
| The current mastername and buildername are looked up from the |
| build properties; we then apply the configs specified in bot_config |
| as appropriate. |
| |
| Returns a tuple of (buildername, bot_config) for subsequent use in |
| the recipe. |
| """ |
| additional_configs = additional_configs or [] |
| |
| # TODO: crbug.com/358481 . The build_config should probably be a property |
| # passed in from the slave config, but that doesn't exist today, so we |
| # need a lookup mechanism to map bot name to build_config. |
| mastername = self.m.properties.get('mastername') |
| buildername = self.m.properties.get('buildername') |
| master_dict = builders_dict.get(mastername, {}) |
| bot_config = master_dict.get('builders', {}).get(buildername) |
| |
| self.set_config(bot_config.get('chromium_config', 'chromium'), |
| **bot_config.get('chromium_config_kwargs', {})) |
| |
| for c in bot_config.get('chromium_apply_config', []): |
| self.apply_config(c) |
| |
| for c in additional_configs: |
| self.apply_config(c) |
| |
| # Note that we have to call gclient.set_config() and apply_config() *after* |
| # calling chromium.set_config(), above, because otherwise the chromium |
| # call would reset the gclient config to its defaults. |
| self.m.gclient.set_config( |
| 'chromium', |
| PATCH_PROJECT=self.m.properties.get('patch_project')) |
| for c in bot_config.get('gclient_apply_config', []): |
| self.m.gclient.apply_config(c) |
| |
| return (buildername, bot_config) |
| |
| def compile(self, targets=None, name=None, out_dir=None, |
| target=None, **kwargs): |
| """Return a compile.py invocation.""" |
| targets = targets or self.c.compile_py.default_targets.as_jsonish() |
| assert isinstance(targets, (list, tuple)) |
| |
| if self.c.gyp_env.GYP_DEFINES.get('clang', 0) == 1: |
| # Get the Clang revision before compiling. |
| self._clang_version = self.get_clang_version() |
| |
| args = [ |
| '--show-path', |
| 'python', |
| self.package_repo_resource('scripts', 'slave', 'compile.py'), |
| '--gsutil-py-path', self.m.depot_tools.gsutil_py_path, |
| '--ninja-path', self.m.depot_tools.ninja_path, |
| '--target', target or self.c.build_config_fs, |
| '--src-dir', self.m.path['checkout'], |
| '--goma-cache-dir', self.m.path['goma_cache'], |
| ] |
| |
| # Set some buildbot info used in goma_utils. |
| for key in ['buildername', 'mastername', 'slavename', 'clobber']: |
| if key in self.m.properties: |
| args.extend([ |
| '--buildbot-%s' % key, self.m.properties[key] |
| ]) |
| |
| # Use explicit goma deps cache dir if it's set in the path config. |
| # Otherwise use the default one inside build output directory. |
| try: |
| args.extend(['--goma-deps-cache-dir', self.m.path['goma_deps_cache']]) |
| except KeyError: |
| pass |
| |
| if self.c.compile_py.build_args: |
| args += ['--build-args', self.c.compile_py.build_args] |
| if self.m.properties.get('build_data_dir'): |
| args += ['--build-data-dir', self.m.properties.get('build_data_dir')] |
| if self.c.compile_py.compiler: |
| args += ['--compiler', self.c.compile_py.compiler] |
| if 'goma' in self.c.compile_py.compiler: |
| args += [ |
| '--goma-jsonstatus', self.m.json.output(), |
| '--goma-service-account-json-file', |
| self.m.goma.service_account_json_path, |
| ] |
| if out_dir: |
| args += ['--out-dir', out_dir] |
| if self.c.compile_py.mode: |
| args += ['--mode', self.c.compile_py.mode] |
| if self.c.compile_py.goma_dir: |
| args += ['--goma-dir', self.c.compile_py.goma_dir] |
| if self.c.compile_py.goma_hermetic: |
| args += ['--goma-hermetic', self.c.compile_py.goma_hermetic] |
| if self.c.compile_py.goma_enable_remote_link: |
| args += ['--goma-enable-remote-link'] |
| if self.c.compile_py.goma_store_local_run_output: |
| args += ['--goma-store-local-run-output'] |
| if self.m.tryserver.is_tryserver: |
| # We rely on goma to meet cycle time goals on the tryserver. It's better |
| # to fail early. |
| args += ['--goma-fail-fast', '--goma-disable-local-fallback'] |
| if self.c.compile_py.ninja_confirm_noop: |
| args.append('--ninja-ensure-up-to-date') |
| if self.c.TARGET_CROS_BOARD: |
| args += ['--cros-board', self.c.TARGET_CROS_BOARD] |
| |
| args.append('--') |
| args.extend(targets) |
| |
| if self.c.TARGET_CROS_BOARD: |
| # Wrap 'compile' through 'cros chrome-sdk' |
| kwargs['wrapper'] = self.get_cros_chrome_sdk_wrapper() |
| kwargs.setdefault('cwd', self.m.path['checkout']) |
| |
| env = self.get_env() |
| env.update(kwargs.pop('env', {})) |
| |
| try: |
| self.m.python( |
| name or 'compile', |
| self.package_repo_resource('scripts', 'tools', 'runit.py'), |
| args, |
| env=env, |
| **kwargs) |
| except self.m.step.StepFailure as e: |
| # Handle failures caused by goma. |
| if self.c.compile_py.compiler and 'goma' in self.c.compile_py.compiler: |
| step_result = self.m.step.active_result |
| failure_result_code = '' |
| try: |
| json_status = step_result.json.output['notice'][0] |
| if (not json_status.get('infra_status')): |
| failure_result_code = 'GOMA_SETUP_FAILURE' |
| elif json_status['infra_status']['ping_status_code'] != 200: |
| failure_result_code = 'GOMA_PING_FAILURE' |
| elif json_status['infra_status'].get('num_user_error', 0) > 0: |
| failure_result_code = 'GOMA_BUILD_ERROR' |
| except Exception as ex: |
| step_result.presentation.logs['exception'] = ['%r' % ex] |
| step_result.presentation.status = self.m.step.WARNING |
| |
| if failure_result_code: |
| # Mark goma setup failure as exception instead of step failure. |
| step_result.presentation.status = self.m.step.EXCEPTION |
| assert len(failure_result_code) <= 20 |
| properties = self.m.step.active_result.presentation.properties |
| if not properties.get('extra_result_code'): |
| properties['extra_result_code'] = [] |
| properties['extra_result_code'].append(failure_result_code) |
| raise self.m.step.InfraFailure('Infra compile failure: %s' % e) |
| |
| raise e |
| |
| @recipe_util.returns_placeholder |
| def test_launcher_filter(self, tests): |
| return TestLauncherFilterFileInputPlaceholder(self, tests) |
| |
| def runtest(self, test, args=None, xvfb=False, name=None, annotate=None, |
| results_url=None, perf_dashboard_id=None, test_type=None, |
| python_mode=False, spawn_dbus=True, parallel=False, |
| point_id=None, revision=None, webkit_revision=None, |
| test_launcher_summary_output=None, flakiness_dash=None, |
| perf_id=None, perf_config=None, chartjson_file=False, |
| disable_src_side_runtest_py=False, **kwargs): |
| """Return a runtest.py invocation.""" |
| args = args or [] |
| assert isinstance(args, list) |
| |
| t_name, ext = self.m.path.splitext(self.m.path.basename(test)) |
| if not python_mode and self.m.platform.is_win and ext == '': |
| test += '.exe' |
| |
| full_args = ['--target', self.c.build_config_fs] |
| if self.c.TARGET_PLATFORM == 'android': |
| full_args.extend(['--test-platform', 'android']) |
| if self.m.platform.is_linux: |
| full_args.append('--xvfb' if xvfb else '--no-xvfb') |
| |
| properties_json = self.m.json.dumps(self.m.properties.legacy()) |
| full_args.extend(['--factory-properties', properties_json, |
| '--build-properties', properties_json]) |
| |
| if annotate: |
| full_args.append('--annotate=%s' % annotate) |
| kwargs['allow_subannotations'] = True |
| |
| if annotate != 'gtest': |
| assert not flakiness_dash |
| |
| if results_url: |
| full_args.append('--results-url=%s' % results_url) |
| if perf_dashboard_id: |
| full_args.append('--perf-dashboard-id=%s' % perf_dashboard_id) |
| if perf_id: |
| full_args.append('--perf-id=%s' % perf_id) |
| if perf_config: |
| full_args.extend(['--perf-config', perf_config]) |
| # This replaces the step_name that used to be sent via factory_properties. |
| if test_type: |
| full_args.append('--test-type=%s' % test_type) |
| step_name = name or t_name |
| full_args.append('--step-name=%s' % step_name) |
| if chartjson_file: |
| full_args.append('--chartjson-file') |
| full_args.append(self.m.json.output()) |
| kwargs['step_test_data'] = lambda: self.m.json.test_api.output([]) |
| if test_launcher_summary_output: |
| full_args.extend([ |
| '--test-launcher-summary-output', |
| test_launcher_summary_output |
| ]) |
| if flakiness_dash: |
| full_args.extend([ |
| '--generate-json-file', |
| '-o', 'gtest-results/%s' % test, |
| ]) |
| # The flakiness dashboard needs the buildnumber, so we assert it here. |
| assert self.m.properties.get('buildnumber') is not None |
| |
| # These properties are specified on every bot, so pass them down |
| # unconditionally. |
| full_args.append('--builder-name=%s' % self.m.properties['buildername']) |
| full_args.append('--slave-name=%s' % self.m.properties['slavename']) |
| # A couple of the recipes contain tests which don't specify a buildnumber, |
| # so make this optional. |
| if self.m.properties.get('buildnumber') is not None: |
| full_args.append('--build-number=%s' % self.m.properties['buildnumber']) |
| if ext == '.py' or python_mode: |
| full_args.append('--run-python-script') |
| if not spawn_dbus: |
| full_args.append('--no-spawn-dbus') |
| if point_id: |
| full_args.append('--point-id=%d' % point_id) |
| if revision: |
| full_args.append('--revision=%s' % revision) |
| if webkit_revision: |
| # TODO(kbr): figure out how to cover this line of code with |
| # tests after the removal of the GPU recipe. crbug.com/584469 |
| full_args.append( |
| '--webkit-revision=%s' % webkit_revision) # pragma: no cover |
| |
| if (self.c.gyp_env.GYP_DEFINES.get('asan', 0) == 1 or |
| self.c.runtests.run_asan_test): |
| full_args.append('--enable-asan') |
| if self.c.runtests.enable_lsan: |
| full_args.append('--enable-lsan') |
| if self.c.gyp_env.GYP_DEFINES.get('msan', 0) == 1: |
| full_args.append('--enable-msan') |
| if self.c.gyp_env.GYP_DEFINES.get('tsan', 0) == 1: |
| full_args.append('--enable-tsan') |
| if self.c.runtests.memory_tool: |
| full_args.extend([ |
| '--pass-build-dir', |
| '--pass-target', |
| '--run-shell-script', |
| self.c.runtests.memory_tests_runner, |
| '--test', t_name, |
| '--tool', self.c.runtests.memory_tool, |
| ]) |
| else: |
| full_args.append(test) |
| |
| full_args.extend(self.c.runtests.test_args) |
| full_args.extend(args) |
| |
| runtest_path = self.package_repo_resource('scripts', 'slave', 'runtest.py') |
| if self.c.runtest_py.src_side and not disable_src_side_runtest_py: |
| runtest_path = self.m.path['checkout'].join( |
| 'infra', 'scripts', 'runtest_wrapper.py') |
| # Note that -- is needed since full_args are not indended |
| # for wrapper script but for real runtest.py . |
| full_args = ['--'] + full_args |
| return self.m.python( |
| step_name, |
| runtest_path, |
| full_args, |
| **kwargs |
| ) |
| |
| def sizes(self, results_url=None, perf_id=None, platform=None, **kwargs): |
| """Return a sizes.py invocation. |
| This uses runtests.py to upload the results to the perf dashboard.""" |
| sizes_script = self.package_repo_resource( |
| 'scripts', 'slave', 'chromium', 'sizes.py') |
| sizes_args = ['--target', self.c.build_config_fs] |
| if platform: |
| sizes_args.extend(['--platform', platform]) |
| else: |
| sizes_args.extend(['--platform', self.c.TARGET_PLATFORM]) |
| |
| run_tests_args = ['--target', self.c.build_config_fs, |
| '--no-xvfb'] |
| properties_json = self.m.json.dumps(self.m.properties.legacy()) |
| run_tests_args.extend(['--factory-properties', properties_json, |
| '--build-properties', properties_json]) |
| run_tests_args.extend(['--test-type=sizes', |
| '--builder-name=%s' % self.m.properties['buildername'], |
| '--slave-name=%s' % self.m.properties['slavename'], |
| '--build-number=%s' % self.m.properties['buildnumber'], |
| '--run-python-script']) |
| |
| if perf_id: |
| assert results_url is not None |
| run_tests_args.extend(['--annotate=graphing', |
| '--results-url=%s' % results_url, |
| '--perf-dashboard-id=sizes', |
| '--perf-id=%s' % perf_id]) |
| |
| # If we have a clang revision, add that to the perf data point. |
| # TODO(hans): We want this for all perf data, not just sizes. |
| if hasattr(self, '_clang_version'): |
| clang_rev = re.match(r'(\d+)(-\d+)?', self._clang_version).group(1) |
| run_tests_args.append( |
| "--perf-config={'r_clang_rev': '%s'}" % clang_rev) |
| |
| full_args = run_tests_args + [sizes_script] + sizes_args |
| |
| return self.m.python( |
| 'sizes', self.package_repo_resource('scripts', 'slave', 'runtest.py'), |
| full_args, allow_subannotations=True, **kwargs) |
| |
| def get_clang_version(self, **kwargs): |
| step_result = self.m.python( |
| 'clang_revision', |
| self.package_repo_resource('scripts', 'slave', 'clang_revision.py'), |
| args=['--src-dir', self.m.path['checkout'], |
| '--output-json', self.m.json.output()], |
| step_test_data=lambda: |
| self.m.json.test_api.output({'clang_revision': '123456-7'}), |
| allow_subannotations=True, |
| env=self.get_env(), |
| **kwargs) |
| return step_result.json.output['clang_revision'] |
| |
| |
| def get_cros_chrome_sdk_wrapper(self, clean=False): |
| """Returns: a wrapper command for 'cros chrome-sdk' |
| |
| Args: |
| external: (bool) If True, force the wrapper to prefer external board |
| configurations over internal ones, even if the latter is available. |
| clean: (bool) If True, instruct the wrapper to clean any previous |
| state data. |
| """ |
| assert self.c.TARGET_CROS_BOARD |
| wrapper = [ |
| self.m.depot_tools.cros_path, 'chrome-sdk', |
| '--board=%s' % (self.c.TARGET_CROS_BOARD,), |
| '--nocolor',] |
| wrapper += self.c.cros_sdk.args |
| if self.c.cros_sdk.external: |
| wrapper += ['--use-external-config'] |
| if clean: |
| wrapper += ['--clear-sdk-cache'] |
| if self.c.compile_py.goma_dir: |
| wrapper += ['--gomadir', self.c.compile_py.goma_dir] |
| if self.c.gyp_env.GYP_DEFINES.get('fastbuild', 0) == 1: |
| wrapper += ['--fastbuild'] |
| wrapper += ['--'] |
| return wrapper |
| |
| def ensure_goma(self, canary=False): |
| goma_dir = self.m.goma.ensure_goma(canary=canary) |
| if goma_dir: |
| # TODO(phajdan.jr): goma_dir should always be non-empty. |
| self.c.gyp_env.GYP_DEFINES['gomadir'] = goma_dir |
| self.c.compile_py.goma_dir = goma_dir |
| |
| def clobber_if_needed(self): |
| """Add an explicit clobber step if requested.""" |
| # clobber_before_runhooks is true for bots that apply the 'clobber' config, |
| # that is for bots that do clobber bots on every build. |
| # properties.get('clobber') is true on bots that normally don't clobber, |
| # when the "Clobber" button in the buildbot UI is pressed. |
| if (self.c.clobber_before_runhooks or |
| self.m.properties.get('clobber') is not None): |
| self.m.file.rmtree('clobber', self.output_dir) |
| |
| def runhooks(self, **kwargs): |
| """Run the build-configuration hooks for chromium.""" |
| |
| # runhooks might write things into the output directory, so clobber before |
| # that. |
| self.clobber_if_needed() |
| |
| env = self.get_env() |
| env.update(kwargs.get('env', {})) |
| |
| # CrOS "chrome_sdk" builds fully override GYP_DEFINES in the wrapper. Zero |
| # it to not show confusing information in the build logs. |
| if not self.c.TARGET_CROS_BOARD: |
| # TODO(sbc): Ideally we would not need gyp_env set during runhooks when |
| # we are not running gyp, but there are some hooks (such as sysroot |
| # installation that peek at GYP_DEFINES and modify thier behaviour |
| # accordingly. |
| env.update(self.c.gyp_env.as_jsonish()) |
| |
| if self.c.project_generator.tool != 'gyp': |
| env['GYP_CHROMIUM_NO_ACTION'] = 1 |
| kwargs['env'] = env |
| if self.c.TARGET_CROS_BOARD: |
| # Wrap 'runhooks' through 'cros chrome-sdk' |
| kwargs['wrapper'] = self.get_cros_chrome_sdk_wrapper(clean=True) |
| self.m.gclient.runhooks(**kwargs) |
| |
| # No cover because internal recipes use this. |
| def run_gyp_chromium(self): # pragma: no cover |
| gyp_chromium_path = self.m.path['checkout'].join('build', 'gyp_chromium.py') |
| env = self.get_env() |
| env.update(self.c.gyp_env.as_jsonish()) |
| self.m.python(name='gyp_chromium', script=gyp_chromium_path, env=env); |
| |
| def run_gn(self, use_goma=False, gn_path=None, build_dir=None, **kwargs): |
| if not gn_path: |
| gn_path = self.m.depot_tools.gn_py_path |
| |
| kwargs.setdefault('cwd', self.m.path['checkout']) |
| |
| gn_args = list(self.c.gn_args) |
| |
| # TODO(dpranke): Figure out if we should use the '_x64' thing to |
| # consistent w/ GYP, or drop it to be consistent w/ the other platforms. |
| build_dir = build_dir or '//out/%s' % self.c.build_config_fs |
| |
| if self.c.BUILD_CONFIG == 'Debug': |
| gn_args.append('is_debug=true') |
| if self.c.BUILD_CONFIG == 'Release': |
| gn_args.append('is_debug=false') |
| |
| if self.c.TARGET_PLATFORM == 'android': |
| gn_args.append('target_os="android"') |
| elif self.c.TARGET_PLATFORM in ('linux', 'mac', 'win'): |
| assert self.c.TARGET_ARCH == 'intel' |
| |
| gn_cpu = { |
| ('intel', 32): 'x86', |
| ('intel', 64): 'x64', |
| ('arm', 32): 'arm', |
| ('arm', 64): 'arm64', |
| ('mipsel', 32): 'mipsel', |
| }.get((self.c.TARGET_ARCH, self.c.TARGET_BITS)) |
| if gn_cpu: |
| gn_args.append('target_cpu="%s"' % gn_cpu) |
| |
| # TODO: crbug.com/395784. |
| # Consider getting the flags to use via the project_generator config |
| # and/or modifying the goma config to modify the gn flags directly, |
| # rather than setting the gn_args flags via a parameter passed to |
| # run_gn(). We shouldn't have *three* different mechanisms to control |
| # what args to use. |
| if use_goma: |
| gn_args.append('use_goma=true') |
| gn_args.append('goma_dir="%s"' % self.c.compile_py.goma_dir) |
| gn_args.extend(self.c.project_generator.args) |
| |
| # TODO(jbudorick): Change this s.t. no clients use gn.py. |
| step_args = [ |
| '--root=%s' % str(self.m.path['checkout']), |
| 'gen', |
| build_dir, |
| '--args=%s' % ' '.join(gn_args), |
| ] |
| if str(gn_path).endswith('.py'): |
| self.m.python(name='gn', script=gn_path, args=step_args, **kwargs) |
| else: |
| self.m.step(name='gn', cmd=[gn_path] + step_args, **kwargs) |
| |
| def run_mb(self, mastername, buildername, use_goma=True, |
| mb_config_path=None, isolated_targets=None, name=None, |
| build_dir=None, android_version_code=None, |
| android_version_name=None, gyp_script=None, phase=None, |
| **kwargs): |
| mb_config_path = (mb_config_path or |
| self.m.path['checkout'].join('tools', 'mb', |
| 'mb_config.pyl')) |
| isolated_targets = isolated_targets or [] |
| |
| out_dir = 'out' |
| if self.c.TARGET_CROS_BOARD: |
| out_dir += '_%s' % self.c.TARGET_CROS_BOARD |
| |
| build_dir = build_dir or '//%s/%s' % (out_dir, self.c.build_config_fs) |
| |
| args=[ |
| 'gen', |
| '-m', mastername, |
| '-b', buildername, |
| '--config-file', mb_config_path, |
| ] |
| |
| if phase is not None: |
| args += [ '--phase', str(phase) ] |
| |
| if use_goma: |
| goma_dir = self.c.compile_py.goma_dir |
| if not goma_dir: |
| # This method defaults to use_goma=True, which doesn't necessarily |
| # match build-side configuration. However, MB is configured |
| # src-side, and so it might be actually using goma. |
| self.ensure_goma() |
| goma_dir = self.c.compile_py.goma_dir |
| if not goma_dir: # pragma: no cover |
| # TODO(phajdan.jr): remove fallback when we always use cipd for goma. |
| goma_dir = self.m.path['build'].join('goma') |
| args += ['--goma-dir', goma_dir] |
| |
| if isolated_targets: |
| sorted_isolated_targets = sorted(set(isolated_targets)) |
| # TODO(dpranke): Change the MB flag to '--isolate-targets-file', maybe? |
| data = '\n'.join(sorted_isolated_targets) + '\n' |
| args += ['--swarming-targets-file', self.m.raw_io.input(data)] |
| |
| if android_version_code: |
| args += ['--android-version-code=%s' % android_version_code] |
| if android_version_name: |
| args += ['--android-version-name=%s' % android_version_name] |
| |
| if gyp_script: |
| args += ['--gyp-script=%s' % gyp_script] |
| |
| args += [build_dir] |
| |
| # This runs with an almost-bare env being passed along, so we get a clean |
| # environment without any GYP_DEFINES being present to cause confusion. |
| step_kwargs = { |
| 'name': name or 'generate_build_files', |
| 'script': self.m.path['checkout'].join('tools', 'mb', 'mb.py'), |
| 'args': args, |
| 'env': { |
| 'GOMA_SERVICE_ACCOUNT_JSON_FILE': self.m.goma.service_account_json_path, |
| }, |
| 'cwd': self.m.path['checkout'], |
| } |
| if self.c.env.FORCE_MAC_TOOLCHAIN: |
| step_kwargs['env']['FORCE_MAC_TOOLCHAIN'] = ( |
| self.c.env.FORCE_MAC_TOOLCHAIN) |
| |
| if self.c.gyp_env.GYP_MSVS_VERSION: |
| # TODO(machenbach): Remove this as soon as it's not read anymore by |
| # vs_toolchain.py (currently called by gn). |
| step_kwargs['env']['GYP_MSVS_VERSION'] = ( |
| self.c.gyp_env.GYP_MSVS_VERSION) |
| |
| if self.c.TARGET_CROS_BOARD: |
| # Wrap 'runhooks' through 'cros chrome-sdk' |
| step_kwargs['wrapper'] = self.get_cros_chrome_sdk_wrapper(clean=True) |
| |
| step_kwargs.update(kwargs) |
| self.m.python(**step_kwargs) |
| |
| # Comes after self.m.python so the log appears in the correct step result. |
| result = self.m.step.active_result |
| if isolated_targets and result: |
| result.presentation.logs['swarming-targets-file.txt'] = ( |
| sorted_isolated_targets) |
| |
| |
| def update_clang(self): |
| # The hooks in DEPS call `update.py --if-needed`, which updates clang by |
| # default on Mac and Linux, or if clang=1 is in GYP_DEFINES. This step |
| # is only needed on bots that use clang but where --if-needed doesn't update |
| # clang. (In practice, this means on Windows when using gn, not gyp.) |
| self.m.python(name='update_clang', |
| script=self.m.path['checkout'].join('tools', 'clang', |
| 'scripts', 'update.py')) |
| |
| def taskkill(self): |
| self.m.python( |
| 'taskkill', |
| self.package_repo_resource('scripts', 'slave', 'kill_processes.py'), |
| infra_step=True) |
| |
| def cleanup_temp(self): |
| self.m.python( |
| 'cleanup_temp', |
| self.package_repo_resource('scripts', 'tools', 'runit.py'), |
| ['--show-path', |
| 'python', |
| self.package_repo_resource('scripts', 'slave', 'cleanup_temp.py')], |
| infra_step=True) |
| |
| def process_dumps(self, **kwargs): |
| # Dumps are especially useful when other steps (e.g. tests) are failing. |
| try: |
| self.m.python( |
| 'process_dumps', |
| self.package_repo_resource('scripts', 'slave', 'process_dumps.py'), |
| ['--target', self.c.build_config_fs], |
| infra_step=True, |
| **kwargs) |
| except self.m.step.InfraFailure: |
| pass |
| |
| def apply_syzyasan(self): |
| args = [ |
| '--src-dir', self.m.path['checkout'], |
| '--target', self.c.BUILD_CONFIG, |
| ] |
| self.m.python( |
| 'apply_syzyasan', |
| self.package_repo_resource( |
| 'scripts', 'slave', 'chromium', 'win_apply_syzyasan.py'), |
| args) |
| |
| def archive_build(self, step_name, gs_bucket, gs_acl=None, mode=None, |
| **kwargs): |
| """Returns a step invoking archive_build.py to archive a Chromium build.""" |
| |
| # archive_build.py insists on inspecting factory properties. For now just |
| # provide these options in the format it expects. |
| fake_factory_properties = { |
| 'gclient_env': self.c.gyp_env.as_jsonish(), |
| 'gs_bucket': 'gs://%s' % gs_bucket, |
| } |
| if gs_acl is not None: |
| fake_factory_properties['gs_acl'] = gs_acl |
| if self.c.TARGET_PLATFORM == 'android': |
| fake_factory_properties['target_os'] = 'android' |
| |
| sanitized_buildername = ''.join( |
| c if c.isalnum() else '_' for c in self.m.properties['buildername']) |
| |
| args = [ |
| '--src-dir', self.m.path['checkout'], |
| '--build-name', sanitized_buildername, |
| '--staging-dir', self.m.path['cache'].join('chrome_staging'), |
| '--target', self.c.build_config_fs, |
| '--factory-properties', self.m.json.dumps(fake_factory_properties), |
| ] |
| if self.build_properties: |
| args += [ |
| '--build-properties', self.m.json.dumps(self.build_properties), |
| ] |
| if mode: |
| args.extend(['--mode', mode]) |
| self.m.python( |
| step_name, |
| self.package_repo_resource( |
| 'scripts', 'slave', 'chromium', 'archive_build.py'), |
| args, |
| infra_step=True, |
| **kwargs) |
| |
| def list_perf_tests(self, browser, num_shards, device=None): |
| args = ['list', '--browser', browser, '--json-output', |
| self.m.json.output(), '--num-shards', num_shards] |
| if device: |
| args += ['--device', device] |
| |
| return self.m.python( |
| 'List Perf Tests', |
| self.m.path['checkout'].join('tools', 'perf', 'run_benchmark'), |
| args, |
| step_test_data=lambda: self.m.json.test_api.output({ |
| "steps": { |
| "blink_perf.all.release": { |
| "cmd": "/usr/bin/python /path/to/run_benchmark --a=1 -v --b=2", |
| "perf_dashboard_id": "blink_perf.all", |
| "device_affinity": 0 |
| }, |
| "blink_perf.all.exact": { |
| "cmd": "/usr/bin/python /path/to/run_benchmark --a=1 -v --b=2", |
| "perf_dashboard_id": "blink_perf.all", |
| "device_affinity": 1 % num_shards |
| }, |
| "blink_perf.dom": { |
| "cmd": "/path/to/run_benchmark -v --upload-results blink_perf.dom", |
| "perf_dashboard_id": "blink_perf.dom", |
| "device_affinity": 1 % num_shards |
| }, |
| "dromaeo.cssqueryjquery.release": { |
| "cmd": "/path/to/run_benchmark", |
| "perf_dashboard_id": "dromaeo.cssqueryjquery", |
| "device_affinity": 11 % num_shards |
| }, |
| "dromaeo.cssqueryjquery": { |
| "cmd": "/path/to/run_benchmark", |
| "device_affinity": 13 % num_shards |
| }, |
| }, |
| "version": 2, |
| })) |
| |
| def get_annotate_by_test_name(self, test_name): |
| return 'graphing' |
| |
| def download_lto_plugin(self): |
| return self.m.python( |
| name='download LTO plugin', |
| script=self.m.path['checkout'].join( |
| 'build', 'download_gold_plugin.py')) |