summaryrefslogtreecommitdiff
path: root/maint/cargo-check-publishable
blob: fe7275919ff95c0bba157b27f735d9e6d08d3c3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/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!")