#!/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, Callable, 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 not 'errors' 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 not response_expect_key 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!')