blob: a2dd881a6ef454ced99166df55081ad856004bd9 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
#
# Copyright 2024 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Checks how out of date our third-party rust deps are
import json
import os
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List, Tuple
from urllib import error, request
from rust import PREBUILT_THIRD_PARTY_DIR, ROOT_PATH
manifest = ROOT_PATH / "third_party/rust_crates/Cargo.toml"
cargo_binary = PREBUILT_THIRD_PARTY_DIR / "cargo"
cache_dir = (
Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
/ "fuchsia-rust-3p-outdated"
)
cache_dir.mkdir(exist_ok=True)
url = "https://crates.io/api/v1/crates/{}"
headers = {}
opener = request.build_opener()
opener.addheaders = [("user-agent", "fuchsia-rust-3p-outdated")]
request.install_opener(opener)
def fetch_crate_versions(crate: str) -> List[Tuple[str, datetime]]:
cache_path = cache_dir / crate
if not cache_path.exists():
try:
print("Info: fetching", crate, "release data from crates.io")
request.urlretrieve(url.format(crate), cache_dir / crate)
except error.HTTPError:
print("Warning:", crate, "was not found on crates.io")
return []
with open(cache_path) as f:
data = json.load(f)
return [
(v["num"], datetime.fromisoformat(v["created_at"]))
for v in data["versions"]
]
if __name__ == "__main__":
cargo_process = subprocess.run(
["cargo", "metadata", "--manifest-path", manifest],
text=True,
capture_output=True,
)
metadata = json.loads(cargo_process.stdout)
crates = {p["name"]: p["version"] for p in metadata["packages"]}
updates = []
for crate, version in list(crates.items()):
versions = fetch_crate_versions(crate)
try:
versions = versions[
: next((i for i, (v, _) in enumerate(versions) if v == version))
+ 1
]
except StopIteration:
continue
current = versions[-1][1]
newest = versions[0][1]
updates.append(
(newest - current, len(versions), crate, version, versions[0])
)
updates.sort()
for age, releases, crate, version, newest in updates:
print(crate, version, "is", end=" ")
if version != newest[0]:
print(age.days, "days out of date")
print(f"\t{releases} newer versions")
print(
f"\tmost recent release was {newest[0]} on {newest[1].date()}"
)
else:
print("up to date")
print()
print("median outdatedness:", updates[len(updates) // 2][0].days, "days")
print(
f"average outdatedness: {sum(u[0].days for u in updates) // len(updates)} days"
)
print("most out of date:", updates[-1][0].days, "days")
releases_behind = sorted(u[1] for u in updates)
print()
print("median releases behind:", releases_behind[len(releases_behind) // 2])
print(
f"average releases behind {sum(releases_behind) / len(releases_behind):.1f}"
)
print("most releases behind:", releases_behind[-1])