about summary refs log tree commit diff
path: root/maintainers/scripts/kde/utils.py
blob: 7a82c4955c6b4681c9196da26c6a66ffdf2cfa73 (plain) (blame)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import collections
import dataclasses
import functools
import json
import pathlib
import subprocess

import yaml

class DataclassEncoder(json.JSONEncoder):
    def default(self, it):
        if dataclasses.is_dataclass(it):
            return dataclasses.asdict(it)
        return super().default(it)


@dataclasses.dataclass
class Project:
    name: str
    description: str | None
    project_path: str
    repo_path: str | None

    def __hash__(self) -> int:
        return hash(self.name)

    @classmethod
    def from_yaml(cls, path: pathlib.Path):
        data = yaml.safe_load(path.open())
        return cls(
            name=data["identifier"],
            description=data["description"],
            project_path=data["projectpath"],
            repo_path=data["repopath"]
        )


def get_git_commit(path: pathlib.Path):
    return subprocess.check_output(["git", "-C", path, "rev-parse", "--short", "HEAD"]).decode().strip()


def validate_unique(projects: list[Project], attr: str):
    seen = set()
    for item in projects:
        attr_value = getattr(item, attr)
        if attr_value in seen:
            raise Exception(f"Duplicate {attr}: {attr_value}")
        seen.add(attr_value)


THIRD_PARTY = {
    "third-party/appstream": "appstream-qt",
    "third-party/cmark": "cmark",
    "third-party/gpgme": "gpgme",
    "third-party/kdsoap": "kdsoap",
    "third-party/libaccounts-qt": "accounts-qt",
    "third-party/libgpg-error": "libgpg-error",
    "third-party/libquotient": "libquotient",
    "third-party/packagekit-qt": "packagekit-qt",
    "third-party/poppler": "poppler",
    "third-party/qcoro": "qcoro",
    "third-party/qmltermwidget": "qmltermwidget",
    "third-party/qtkeychain": "qtkeychain",
    "third-party/signond": "signond",
    "third-party/taglib": "taglib",
    "third-party/wayland-protocols": "wayland-protocols",
    "third-party/wayland": "wayland",
    "third-party/zxing-cpp": "zxing-cpp",
}

IGNORE = {
    "kdesupport/phonon-directshow",
    "kdesupport/phonon-mmf",
    "kdesupport/phonon-mplayer",
    "kdesupport/phonon-quicktime",
    "kdesupport/phonon-waveout",
    "kdesupport/phonon-xine"
}

WARNED = set()


@dataclasses.dataclass
class KDERepoMetadata:
    version: str
    projects: list[Project]
    dep_graph: dict[Project, set[Project]]

    @functools.cached_property
    def projects_by_name(self):
        return {p.name: p for p in self.projects}

    @functools.cached_property
    def projects_by_path(self):
        return {p.project_path: p for p in self.projects}

    def try_lookup_package(self, path):
        if path in IGNORE:
            return None
        project = self.projects_by_path.get(path)
        if project is None and path not in WARNED:
            WARNED.add(path)
            print(f"Warning: unknown project {path}")
        return project

    @classmethod
    def from_repo_metadata_checkout(cls, repo_metadata: pathlib.Path):
        projects = [
            Project.from_yaml(metadata_file)
            for metadata_file in repo_metadata.glob("projects-invent/**/metadata.yaml")
        ] + [
            Project(id, None, project_path, None)
            for project_path, id in THIRD_PARTY.items()
        ]

        validate_unique(projects, "name")
        validate_unique(projects, "project_path")

        self = cls(
            version=get_git_commit(repo_metadata),
            projects=projects,
            dep_graph={},
        )

        dep_specs = [
            "dependency-data-common",
            "dependency-data-kf6-qt6"
        ]
        dep_graph = collections.defaultdict(set)

        for spec in dep_specs:
            spec_path = repo_metadata / "dependencies" / spec
            for line in spec_path.open():
                line = line.strip()
                if line.startswith("#"):
                    continue
                if not line:
                    continue

                dependent, dependency = line.split(": ")

                dependent = self.try_lookup_package(dependent)
                if dependent is None:
                    continue

                dependency = self.try_lookup_package(dependency)
                if dependency is None:
                    continue

                dep_graph[dependent].add(dependency)

        self.dep_graph = dep_graph

        return self

    def write_json(self, root: pathlib.Path):
        root.mkdir(parents=True, exist_ok=True)

        with (root / "projects.json").open("w") as fd:
            json.dump(self.projects_by_name, fd, cls=DataclassEncoder, sort_keys=True, indent=2)

        with (root / "dependencies.json").open("w") as fd:
            deps = {k.name: sorted(dep.name for dep in v) for k, v in self.dep_graph.items()}
            json.dump({"version": self.version, "dependencies": deps}, fd, cls=DataclassEncoder, sort_keys=True, indent=2)

    @classmethod
    def from_json(cls, root: pathlib.Path):
        projects = [
            Project(**v) for v in json.load((root / "projects.json").open()).values()
        ]

        deps = json.load((root / "dependencies.json").open())
        self = cls(
            version=deps["version"],
            projects=projects,
            dep_graph={},
        )

        dep_graph = collections.defaultdict(set)
        for dependent, dependencies in deps["dependencies"].items():
            for dependency in dependencies:
                dep_graph[self.projects_by_name[dependent]].add(self.projects_by_name[dependency])

        self.dep_graph = dep_graph
        return self