| #!/usr/bin/env python3 |
| # Copyright 2025 The Dawn & Tint Authors |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, this |
| # list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright notice, |
| # this list of conditions and the following disclaimer in the documentation |
| # and/or other materials provided with the distribution. |
| # |
| # 3. Neither the name of the copyright holder nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import argparse |
| import logging |
| import os |
| import pprint |
| import sys |
| import tempfile |
| from typing import Dict, List, Optional, Tuple |
| |
| # //testing/buildbot is only retrieved via DEPS for standalone checkouts. |
| THIS_DIR = os.path.dirname(os.path.abspath(__file__)) |
| DAWN_TESTING_BUILDBOT_DIR = os.path.realpath( |
| os.path.join(THIS_DIR, '..', '..', 'testing', 'buildbot')) |
| if os.path.isdir(DAWN_TESTING_BUILDBOT_DIR): |
| TESTING_BUILDBOT_DIR = DAWN_TESTING_BUILDBOT_DIR |
| else: |
| raise RuntimeError( |
| 'Unable to find //testing/buildbot/ - it seems likely that you are ' |
| 'running this from a Chromium checkout. Please run this from a ' |
| 'standalone Dawn checkout.') |
| |
| sys.path.insert(0, TESTING_BUILDBOT_DIR) |
| import generate_buildbot_json |
| |
| # Add custom mixins here. |
| ADDITIONAL_MIXINS = { |
| 'result_adapter_gtest_json': { |
| 'resultdb': { |
| 'result_format': 'gtest_json', |
| }, |
| }, |
| } |
| |
| MIXIN_FILEPATH = os.path.join(THIS_DIR, 'mixins.pyl') |
| MIXINS_PYL_TEMPLATE = """\ |
| # GENERATED FILE - DO NOT EDIT. |
| # Generated by {script_name} using data from {data_source} |
| # |
| # Copyright 2025 The Dawn & Tint Authors |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, this |
| # list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright notice, |
| # this list of conditions and the following disclaimer in the documentation |
| # and/or other materials provided with the distribution. |
| # |
| # 3. Neither the name of the copyright holder nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| # |
| # This is a .pyl, or "Python Literal", file. You can treat it just like a |
| # .json file, with the following exceptions: |
| # * all keys must be quoted (use single quotes, please); |
| # * comments are allowed, using '#' syntax; and |
| # * trailing commas are allowed. |
| # |
| # For more info see Chromium's mixins.pyl in testing/buildbot. |
| |
| {mixin_data} |
| """ |
| |
| |
| def _generate_mixins_pyl( |
| generator: generate_buildbot_json.BBJSONGenerator) -> str: |
| """Helper function to generate the mixins.pyl file. |
| |
| Args: |
| generator: The BBJSONGenerator instance to use for generating mixins. |
| |
| Returns: |
| A string containing the generated mixins.pyl content. |
| """ |
| mixins = _get_trimmed_mixins(generator) |
| pp = pprint.PrettyPrinter(indent=2) |
| generated_mixin_pyl = MIXINS_PYL_TEMPLATE.format( |
| script_name=os.path.basename(__file__), |
| data_source="waterfalls.pyl and Chromium's mixins.pyl", |
| mixin_data=pp.pformat(mixins)) |
| return generated_mixin_pyl |
| |
| |
| def _get_trimmed_mixins( |
| generator: generate_buildbot_json.BBJSONGenerator) -> Dict[str, dict]: |
| """Helper function to get a trimmed set of mixins. |
| |
| Chromium-provided mixins trimmed to only those that are actually used by |
| Dawn, then merged with any Dawn-specific mixins. |
| |
| Args: |
| generator: The BBJSONGenerator instance to use for generating mixins. |
| |
| Returns: |
| The resulting dict mapping mixin name to mixin definition. |
| """ |
| seen_mixins = set() |
| for waterfall in generator.waterfalls: |
| seen_mixins |= set(waterfall.get('mixins', [])) |
| for builder_spec in waterfall['machines'].values(): |
| seen_mixins |= set(builder_spec.get('mixins', [])) |
| for suite in generator.test_suites.values(): |
| if isinstance(suite, list): |
| # This is a compound suite, which does not include any mixins. |
| continue |
| for test in suite.values(): |
| assert isinstance(test, dict) |
| seen_mixins |= set(test.get('mixins', [])) |
| |
| chromium_mixins = generator.load_pyl_file( |
| os.path.join(TESTING_BUILDBOT_DIR, 'mixins.pyl')) |
| kept_mixins = ADDITIONAL_MIXINS.copy() |
| for mixin in seen_mixins: |
| if mixin in kept_mixins: |
| continue |
| assert mixin in chromium_mixins, f'Mixin {mixin} used but not defined' |
| kept_mixins[mixin] = chromium_mixins[mixin] |
| |
| return kept_mixins |
| |
| |
| def _write_or_verify_file(filepath: str, new_content: str, |
| verify_only: bool) -> None: |
| """Helper function to either write content to disk or verify it matches. |
| |
| Args: |
| filepath: The filepath to write |new_content| to if |verify_only| is |
| False. |
| new_content: The new content potentially being written to |filepath|. |
| verify_only: Determines whether |new_content| is actually written to |
| disk vs. asserting that the existing on-disk content matches |
| |new_content|. |
| """ |
| if verify_only: |
| with open(filepath, encoding='utf-8') as infile: |
| existing_content = infile.read() |
| if existing_content != new_content: |
| raise RuntimeError( |
| f'Generated and existing content for {filepath} do not match. ' |
| f'Please run {__file__} to re-generate content.') |
| else: |
| with open(filepath, 'w', encoding='utf-8') as outfile: |
| outfile.write(new_content) |
| |
| |
| def _run_generator(generator_args: List[str], |
| output_dir: Optional[str] = None) -> None: |
| """Runs the generate_buildbot_json script for Dawn. |
| |
| Args: |
| generator_args: A list of command line arguments to pass on to the |
| generator. |
| output_dir: An optional filepath to a directory to use for output. If |
| set, it is assumed that the generator is being run to verify that |
| generated files are up to date instead of actually saving updated |
| files to disk. |
| """ |
| verify_only = output_dir != None |
| |
| assert '--pyl-files-dir' not in generator_args |
| generator_args.extend(['--pyl-files-dir', THIS_DIR]) |
| if verify_only: |
| assert '--output-dir' not in generator_args |
| generator_args.extend(['--output-dir', output_dir]) |
| |
| args = generate_buildbot_json.BBJSONGenerator.parse_args(generator_args) |
| generator = generate_buildbot_json.BBJSONGenerator(args) |
| generator.load_configuration_files() |
| generator.resolve_configuration_files() |
| |
| mixin_content = _generate_mixins_pyl(generator) |
| _write_or_verify_file(MIXIN_FILEPATH, mixin_content, verify_only) |
| |
| retval = generator.main() |
| if retval != 0: |
| raise RuntimeError( |
| f'generate_buildbot_json.py failed with exit code {retval}') |
| |
| if verify_only: |
| for waterfall in generator.waterfalls: |
| json_filename = waterfall['name'] + '.json' |
| with open(os.path.join(output_dir, json_filename), |
| encoding='utf-8') as infile: |
| new_content = infile.read() |
| existing_filepath = os.path.join(THIS_DIR, json_filename) |
| _write_or_verify_file(existing_filepath, new_content, verify_only) |
| |
| |
| def _parse_args() -> Tuple[argparse.Namespace, List[str]]: |
| """Parses known and unknown args.""" |
| parser = argparse.ArgumentParser( |
| 'Generate //testing/buildbot JSON files. Unknown args will be passed ' |
| 'on to the underlying generate_buildbot_json.py script.') |
| parser.add_argument('--verify-only', |
| action='store_true', |
| default=False, |
| help=('Only verify that generated files are up to ' |
| 'date without writing new ones to disk.')) |
| args, unknown_args = parser.parse_known_args() |
| return args, unknown_args |
| |
| |
| def main() -> None: |
| args, unknown_args = _parse_args() |
| if args.verify_only: |
| with tempfile.TemporaryDirectory() as temp_dir: |
| _run_generator(unknown_args, temp_dir) |
| else: |
| _run_generator(unknown_args) |
| |
| |
| if __name__ == '__main__': |
| main() |