• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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