| #!/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() |