| # Copyright 2018 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. |
| |
| from recipe_engine import post_process |
| |
| DEPS = [ |
| 'chromium', |
| 'chromium_android', |
| 'chromium_checkout', |
| 'chromium_tests', |
| 'depot_tools/bot_update', |
| 'depot_tools/gclient', |
| 'depot_tools/gerrit', |
| 'depot_tools/tryserver', |
| 'filter', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/json', |
| 'recipe_engine/path', |
| 'recipe_engine/platform', |
| 'recipe_engine/properties', |
| 'recipe_engine/python', |
| 'recipe_engine/step', |
| ] |
| |
| _ANALYZE_TARGETS = [ |
| '//chrome/android:monochrome_public_apk', |
| '//tools/binary_size:binary_size_trybot_py', |
| ] |
| _COMPILE_TARGETS = [ |
| 'monochrome_public_apk', |
| ] |
| _APK_NAME = 'MonochromePublic.apk' |
| _PATCH_FIXED_BUILD_STEP_NAME = ( |
| 'Not measuring binary size because build is broken without patch.') |
| _FOOTER_PRESENT_STEP_NAME = ( |
| 'Not measuring binary size because Binary-Size justification was provided.') |
| |
| |
| def RunSteps(api): |
| assert api.tryserver.is_tryserver |
| |
| with api.chromium.chromium_layout(): |
| api.gclient.set_config('chromium') |
| api.gclient.apply_config('android') |
| api.chromium.set_config('chromium') |
| api.chromium.apply_config('mb') |
| api.chromium_android.set_config('base_config') |
| |
| revision_info = api.gerrit.get_revision_info( |
| api.properties['patch_gerrit_url'], |
| api.properties['patch_issue'], |
| api.properties['patch_set']) |
| author = revision_info['commit']['author']['email'] |
| # get_footer returns a list of footer values. |
| size_footers = api.tryserver.get_footer( |
| 'Binary-Size', patch_text=revision_info['commit']['message']) |
| # Short-circuit early so that the bot is fast when disabled via header. |
| # Although the bot is also meant to test compiles of official builds, |
| # headers are generally only added when a previous job compiles fine and |
| # fails with the "You need to add the header" message. |
| if size_footers: |
| api.python.succeeding_step(_FOOTER_PRESENT_STEP_NAME, '') |
| return |
| |
| suffix = ' (with patch)' |
| bot_update_step = api.bot_update.ensure_checkout(suffix=suffix, patch=True) |
| api.chromium.runhooks(name='runhooks' + suffix) |
| |
| affected_files = api.chromium_checkout.get_files_affected_by_patch() |
| if not api.filter.analyze(affected_files, _ANALYZE_TARGETS, None, |
| 'trybot_analyze_config.json')[0]: |
| return |
| |
| api.chromium.ensure_goma() |
| with_results_dir = _BuildAndMeasure(api, True) |
| |
| with api.context(cwd=api.chromium_checkout.working_dir): |
| api.bot_update.deapply_patch(bot_update_step) |
| |
| with api.context(cwd=api.path['checkout']): |
| suffix = ' (without patch)' |
| try: |
| api.chromium.runhooks(name='runhooks' + suffix) |
| without_results_dir = _BuildAndMeasure(api, False) |
| except api.step.StepFailure: |
| api.python.succeeding_step(_PATCH_FIXED_BUILD_STEP_NAME, '') |
| return |
| |
| # Re-apply patch so that the diff scripts can be tested via tryjobs. |
| # We could build without-patch first to avoid having to apply the patch |
| # twice, but it's nicer to fail fast when the patch does not compile. |
| suffix = ' (with patch again)' |
| api.bot_update.ensure_checkout(suffix=suffix, patch=True) |
| api.chromium.runhooks(name='runhooks' + suffix) |
| |
| with api.context(cwd=api.path['checkout']): |
| resource_sizes_diff_path, diff_summary_str = _ResourceSizesDiff( |
| api, without_results_dir, with_results_dir) |
| _SupersizeDiff(api, without_results_dir, with_results_dir) |
| _CheckForUnexpectedIncrease( |
| api, resource_sizes_diff_path, author, diff_summary_str) |
| |
| |
| def _BuildAndMeasure(api, with_patch): |
| suffix = ' (with patch)' if with_patch else ' (without patch)' |
| results_basename = 'with_patch' if with_patch else 'without_patch' |
| |
| api.chromium_tests.run_mb_and_compile(_COMPILE_TARGETS, None, suffix) |
| |
| results_dir = api.chromium.output_dir.join(results_basename) |
| api.file.ensure_directory('mkdir ' + results_basename, results_dir) |
| |
| apk_path = api.chromium_android.apk_path(_APK_NAME) |
| # Can't use api.chromium_android.resource_sizes() without it trying to upload |
| # the results. |
| api.python( |
| 'resource_sizes ({}){}'.format(api.path.basename(apk_path), suffix), |
| api.chromium_android.c.resource_sizes, [ |
| str(apk_path), |
| '--chartjson', |
| '--output-dir', results_dir, |
| '--chromium-output-directory', api.chromium.output_dir, |
| ]) |
| api.json.read( |
| 'resource_sizes result{}'.format(suffix), |
| results_dir.join('results-chart.json')) |
| |
| size_path = results_dir.join(_APK_NAME + '.size') |
| api.chromium_android.supersize_archive( |
| apk_path, size_path, step_suffix=suffix) |
| return results_dir |
| |
| |
| def _SupersizeDiff(api, without_results_dir, with_results_dir): |
| diagnose_bloat = api.path['checkout'].join( |
| 'tools', 'binary_size', 'diagnose_bloat.py') |
| diff_output_path = api.chromium.output_dir.join('supersize_diff.txt') |
| api.python('Supersize diff', diagnose_bloat, [ |
| 'diff', |
| '--apk-name', _APK_NAME, |
| '--diff-type', 'native', |
| '--before-dir', without_results_dir, |
| '--after-dir', with_results_dir, |
| '--diff-output', diff_output_path, |
| ]) |
| diff_text = api.file.read_text('Show Supersize Diff', diff_output_path) |
| read_step_result = api.step.active_result |
| read_step_result.presentation.step_text = '(Look here for detailed breakdown)' |
| read_step_result.presentation.logs['>>> Show Diff <<<'] = ( |
| diff_text.splitlines()) |
| |
| |
| def _ResourceSizesDiff(api, without_results_dir, with_results_dir): |
| diagnose_bloat = api.path['checkout'].join( |
| 'tools', 'binary_size', 'diagnose_bloat.py') |
| diff_output_path = api.chromium.output_dir.join('resource_sizes_diff.txt') |
| api.python('resource_sizes diff', diagnose_bloat, [ |
| 'diff', |
| '--apk-name', _APK_NAME, |
| '--diff-type', 'sizes', |
| '--before-dir', without_results_dir, |
| '--after-dir', with_results_dir, |
| '--diff-output', diff_output_path, |
| ]) |
| diff_text = api.file.read_text( |
| 'Show Resource Sizes Diff', diff_output_path, |
| test_data='MonochromePublic.apk_Specifics normalized apk size=-5\n') |
| diff_lines = diff_text.splitlines() |
| diff_summary_str = 'Normalized apk size: ' + diff_lines[-1].partition('=')[2] |
| read_step_result = api.step.active_result |
| read_step_result.presentation.step_text = '(Look here for high-level metrics)' |
| read_step_result.presentation.logs['>>> Show Diff <<<'] = diff_lines |
| return diff_output_path, diff_summary_str |
| |
| |
| def _CheckForUnexpectedIncrease(api, resource_sizes_diff_path, author, |
| diff_summary_str): |
| checker_script = api.path['checkout'].join( |
| 'tools', 'binary_size', 'trybot_commit_size_checker.py') |
| step_result = api.python('check for undocumented increase', checker_script, [ |
| '--author', author, |
| '--resource-sizes-diff', resource_sizes_diff_path, |
| ]) |
| step_result.presentation.step_text = '({})'.format(diff_summary_str) |
| |
| |
| def GenTests(api): |
| def props(name, size_footer=False, **kwargs): |
| kwargs.setdefault('path_config', 'kitchen') |
| kwargs['revision'] = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' |
| revision_info = { |
| '_number': 1, |
| 'commit': { |
| 'author': { |
| 'email': 'foo@bar.com', |
| }, |
| 'message': 'message', |
| } |
| } |
| footer_json = {} |
| if size_footer: |
| footer_json['Binary-Size'] = ['Totally worth it.'] |
| return ( |
| api.test(name) + |
| api.properties.tryserver( |
| build_config='Release', |
| mastername='tryserver.chromium.android', |
| buildername='android_binary_size', |
| patch_set=1, |
| **kwargs) + |
| api.platform('linux', 64) + |
| api.override_step_data( |
| 'gerrit changes', |
| api.json.output([{ |
| 'revisions': { |
| kwargs['revision']: revision_info |
| } |
| }])) + |
| api.override_step_data('parse description', |
| api.json.output(footer_json)) |
| ) |
| |
| |
| def override_analyze(no_changes=False): |
| """Overrides analyze step data so that targets get compiled.""" |
| return api.override_step_data( |
| 'analyze', |
| api.json.output({ |
| 'status': 'Found dependency', |
| 'compile_targets': _ANALYZE_TARGETS, |
| 'test_targets': [] if no_changes else _COMPILE_TARGETS})) |
| |
| yield ( |
| props('noop_because_of_size_footer', size_footer=True) + |
| api.post_process(post_process.MustRun, _FOOTER_PRESENT_STEP_NAME) + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('noop_because_of_analyze') + |
| override_analyze(no_changes=True) + |
| api.post_process(post_process.MustRun, 'analyze') + |
| api.post_process(post_process.DoesNotRunRE, r'.*build') + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('patch_fixes_build') + |
| override_analyze() + |
| api.override_step_data('compile (without patch)', retcode=1) + |
| api.post_process(post_process.MustRun, _PATCH_FIXED_BUILD_STEP_NAME) + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('normal_build') + |
| override_analyze() |
| ) |