#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Update in-tree checkout of Rust toolchain

Minimal Skia adapation of [1] to fetch a Rust revision manually
specified in MANUAL_REVISION below. Frequently roll this to latest
version available in [2].

[1] https://source.chromium.org/chromium/chromium/src/+/main:tools/rust/update_rust.py
[2] https://commondatastorage.googleapis.com/chromium-browser-clang/index.html?path=Linux_x64/rust-toolchain-

"""

import argparse
import os
import re
import shutil
import sys
import tempfile
import urllib

from pathlib import Path

# Chromium's Rust builds (for Linux) that worked are found at:
# https://commondatastorage.googleapis.com/chromium-browser-clang/index.html?path=Linux_x64/rust-toolchain-
# The latest builds are prefixed with a date, such as `20230101-1`.
# To update, roll this to versions that Chromium's tools/rust/build-rust.py has produced and which are found from the CDS url.
MANUAL_REVISION = (
    "ac4379fea9e83465d814bb05005689f49bd2141e-1-llvmorg-17-init-3874-g93a2fecc-1"
)

THIS_DIR = os.path.abspath(os.path.dirname(__file__))
SKIA_DIR = os.path.abspath(os.path.join(THIS_DIR, ".."))
THIRD_PARTY_DIR = os.path.join(SKIA_DIR, "third_party")
RUST_TOOLCHAIN_OUT_DIR = os.path.join(THIRD_PARTY_DIR, "rust-toolchain")
VERSION_STAMP_PATH = os.path.join(RUST_TOOLCHAIN_OUT_DIR, "VERSION")


def GetDownloadPackageVersion():
    # TODO(https://crbug.com/14191): This is hardcoded in Skia right
    # now, as Skia does not rebuild rust on its own and can't directly
    # access the most recent version that's been built for
    # Chromium. Could the Chromium side-build publish something like a
    # symbolic link to last-known-good?
    return MANUAL_REVISION


# Get the version of the toolchain package we already have.
def GetStampVersion():
    if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
        with open(VERSION_STAMP_PATH) as version_file:
            existing_stamp = version_file.readline().rstrip()
        version_re = re.compile(r"rustc [0-9.]+ [0-9a-f]+ \((.+?) chromium\)")
        match = version_re.fullmatch(existing_stamp)
        if match is None:
            return None
        return match.group(1)

    return None


def main():
    parser = argparse.ArgumentParser(description="Update Rust package")
    parser.add_argument(
        "--print-package-version",
        action="store_true",
        help="Print Rust package version (including both the "
        "Rust and Clang revisions) and quit.",
    )
    args = parser.parse_args()

    if args.print_package_version:
        print(GetDownloadPackageVersion())
        return 0

    from clang_update import DownloadAndUnpack, GetDefaultHostOs, GetPlatformUrlPrefix

    # Exit early if the existing package is up-to-date. Note that we cannot
    # simply call DownloadAndUnpack() every time: aside from unnecessarily
    # downloading the toolchain if it hasn't changed, it also leads to multiple
    # versions of the same rustlibs. build/rust/std/find_std_rlibs.py chokes in
    # this case.
    if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
        if GetDownloadPackageVersion() == GetStampVersion():
            return 0

    if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
        shutil.rmtree(RUST_TOOLCHAIN_OUT_DIR)

    try:
        host_os = GetDefaultHostOs()
        # TODO(https://crbug.com/skia/14190): Enable this on additional
        # platforms.
        if not "linux" in host_os:
            print(
                "Unsupported platform, Rust support only available on Linux "
                "at the moment, see https://crbug.com/skia/14190"
            )
            return 1
        platform_prefix = GetPlatformUrlPrefix(host_os)
        version = GetDownloadPackageVersion()
        url = f"{platform_prefix}rust-toolchain-{version}.tgz"
        DownloadAndUnpack(url, THIRD_PARTY_DIR)
    except urllib.error.HTTPError as e:
        # Fail softly for now. This can happen if a Rust package was not
        # produced, e.g. if the Rust build failed upon a Clang update, or if a
        # Rust roll and a Clang roll raced against each other.
        #
        # TODO(https://crbug.com/1245714): Reconsider how to handle this.
        print(f"warning: could not download Rust package")

    # Ensure the newly extracted package has the correct version.
    assert GetDownloadPackageVersion() == GetStampVersion()


if __name__ == "__main__":
    sys.exit(main())
