1#!/usr/bin/env nix-shell
2#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.beautifulsoup4 ps.lxml ])"
3import json
4import os
5import pathlib
6import subprocess
7import sys
8import urllib.request
9from dataclasses import dataclass
10from enum import Enum
11
12from bs4 import BeautifulSoup, NavigableString, Tag
13
14HERE = pathlib.Path(__file__).parent
15ROOT = HERE.parent.parent.parent.parent
16VERSIONS_FILE = HERE / "kernels-org.json"
17
18
19class KernelNature(Enum):
20 MAINLINE = 1
21 STABLE = 2
22 LONGTERM = 3
23
24
25@dataclass
26class KernelRelease:
27 nature: KernelNature
28 version: str
29 branch: str
30 date: str
31 link: str
32 eol: bool = False
33
34
35def parse_release(release: Tag) -> KernelRelease | None:
36 columns: list[Tag] = list(release.find_all("td"))
37 try:
38 nature = KernelNature[columns[0].get_text().rstrip(":").upper()]
39 except KeyError:
40 return None
41
42 version = columns[1].get_text().rstrip(" [EOL]")
43 date = columns[2].get_text()
44 link = columns[3].find("a")
45 if link is not None and isinstance(link, Tag):
46 link = link.attrs.get("href")
47 assert link is not None, f"link for kernel {version} is non-existent"
48 eol = bool(release.find(class_="eolkernel"))
49
50 return KernelRelease(
51 nature=nature,
52 branch=get_branch(version),
53 version=version,
54 date=date,
55 link=link,
56 eol=eol,
57 )
58
59
60def get_branch(version: str):
61 # This is a testing kernel.
62 if "rc" in version:
63 return "testing"
64 else:
65 major, minor, *_ = version.split(".")
66 return f"{major}.{minor}"
67
68
69def get_hash(kernel: KernelRelease):
70 if kernel.branch == "testing":
71 args = ["--unpack"]
72 else:
73 args = []
74
75 hash = (
76 subprocess.check_output(["nix-prefetch-url", kernel.link] + args)
77 .decode()
78 .strip()
79 )
80 return f"sha256:{hash}"
81
82
83def commit(message):
84 return subprocess.check_call(["git", "commit", "-m", message, VERSIONS_FILE])
85
86
87def main():
88 kernel_org = urllib.request.urlopen("https://kernel.org/")
89 soup = BeautifulSoup(kernel_org.read().decode(), "lxml")
90 release_table = soup.find(id="releases")
91 if not release_table or isinstance(release_table, NavigableString):
92 print(release_table, file=sys.stderr)
93 print("Failed to find the release table on https://kernel.org", file=sys.stderr)
94 sys.exit(1)
95
96 releases = release_table.find_all("tr")
97 parsed_releases = filter(None, [parse_release(release) for release in releases])
98 all_kernels = json.load(VERSIONS_FILE.open())
99
100 for kernel in parsed_releases:
101 branch = get_branch(kernel.version)
102 nixpkgs_branch = branch.replace(".", "_")
103
104 old_version = all_kernels.get(branch, {}).get("version")
105 if old_version == kernel.version:
106 print(f"linux_{nixpkgs_branch}: {kernel.version} is latest, skipping...")
107 continue
108
109 if old_version is None:
110 message = f"linux_{nixpkgs_branch}: init at {kernel.version}"
111 else:
112 message = f"linux_{nixpkgs_branch}: {old_version} -> {kernel.version}"
113
114 print(message, file=sys.stderr)
115
116 all_kernels[branch] = {
117 "version": kernel.version,
118 "hash": get_hash(kernel),
119 }
120
121 with VERSIONS_FILE.open("w") as fd:
122 json.dump(all_kernels, fd, indent=4)
123 fd.write("\n") # makes editorconfig happy
124
125 if os.environ.get("COMMIT") == "1":
126 commit(message)
127
128
129if __name__ == "__main__":
130 main()