| #!/usr/bin/env vpython3 | |
| # Copyright 2026 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Extracts CAS isolate digest hash from Buildbucket properties.""" | |
| import argparse | |
| import json | |
| import re | |
| import subprocess | |
| import sys | |
| def _query_properties(build_id: str, target: str) -> str | None: | |
| """Queries bb CLI for CAS digest in a single build. | |
| Args: | |
| build_id: Buildbucket build ID string. | |
| target: Target test suite or compilation output name. | |
| Returns: | |
| Hexadecimal CAS digest hash string or None. | |
| """ | |
| cmd_in = ['bb', 'get', '-json', '-fields', 'input.properties', build_id] | |
| res = subprocess.run(cmd_in, capture_output=True, text=True, check=False) | |
| if res.returncode == 0: | |
| try: | |
| data = json.loads(res.stdout) | |
| props = data.get('input', {}).get('properties', {}) | |
| hashes = props.get('swarm_hashes', {}) | |
| if target in hashes and isinstance(hashes[target], str): | |
| return hashes[target].split('/')[0] | |
| except json.JSONDecodeError: | |
| pass | |
| cmd_out = ['bb', 'get', '-json', '-fields', 'output.properties', build_id] | |
| res = subprocess.run(cmd_out, capture_output=True, text=True, check=False) | |
| if res.returncode == 0: | |
| try: | |
| data = json.loads(res.stdout) | |
| props = data.get('output', {}).get('properties', {}) | |
| if target in props and isinstance(props[target], str): | |
| return props[target].split('/')[0] | |
| hashes = props.get('swarm_hashes', {}) | |
| if target in hashes and isinstance(hashes[target], str): | |
| return hashes[target].split('/')[0] | |
| trig = props.get('swarming_trigger_properties', {}) | |
| trig_hashes = trig.get('swarm_hashes', {}) | |
| if target in trig_hashes and isinstance(trig_hashes[target], str): | |
| return trig_hashes[target].split('/')[0] | |
| except json.JSONDecodeError: | |
| pass | |
| return None | |
| def get_digest_from_properties(build_id: str, target: str) -> str | None: | |
| """Queries bb CLI to retrieve CAS digest from build or child compilator. | |
| Args: | |
| build_id: Buildbucket build ID string. | |
| target: Target test suite or compilation output name. | |
| Returns: | |
| Hexadecimal CAS digest hash string or None. | |
| """ | |
| digest = _query_properties(build_id, target) | |
| if digest: | |
| return digest | |
| cmd = ['bb', 'get', '-json', '-fields', 'steps', build_id] | |
| res = subprocess.run(cmd, capture_output=True, text=True, check=False) | |
| if res.returncode == 0: | |
| try: | |
| data = json.loads(res.stdout) | |
| for step in data.get('steps', []): | |
| summary = step.get('summaryMarkdown', '') | |
| match = re.search(r'\b8\d{18}\b', summary) | |
| if match: | |
| return _query_properties(match.group(0), target) | |
| except json.JSONDecodeError: | |
| pass | |
| return None | |
| def main() -> None: | |
| """Parses CLI args and outputs CAS digest hash to stdout.""" | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('--build', required=True, help='Buildbucket build ID') | |
| parser.add_argument('--step', required=True, help='Target suite name') | |
| args = parser.parse_args() | |
| digest = get_digest_from_properties(args.build, args.step) | |
| if not digest: | |
| print(f'CAS digest for "{args.step}" not found', file=sys.stderr) | |
| sys.exit(1) | |
| print(digest) | |
| if __name__ == '__main__': | |
| main() |