1#!/usr/bin/env nix-shell
2#!nix-shell -i python3 -p "python3.withPackages(ps: [ ps.beautifulsoup4 ps.click ps.httpx ps.jinja2 ps.packaging ps.pyyaml ])" nix-update
3import base64
4import binascii
5import json
6import pathlib
7import subprocess
8from urllib.parse import urljoin, urlparse
9
10import bs4
11import click
12import httpx
13import jinja2
14import packaging.version as v
15
16import utils
17
18
19LEAF_TEMPLATE = jinja2.Template('''
20{ mkKdeDerivation }:
21mkKdeDerivation {
22 pname = "{{ pname }}";
23}
24'''.strip())
25
26ROOT_TEMPLATE = jinja2.Template('''
27{ callPackage }:
28{
29 {%- for p in packages %}
30 {{ p }} = callPackage ./{{ p }} { };
31 {%- endfor %}
32}
33'''.strip())
34
35PROJECTS_WITH_RUST = {
36 "akonadi-search",
37 "angelfish",
38 "kdepim-addons",
39}
40
41def to_sri(hash):
42 raw = binascii.unhexlify(hash)
43 b64 = base64.b64encode(raw).decode()
44 return f"sha256-{b64}"
45
46
47@click.command
48@click.argument(
49 "pkgset",
50 type=click.Choice(["frameworks", "gear", "plasma"]),
51 required=True
52)
53@click.argument(
54 "version",
55 type=str,
56 required=True
57)
58@click.option(
59 "--nixpkgs",
60 type=click.Path(
61 exists=True,
62 file_okay=False,
63 resolve_path=True,
64 writable=True,
65 path_type=pathlib.Path,
66 ),
67 default=pathlib.Path(__file__).parent.parent.parent.parent
68)
69@click.option(
70 "--sources-url",
71 type=str,
72 default=None,
73)
74def main(pkgset: str, version: str, nixpkgs: pathlib.Path, sources_url: str | None):
75 root_dir = nixpkgs / "pkgs/kde"
76 set_dir = root_dir / pkgset
77 generated_dir = root_dir / "generated"
78 metadata = utils.KDERepoMetadata.from_json(generated_dir)
79
80 if sources_url is None:
81 set_url = {
82 "frameworks": f"frameworks/{version}/",
83 "gear": f"release-service/{version}/src/",
84 "plasma": f"plasma/{version}/",
85 }[pkgset]
86 sources_url = f"https://download.kde.org/stable/{set_url}"
87
88 client = httpx.Client()
89 sources = client.get(sources_url)
90 sources.raise_for_status()
91 bs = bs4.BeautifulSoup(sources.text, features="html.parser")
92
93 results = {}
94 projects_to_update_rust = set()
95 for item in bs.select("tr")[3:]:
96 link = item.select_one("td:nth-child(2) a")
97 if not link:
98 continue
99
100 project_name, version_and_ext = link.text.rsplit("-", maxsplit=1)
101
102 if project_name not in metadata.projects_by_name:
103 print(f"Warning: unknown tarball: {project_name}")
104
105 if project_name in PROJECTS_WITH_RUST:
106 projects_to_update_rust.add(project_name)
107
108 if version_and_ext.endswith(".sig"):
109 continue
110
111 version = version_and_ext.removesuffix(".tar.xz")
112
113 url = urljoin(sources_url, link.attrs["href"])
114
115 hash = client.get(url + ".sha256").text.split(" ", maxsplit=1)[0]
116 assert hash
117
118 if existing := results.get(project_name):
119 old_version = existing["version"]
120 if v.parse(old_version) > v.parse(version):
121 print(f"{project_name} {old_version} is newer than {version}, skipping...")
122 continue
123
124 results[project_name] = {
125 "version": version,
126 "url": "mirror://kde" + urlparse(url).path,
127 "hash": to_sri(hash)
128 }
129
130 pkg_dir = set_dir / project_name
131 pkg_file = pkg_dir / "default.nix"
132
133 if not pkg_file.exists():
134 print(f"Generated new package: {pkgset}/{project_name}")
135 pkg_dir.mkdir(parents=True, exist_ok=True)
136 with pkg_file.open("w") as fd:
137 fd.write(LEAF_TEMPLATE.render(pname=project_name) + "\n")
138
139 set_dir.mkdir(parents=True, exist_ok=True)
140 with (set_dir / "default.nix").open("w") as fd:
141 fd.write(ROOT_TEMPLATE.render(packages=sorted(results.keys())) + "\n")
142
143 sources_dir = generated_dir / "sources"
144 sources_dir.mkdir(parents=True, exist_ok=True)
145 with (sources_dir / f"{pkgset}.json").open("w") as fd:
146 json.dump(results, fd, indent=2)
147
148 for project_name in projects_to_update_rust:
149 print(f"Updating cargoDeps hash for {pkgset}/{project_name}...")
150 subprocess.run([
151 "nix-update",
152 f"kdePackages.{project_name}",
153 "--version",
154 "skip",
155 "--override-filename",
156 pkg_file
157 ])
158
159
160if __name__ == "__main__":
161 main() # type: ignore