1#!/usr/bin/env python3 2# Copyright 2021 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5# 6# Run inside `cros_sdk` to uprev dependency versions between Cargo.lock and 7# chromiumos-overlay ebuild files. 8 9import re 10import glob 11import os 12import sys 13import subprocess 14from distutils.version import StrictVersion 15from datetime import datetime 16 17# Ideally we would install a toml parser, but this will do for a quick little 18# tool. 19CARGO_LOCK_REGEX = re.compile( 20 r"""name = "([^"]+)" 21version = "([^"]+)" 22source =""" 23) 24 25IGNORED_PACKAGES = ["pin-utils", "pkg-config"] 26 27DEV_RUST_PATH = "../../third_party/chromiumos-overlay/dev-rust" 28EBUILD_FILE_GLOB = f"{DEV_RUST_PATH}/*/*.ebuild" 29EBUILD_FILE_REGEX = re.compile(r"([\w\-_]+)-([0-9\.]+)\.ebuild") 30YEAR = datetime.today().year 31 32 33def ebuild_template(package: str): 34 return f"""\ 35# Copyright {YEAR} The Chromium OS Authors. All rights reserved. 36# Distributed under the terms of the GNU General Public License v2 37 38EAPI="7" 39 40CROS_RUST_REMOVE_DEV_DEPS=1 41 42inherit cros-rust 43 44DESCRIPTION="Build file for the {package} crate." 45HOMEPAGE="https://crates.io/crates/{package}" 46SRC_URI="https://crates.io/api/v1/crates/${{PN}}/${{PV}}/download -> ${{P}}.crate" 47 48LICENSE="|| ( MIT Apache-2.0 )" 49SLOT="${{PV}}/${{PR}}" 50KEYWORDS="*" 51 52# TODO: Add crate dependencies 53DEPEND="" 54""" 55 56 57def ebuild_file_path(package: str, version: str): 58 return f"{DEV_RUST_PATH}/{package}/{package}-{version}.ebuild" 59 60 61def parse_cargo_lock(): 62 """Parses Cargo.lock file and returns (package, version) pairs.""" 63 with open("Cargo.lock", "r") as lock_file: 64 lock_str = lock_file.read() 65 for match in CARGO_LOCK_REGEX.finditer(lock_str): 66 yield (match.group(1), match.group(2)) 67 68 69def all_ebuild_versions(): 70 """Returns (package, version) pairs of ebuild files in dev-rust.""" 71 for ebuild_path in glob.glob(EBUILD_FILE_GLOB): 72 ebuild_name = os.path.basename(ebuild_path) 73 match = EBUILD_FILE_REGEX.match(ebuild_name) 74 if match: 75 yield (match.group(1), match.group(2)) 76 77 78def ebuild_versions_dict(): 79 """Returns a dict of package versions of all ebuild files in dev-rust.""" 80 versions: dict[str, list[str]] = {} 81 for (package, version) in all_ebuild_versions(): 82 if package in versions: 83 versions[package].append(version) 84 versions[package].sort(key=StrictVersion) 85 else: 86 versions[package] = [version] 87 return versions 88 89 90def update_manifest(package: str, version: str): 91 """Regenerate ebuild manifest for the provided package/version.""" 92 cmd = ["ebuild", ebuild_file_path(package, version), "manifest"] 93 print(" ", " ".join(cmd)) 94 subprocess.run(cmd) 95 96 97def uprev_ebuild(package: str, new_version: str, old_version: str): 98 """Updates the ebuild file from `old_version` to `new_version`.""" 99 old_path = ebuild_file_path(package, old_version) 100 new_path = ebuild_file_path(package, new_version) 101 print(f" {old_path} -> {new_path}") 102 os.rename(old_path, new_path) 103 update_manifest(package, new_version) 104 105 106def add_ebuild(package: str, version: str): 107 """Creates a new ebuild file for the provided package.""" 108 ebuild_path = ebuild_file_path(package, version) 109 print(f" Writing {ebuild_path}") 110 open(ebuild_path, "w").write(ebuild_template(package)) 111 update_manifest(package, version) 112 113 114def update_cargo(package: str, latest_version: str): 115 """Runs `cargo update` to update the version in Cargo.lock.""" 116 cmd = ["cargo", "update", "-p", package, "--precise", latest_version] 117 print(" ", " ".join(cmd)) 118 subprocess.run(cmd) 119 120 121def confirm(question: str): 122 print(f"{question} [y/N]") 123 return sys.stdin.readline().strip().lower() == "y" 124 125 126def main(): 127 ebuild_packages = ebuild_versions_dict() 128 for (package, cargo_version) in parse_cargo_lock(): 129 ebuild_versions = ebuild_packages.get(package, []) 130 if package in IGNORED_PACKAGES: 131 continue 132 if cargo_version in ebuild_versions: 133 continue 134 if not ebuild_versions: 135 print(f"{package}: No ebuild file.") 136 if confirm("Create ebuild?"): 137 add_ebuild(package, cargo_version) 138 elif StrictVersion(ebuild_versions[-1]) > StrictVersion(cargo_version): 139 print( 140 f"{package}: Cargo version {cargo_version} is older than " 141 f"latest ebuild version ({', '.join(ebuild_versions)})." 142 ) 143 if confirm("Update Cargo.lock?"): 144 update_cargo(package, ebuild_versions[-1]) 145 else: 146 print( 147 f"{package}: Ebuild versions ({', '.join(ebuild_versions)}) " 148 f"are older than cargo version {cargo_version}." 149 ) 150 if confirm("Uprev ebuild?"): 151 uprev_ebuild(package, cargo_version, ebuild_versions[-1]) 152 153 154if __name__ == "__main__": 155 main() 156