blob: 4007d71638b6c1a06e39c4a2eec3b42bb8c49cce [file] [log] [blame]
#!/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()