| # Copyright 2026 The Chromium Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Pinned CIPD package versions and toolchains for GN.""" | |
| import argparse | |
| import dataclasses | |
| import json | |
| import pathlib | |
| import shutil | |
| import subprocess | |
| from typing import Optional | |
| from target import Arch, Platform, Target | |
| # https://chrome-infra-packages.appspot.com/p/fuchsia/third_party/clang | |
| # https://llvm.googlesource.com/llvm-project | |
| _CLANG_VERSION = 'integration' | |
| # https://chrome-infra-packages.appspot.com/p/fuchsia/third_party/sysroot/linux | |
| _SYSROOT_LINUX_VERSION = 'git_revision:c912d089c3d46d8982fdef76a50514cca79b6132' | |
| # https://chrome-infra-packages.appspot.com/p/fuchsia/third_party/sysroot/focal | |
| _SYSROOT_FOCAL_VERSION = 'git_revision:fa7a5a9710540f30ff98ae48b62f2cdf72ed2acd' | |
| _HOST_CLANG = 'fuchsia/third_party/clang/${platform}' | |
| _REPO_ROOT = pathlib.Path(__file__).resolve().parents[1] | |
| _DEFAULT_INSTALL_DIR = _REPO_ROOT / 'clang' | |
| @dataclasses.dataclass(frozen=True) | |
| class Package: | |
| name: str | |
| version: str | |
| path: str = '' | |
| @dataclasses.dataclass(frozen=True) | |
| class Toolchain: | |
| packages: list[Package] | |
| sysroot: Optional[pathlib.Path] | |
| env: dict[str, str] | |
| def get_toolchain(host: Target, | |
| target: Target, | |
| cipd_dir: pathlib.Path = _DEFAULT_INSTALL_DIR) -> Toolchain: | |
| """Returns the Toolchain required to cross-compile host to target.""" | |
| packages = [] | |
| env = {} | |
| sysroot = None | |
| if host.platform.is_linux() or host.platform.is_darwin(): | |
| packages.append(Package(_HOST_CLANG, _CLANG_VERSION)) | |
| env.update({ | |
| 'CC': str(cipd_dir / 'bin' / 'clang'), | |
| 'CXX': str(cipd_dir / 'bin' / 'clang++'), | |
| 'AR': str(cipd_dir / 'bin' / 'llvm-ar'), | |
| 'LD': str(cipd_dir / 'bin' / 'clang++'), | |
| }) | |
| if target.platform.is_linux(): | |
| # RISCV64 support starts in focal. | |
| if target.arch == Arch.RISCV64: | |
| packages.append( | |
| Package('fuchsia/third_party/sysroot/focal', _SYSROOT_FOCAL_VERSION, | |
| 'sysroot-focal')) | |
| sysroot = cipd_dir / 'sysroot-focal' | |
| else: | |
| packages.append( | |
| Package('fuchsia/third_party/sysroot/linux', _SYSROOT_LINUX_VERSION, | |
| 'sysroot')) | |
| sysroot = cipd_dir / 'sysroot' | |
| return Toolchain( | |
| packages=packages, | |
| sysroot=sysroot, | |
| env=env, | |
| ) | |
| def install(packages: list[Package]): | |
| """Installs packages locally via `cipd ensure`.""" | |
| cipd_bin = shutil.which('cipd') | |
| if not cipd_bin: | |
| raise FileNotFoundError('cipd executable not found in PATH') | |
| lines = [] | |
| by_subdir = {} | |
| for pkg in packages: | |
| by_subdir.setdefault(pkg.path, []).append(f'{pkg.name} {pkg.version}') | |
| for subdir, pkgs in by_subdir.items(): | |
| lines.append(f'@Subdir {subdir}\n' if subdir else '@Subdir\n') | |
| lines.extend([f'{p}\n' for p in pkgs]) | |
| _DEFAULT_INSTALL_DIR.mkdir(exist_ok=True) | |
| subprocess.run( | |
| [cipd_bin, 'ensure', '-ensure-file', '-', '-root', | |
| str(_DEFAULT_INSTALL_DIR)], | |
| input=''.join(lines), | |
| text=True, | |
| check=True, | |
| ) | |
| def _parse_target(target: str): | |
| os, cpu = target.split('-', 1) | |
| return Target(Platform(os), Arch(cpu)) | |
| def _json_default(obj): | |
| if dataclasses.is_dataclass(obj): | |
| return dataclasses.asdict(obj) | |
| elif isinstance(obj, pathlib.Path): | |
| return str(obj) | |
| raise NotImplementedError(repr(obj)) | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| '--host', | |
| type=_parse_target, | |
| default=Target.host(), | |
| help='Host platform (e.g. linux-amd64)', | |
| ) | |
| parser.add_argument( | |
| '--target', | |
| nargs='*', | |
| default=[], | |
| help='Target platforms', | |
| ) | |
| parser.add_argument( | |
| '--ci', | |
| type=pathlib.Path, | |
| help='If provided, this is running in CI, installing to this path.', | |
| ) | |
| args = parser.parse_args() | |
| targets = args.target if args.target else [args.host] | |
| if args.ci: | |
| out = {} | |
| for t in targets: | |
| target_obj = _parse_target(t) if isinstance(t, str) else t | |
| out[t if isinstance(t, str) else str(t)] = get_toolchain( | |
| args.host, target_obj, cipd_dir=args.ci | |
| ) | |
| print(json.dumps(out, default=_json_default)) | |
| else: | |
| install(get_toolchain(host=args.host, target=args.host).packages) | |
| if __name__ == '__main__': | |
| main() |