aboutsummaryrefslogtreecommitdiff
path: root/check_portable_toolchains.py
diff options
context:
space:
mode:
Diffstat (limited to 'check_portable_toolchains.py')
-rwxr-xr-xcheck_portable_toolchains.py224
1 files changed, 224 insertions, 0 deletions
diff --git a/check_portable_toolchains.py b/check_portable_toolchains.py
new file mode 100755
index 00000000..3e3bce8d
--- /dev/null
+++ b/check_portable_toolchains.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Verify that a given portable toolchain SDK version can link and compile.
+
+Used to test that new portable toolchain SDKs work. See go/crostc-mage for
+when to use this script.
+"""
+
+import argparse
+import json
+import logging
+import os
+from pathlib import Path
+import re
+import subprocess
+import sys
+import tempfile
+from typing import List, Optional, Tuple
+
+
+ABIS = (
+ "aarch64-cros-linux-gnu",
+ "armv7a-cros-linux-gnueabihf",
+ "x86_64-cros-linux-gnu",
+)
+
+GS_PREFIX = "gs://staging-chromiumos-sdk"
+
+# Type alias to make clear when a string is a specially
+# formatted timestamp-version string.
+Version = str
+
+HELLO_WORLD = """#include <iostream>
+
+int main() {
+ std::cout << "Hello world!" << std::endl;
+}
+"""
+
+_COLOR_RED = "\033[91m"
+_COLOR_GREEN = "\033[92m"
+_COLOR_RESET = "\033[0m"
+
+
+def main() -> int:
+ logging.basicConfig(
+ format=">> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: "
+ "%(message)s",
+ level=logging.INFO,
+ )
+ args = parse_args()
+
+ version = args.version
+ if not version:
+ version = _autodetect_latest_llvm_next_sdk_version()
+
+ errors: List[Tuple[str, Exception]] = []
+ for abi in ABIS:
+ res = check_abi(args.bucket_prefix, abi, version)
+ if res:
+ errors.append((abi, res))
+ if errors:
+ logging.error(
+ "%sAt least one ABI failed to validate: %s%s",
+ _COLOR_RED,
+ ", ".join(abi for (abi, _) in errors),
+ _COLOR_RESET,
+ )
+ return 1
+ logging.info(
+ "%sAll ABIs successfully validated :)%s",
+ _COLOR_GREEN,
+ _COLOR_RESET,
+ )
+ return 0
+
+
+def check_abi(
+ bucket_prefix: str, abi: str, version: Version
+) -> Optional[Exception]:
+ """Verify that a given ABI target triplet is okay."""
+ year, month, _ = _split_version(version)
+ toolchain_name = f"{abi}-{version}.tar.xz"
+ artifact_path = f"{bucket_prefix}/{year}/{month}/{toolchain_name}"
+ try:
+ with tempfile.TemporaryDirectory() as tmpdir_str:
+ tmpdir = Path(tmpdir_str)
+
+ def run(*args, **kwargs):
+ return subprocess.run(*args, check=True, cwd=tmpdir, **kwargs)
+
+ logging.info(
+ "Downloading the toolchain %s into %s",
+ artifact_path,
+ tmpdir,
+ )
+ run(["gsutil.py", "cp", artifact_path, tmpdir])
+
+ logging.info("Extracting the toolchain %s", toolchain_name)
+ run(["tar", "-axf", tmpdir / toolchain_name])
+
+ logging.info("Checking if can find ld linker")
+ proc = run(
+ [f"bin/{abi}-clang", "-print-prog-name=ld"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ linker_path = tmpdir / proc.stdout.strip()
+ logging.info("linker binary path: %s", linker_path)
+ if not linker_path.exists():
+ raise RuntimeError(f"{linker_path} does not exist")
+ if not os.access(linker_path, os.X_OK):
+ raise RuntimeError(f"{linker_path} is not executable")
+
+ logging.info("Building a simple c++ binary")
+ hello_world_file = tmpdir / "hello_world.cc"
+ hello_world_file.write_text(HELLO_WORLD, encoding="utf-8")
+ hello_world_output = tmpdir / "hello_world"
+ cmd = [
+ f"bin/{abi}-clang++",
+ "-o",
+ hello_world_output,
+ hello_world_file,
+ ]
+ run(cmd)
+ if not hello_world_output.exists():
+ raise RuntimeError(f"{hello_world_output} does not exist")
+ proc = run(
+ [f"bin/{abi}-clang++", "--version"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ logging.info(
+ "%s-clang++ --version:\n%s",
+ abi,
+ "> " + "\n> ".join(proc.stdout.strip().split("\n")),
+ )
+
+ logging.info(
+ "%s[PASS] %s was validated%s", _COLOR_GREEN, abi, _COLOR_RESET
+ )
+ except Exception as e:
+ logging.exception(
+ "%s[FAIL] %s could not be validated%s",
+ _COLOR_RED,
+ abi,
+ _COLOR_RESET,
+ )
+ return e
+ return None
+
+
+def _autodetect_latest_llvm_next_sdk_version() -> str:
+ output = subprocess.run(
+ [
+ "bb",
+ "ls",
+ "-json",
+ "-n",
+ "1",
+ "-status",
+ "success",
+ "chromeos/infra/build-chromiumos-sdk-llvm-next",
+ ],
+ check=True,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ ).stdout
+ builder_summary = json.loads(output)["summaryMarkdown"]
+ # Builder summary looks like:
+ # ```
+ # Built SDK version [2023.12.11.140022](https://link-redacted)
+ # Launched SDK uprev build: https://link-redacted
+ # ```
+ matches = re.findall(r"\[(\d+\.\d+\.\d+\.\d+)\]\(", builder_summary)
+ if len(matches) != 1:
+ raise ValueError(
+ f"Expected exactly 1 match of version in {builder_summary!r}."
+ f" Got {matches}. You can pass --version to disable auto-detection."
+ )
+ version = matches[0]
+ logging.info("Found latest llvm-next SDK version: %s", version)
+ return version
+
+
+def _split_version(version: Version) -> Tuple[str, str, str]:
+ y, m, rest = version.split(".", 2)
+ return y, m, rest
+
+
+def _verify_version(version: str) -> Version:
+ _split_version(version) # Raises a ValueError if invalid.
+ return version
+
+
+def parse_args() -> argparse.Namespace:
+ """Parse arguments."""
+ parser = argparse.ArgumentParser(
+ "check_portable_toolchains", description=__doc__
+ )
+ parser.add_argument(
+ "--version",
+ help="""
+ Version/Timestamp formatted as 'YYYY.MM.DD.HHMMSS'. e.g.
+ '2023.09.01.221258'. Generally this comes from a
+ 'build-chromiumos-sdk-llvm-next' run. Will autodetect if none is
+ specified.
+ """,
+ type=_verify_version,
+ )
+ parser.add_argument(
+ "-p",
+ "--bucket-prefix",
+ default=GS_PREFIX,
+ help="Top level gs:// path. (default: %(default)s)",
+ )
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ sys.exit(main())