k3s: format with nixfmt-rfc-style
[NixPkgs.git] / maintainers / scripts / kde / utils.py
blobb3a00093d70380abaad08a6e7096b40634b22f60
1 import collections
2 import dataclasses
3 import functools
4 import json
5 import pathlib
6 import subprocess
8 import yaml
10 class DataclassEncoder(json.JSONEncoder):
11 def default(self, it):
12 if dataclasses.is_dataclass(it):
13 return dataclasses.asdict(it)
14 return super().default(it)
17 @dataclasses.dataclass
18 class Project:
19 name: str
20 description: str | None
21 project_path: str
22 repo_path: str | None
24 def __hash__(self) -> int:
25 return hash(self.name)
27 @classmethod
28 def from_yaml(cls, path: pathlib.Path):
29 data = yaml.safe_load(path.open())
30 return cls(
31 name=data["identifier"],
32 description=data["description"],
33 project_path=data["projectpath"],
34 repo_path=data["repopath"]
38 def get_git_commit(path: pathlib.Path):
39 return subprocess.check_output(["git", "-C", path, "rev-parse", "--short", "HEAD"]).decode().strip()
42 def validate_unique(projects: list[Project], attr: str):
43 seen = set()
44 for item in projects:
45 attr_value = getattr(item, attr)
46 if attr_value in seen:
47 raise Exception(f"Duplicate {attr}: {attr_value}")
48 seen.add(attr_value)
51 THIRD_PARTY = {
52 "third-party/appstream": "appstream-qt",
53 "third-party/cmark": "cmark",
54 "third-party/gpgme": "gpgme",
55 "third-party/kdsoap": "kdsoap",
56 "third-party/libaccounts-qt": "accounts-qt",
57 "third-party/libgpg-error": "libgpg-error",
58 "third-party/libquotient": "libquotient",
59 "third-party/packagekit-qt": "packagekit-qt",
60 "third-party/poppler": "poppler",
61 "third-party/qcoro": "qcoro",
62 "third-party/qmltermwidget": "qmltermwidget",
63 "third-party/qtkeychain": "qtkeychain",
64 "third-party/signond": "signond",
65 "third-party/taglib": "taglib",
66 "third-party/wayland-protocols": "wayland-protocols",
67 "third-party/wayland": "wayland",
68 "third-party/zxing-cpp": "zxing-cpp",
71 IGNORE = {
72 "kdesupport/phonon-directshow",
73 "kdesupport/phonon-mmf",
74 "kdesupport/phonon-mplayer",
75 "kdesupport/phonon-quicktime",
76 "kdesupport/phonon-waveout",
77 "kdesupport/phonon-xine"
80 WARNED = set()
83 @dataclasses.dataclass
84 class KDERepoMetadata:
85 version: str
86 projects: list[Project]
87 dep_graph: dict[Project, set[Project]]
89 @functools.cached_property
90 def projects_by_name(self):
91 return {p.name: p for p in self.projects}
93 @functools.cached_property
94 def projects_by_path(self):
95 return {p.project_path: p for p in self.projects}
97 def try_lookup_package(self, path):
98 if path in IGNORE:
99 return None
100 project = self.projects_by_path.get(path)
101 if project is None and path not in WARNED:
102 WARNED.add(path)
103 print(f"Warning: unknown project {path}")
104 return project
106 @classmethod
107 def from_repo_metadata_checkout(cls, repo_metadata: pathlib.Path):
108 projects = [
109 Project.from_yaml(metadata_file)
110 for metadata_file in repo_metadata.glob("projects-invent/**/metadata.yaml")
111 ] + [
112 Project(id, None, project_path, None)
113 for project_path, id in THIRD_PARTY.items()
116 validate_unique(projects, "name")
117 validate_unique(projects, "project_path")
119 self = cls(
120 version=get_git_commit(repo_metadata),
121 projects=projects,
122 dep_graph={},
125 dep_specs = ["dependency-data-stable-kf6-qt6"]
126 dep_graph = collections.defaultdict(set)
128 for spec in dep_specs:
129 spec_path = repo_metadata / "dependencies" / spec
130 for line in spec_path.open():
131 line = line.strip()
132 if line.startswith("#"):
133 continue
134 if not line:
135 continue
137 dependent, dependency = line.split(": ")
139 dependent = self.try_lookup_package(dependent)
140 if dependent is None:
141 continue
143 dependency = self.try_lookup_package(dependency)
144 if dependency is None:
145 continue
147 dep_graph[dependent].add(dependency)
149 self.dep_graph = dep_graph
151 return self
153 def write_json(self, root: pathlib.Path):
154 root.mkdir(parents=True, exist_ok=True)
156 with (root / "projects.json").open("w") as fd:
157 json.dump(self.projects_by_name, fd, cls=DataclassEncoder, sort_keys=True, indent=2)
159 with (root / "dependencies.json").open("w") as fd:
160 deps = {k.name: sorted(dep.name for dep in v) for k, v in self.dep_graph.items()}
161 json.dump({"version": self.version, "dependencies": deps}, fd, cls=DataclassEncoder, sort_keys=True, indent=2)
163 @classmethod
164 def from_json(cls, root: pathlib.Path):
165 projects = [
166 Project(**v) for v in json.load((root / "projects.json").open()).values()
169 deps = json.load((root / "dependencies.json").open())
170 self = cls(
171 version=deps["version"],
172 projects=projects,
173 dep_graph={},
176 dep_graph = collections.defaultdict(set)
177 for dependent, dependencies in deps["dependencies"].items():
178 for dependency in dependencies:
179 dep_graph[self.projects_by_name[dependent]].add(self.projects_by_name[dependency])
181 self.dep_graph = dep_graph
182 return self