#!/usr/bin/env python3 # Copyright 2020 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Gets info about completed chromiumos-sdk runs. Moreover, this script exists to get versions of published sdk tarballs in gs://chromiumos-sdk/. The hope is that it'll help answer the question "when did the toolchain ebuild ${x} go live?" """ # pylint: disable=cros-logging-import import argparse import json import logging import os import shutil import subprocess import sys import tempfile from typing import Dict, List from pathlib import Path def fetch_all_sdk_manifest_paths() -> List[str]: """Fetches all paths of SDK manifests; newer = later in the return value.""" results = subprocess.run( ['gsutil', 'ls', 'gs://chromiumos-sdk/cros-sdk-20??.*.Manifest'], check=True, stdout=subprocess.PIPE, encoding='utf-8', ).stdout # These are named so that sorted order == newest last. return sorted(x.strip() for x in results.splitlines()) def fetch_manifests_into(into_dir: Path, manifests: List[str]): # Wrap this in a `try` block because gsutil likes to print to stdout *and* # stderr even on success, so we silence them & only print on failure. try: subprocess.run( [ 'gsutil', '-m', 'cp', '-I', str(into_dir), ], check=True, input='\n'.join(manifests), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', ) except subprocess.CalledProcessError as e: logging.exception('gsutil failed; output:\n%s', e.stdout) def load_manifest_versions(manifest: Path) -> Dict[str, str]: with manifest.open(encoding='utf-8') as f: raw_versions = json.load(f) # We get a dict of list of lists of versions and some other metadata, e.g. # {"foo/bar": [["1.2.3", {}]]} # Trim out the metadata. return {k: v[0][0] for k, v in raw_versions['packages'].items()} def main(): parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '-d', '--debug', action='store_true', help='Emit debugging output') parser.add_argument( '-n', '--number', type=int, default=20, help='Number of recent manifests to fetch info about. 0 means unlimited.') args = parser.parse_args() is_debug = args.debug logging.basicConfig(level=logging.DEBUG if is_debug else logging.INFO) logging.debug('Fetching SDK manifests') manifest_paths = fetch_all_sdk_manifest_paths() logging.debug('%d SDK manifests fetched', len(manifest_paths)) number = args.number if number: manifest_paths = manifest_paths[-number:] tempdir = Path(tempfile.mkdtemp(prefix='cros-sdk-rolls')) try: logging.debug('Working in tempdir %r', tempdir) fetch_manifests_into(tempdir, manifest_paths) for path in manifest_paths: basename = os.path.basename(path) versions = load_manifest_versions(tempdir.joinpath(basename)) print(f'{basename}: {versions["sys-devel/llvm"]}') finally: if is_debug: logging.debug('Keeping around tempdir %r to aid debugging', tempdir) else: shutil.rmtree(tempdir) if __name__ == '__main__': sys.exit(main())