#!/usr/bin/env python3 # # usage: # maint/cargo-check-publishable # # Checks that every crate that is supposed to be published, # looks like it could be published. # # Currently the only check performed is that the `package.categories` # value is correct for crates.io. import os import requests import sys import time from list_crates import list_crates, Crate from typing import Any, Dict, TYPE_CHECKING problems = 0 if not TYPE_CHECKING: sys.stdout.reconfigure(line_buffering=True) api_call_memo: Dict[str, Any] = {} def crates_io_api_call(endpoint: str, response_expect_key: str) -> Any: """ Queries https://crates.io/api/$endpoint Expects to receive either HTTP 200 and a json document which is an object containing a key `response_expect_key` HTTP 404 and a json document containing a `.errors` key The fetched JSON is returned, or `None` for 404 Reimplementation in Python of the shell version in `crates-io-utils.sh` """ try: base = os.environ["CRATES_IO_URL_BASE"] except KeyError: base = "https://crates.io/api" url = base + "/" + endpoint try: json = api_call_memo[url] except KeyError: session = requests.Session() session.headers.update( {"User-Agent": "maint/ scripts for Tor Project CI (python)"} ) time.sleep(1) response = session.get(url) try: json = response.json() except Exception: print("from url %s obtained %s" % (url, repr(response)), file=sys.stderr) raise if response.status_code == 404: if "errors" not in json: raise ( RuntimeError( "404 but no `errors` URL %s: %s, %s" % (url, response, repr(json)) ) ) api_call_memo[url] = None return None if response.status_code != 200: raise ( RuntimeError( "error data from URL %s: %s, %s" % (url, response, repr(response.content)) ) ) api_call_memo[url] = json if response_expect_key not in json: raise (RuntimeError("unexpected JSON data from URL %s: %s" % (url, json))) return json def problem(msg: str) -> None: print("problem: %s" % msg, file=sys.stderr) global problems problems += 1 def prepare() -> None: pass def check_crate(crate: Crate) -> None: # if not crate.publish: # return print("checking crate %s" % crate.name) try: cats = crate.raw_metadata["package"]["categories"] except KeyError: cats = [] for spec_cat in cats: if "::" in spec_cat: (cat, subcat) = spec_cat.split("::", 1) else: cat = spec_cat subcat = None info = crates_io_api_call("v1/categories/%s" % cat, "category") if info is None: problem("category %s not known at crates.io!" % repr(cat)) continue info = info["category"] if subcat is not None: if not (any([s["slug"] == spec_cat for s in info["subcategories"]])): problem("subcategory %s not known at crates.io!" % repr(spec_cat)) prepare() for crate in list_crates(): check_crate(crate) if problems: print("%d problems!" % problems, file=sys.stderr) sys.exit(1) else: print("ok!")