ruyi 0.39.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ruyi/__init__.py +21 -0
- ruyi/__main__.py +98 -0
- ruyi/cli/__init__.py +5 -0
- ruyi/cli/builtin_commands.py +14 -0
- ruyi/cli/cmd.py +224 -0
- ruyi/cli/completer.py +50 -0
- ruyi/cli/completion.py +26 -0
- ruyi/cli/config_cli.py +153 -0
- ruyi/cli/main.py +111 -0
- ruyi/cli/self_cli.py +295 -0
- ruyi/cli/user_input.py +127 -0
- ruyi/cli/version_cli.py +45 -0
- ruyi/config/__init__.py +401 -0
- ruyi/config/editor.py +92 -0
- ruyi/config/errors.py +76 -0
- ruyi/config/news.py +39 -0
- ruyi/config/schema.py +197 -0
- ruyi/device/__init__.py +0 -0
- ruyi/device/provision.py +591 -0
- ruyi/device/provision_cli.py +40 -0
- ruyi/log/__init__.py +272 -0
- ruyi/mux/.gitignore +1 -0
- ruyi/mux/__init__.py +0 -0
- ruyi/mux/runtime.py +213 -0
- ruyi/mux/venv/__init__.py +12 -0
- ruyi/mux/venv/emulator_cfg.py +41 -0
- ruyi/mux/venv/maker.py +782 -0
- ruyi/mux/venv/venv_cli.py +92 -0
- ruyi/mux/venv_cfg.py +214 -0
- ruyi/pluginhost/__init__.py +0 -0
- ruyi/pluginhost/api.py +206 -0
- ruyi/pluginhost/ctx.py +222 -0
- ruyi/pluginhost/paths.py +135 -0
- ruyi/pluginhost/plugin_cli.py +37 -0
- ruyi/pluginhost/unsandboxed.py +246 -0
- ruyi/py.typed +0 -0
- ruyi/resource_bundle/__init__.py +20 -0
- ruyi/resource_bundle/__main__.py +55 -0
- ruyi/resource_bundle/data.py +26 -0
- ruyi/ruyipkg/__init__.py +0 -0
- ruyi/ruyipkg/admin_checksum.py +88 -0
- ruyi/ruyipkg/admin_cli.py +83 -0
- ruyi/ruyipkg/atom.py +184 -0
- ruyi/ruyipkg/augmented_pkg.py +212 -0
- ruyi/ruyipkg/canonical_dump.py +320 -0
- ruyi/ruyipkg/checksum.py +39 -0
- ruyi/ruyipkg/cli_completion.py +42 -0
- ruyi/ruyipkg/distfile.py +208 -0
- ruyi/ruyipkg/entity.py +387 -0
- ruyi/ruyipkg/entity_cli.py +123 -0
- ruyi/ruyipkg/entity_provider.py +273 -0
- ruyi/ruyipkg/fetch.py +271 -0
- ruyi/ruyipkg/host.py +55 -0
- ruyi/ruyipkg/install.py +554 -0
- ruyi/ruyipkg/install_cli.py +150 -0
- ruyi/ruyipkg/list.py +126 -0
- ruyi/ruyipkg/list_cli.py +79 -0
- ruyi/ruyipkg/list_filter.py +173 -0
- ruyi/ruyipkg/msg.py +99 -0
- ruyi/ruyipkg/news.py +123 -0
- ruyi/ruyipkg/news_cli.py +78 -0
- ruyi/ruyipkg/news_store.py +183 -0
- ruyi/ruyipkg/pkg_manifest.py +657 -0
- ruyi/ruyipkg/profile.py +208 -0
- ruyi/ruyipkg/profile_cli.py +33 -0
- ruyi/ruyipkg/protocols.py +55 -0
- ruyi/ruyipkg/repo.py +763 -0
- ruyi/ruyipkg/state.py +345 -0
- ruyi/ruyipkg/unpack.py +369 -0
- ruyi/ruyipkg/unpack_method.py +91 -0
- ruyi/ruyipkg/update_cli.py +54 -0
- ruyi/telemetry/__init__.py +0 -0
- ruyi/telemetry/aggregate.py +72 -0
- ruyi/telemetry/event.py +41 -0
- ruyi/telemetry/node_info.py +192 -0
- ruyi/telemetry/provider.py +411 -0
- ruyi/telemetry/scope.py +43 -0
- ruyi/telemetry/store.py +238 -0
- ruyi/telemetry/telemetry_cli.py +127 -0
- ruyi/utils/__init__.py +0 -0
- ruyi/utils/ar.py +74 -0
- ruyi/utils/ci.py +63 -0
- ruyi/utils/frontmatter.py +38 -0
- ruyi/utils/git.py +169 -0
- ruyi/utils/global_mode.py +204 -0
- ruyi/utils/l10n.py +83 -0
- ruyi/utils/markdown.py +73 -0
- ruyi/utils/nuitka.py +33 -0
- ruyi/utils/porcelain.py +51 -0
- ruyi/utils/prereqs.py +77 -0
- ruyi/utils/ssl_patch.py +170 -0
- ruyi/utils/templating.py +34 -0
- ruyi/utils/toml.py +115 -0
- ruyi/utils/url.py +7 -0
- ruyi/utils/xdg_basedir.py +80 -0
- ruyi/version.py +67 -0
- ruyi-0.39.0.dist-info/LICENSE-Apache.txt +201 -0
- ruyi-0.39.0.dist-info/METADATA +403 -0
- ruyi-0.39.0.dist-info/RECORD +101 -0
- ruyi-0.39.0.dist-info/WHEEL +4 -0
- ruyi-0.39.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import re
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
BinaryIO,
|
|
9
|
+
Final,
|
|
10
|
+
Iterable,
|
|
11
|
+
Iterator,
|
|
12
|
+
Literal,
|
|
13
|
+
TypedDict,
|
|
14
|
+
TYPE_CHECKING,
|
|
15
|
+
cast,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing_extensions import NotRequired, Self
|
|
20
|
+
|
|
21
|
+
# pyright only works with semver 3.x
|
|
22
|
+
from semver.version import Version
|
|
23
|
+
else:
|
|
24
|
+
try:
|
|
25
|
+
from semver.version import Version # type: ignore[import-untyped,unused-ignore]
|
|
26
|
+
except ModuleNotFoundError:
|
|
27
|
+
# semver 2.x
|
|
28
|
+
from semver import VersionInfo as Version # type: ignore[import-untyped,unused-ignore]
|
|
29
|
+
|
|
30
|
+
import tomlkit
|
|
31
|
+
|
|
32
|
+
from .host import RuyiHost, canonicalize_host_str, get_native_host
|
|
33
|
+
from .msg import RepoMessageStore
|
|
34
|
+
from .unpack_method import UnpackMethod, determine_unpack_method
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
# avoid circular import at runtime
|
|
38
|
+
from .repo import MetadataRepo
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VendorDeclType(TypedDict):
|
|
42
|
+
name: str
|
|
43
|
+
eula: str | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
RestrictKind = Literal["fetch"] | Literal["mirror"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FetchRestrictionDeclType(TypedDict):
|
|
50
|
+
msgid: str
|
|
51
|
+
params: "NotRequired[dict[str, str]]"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DistfileDeclType(TypedDict):
|
|
55
|
+
name: str
|
|
56
|
+
urls: "NotRequired[list[str]]"
|
|
57
|
+
restrict: "NotRequired[list[RestrictKind]]"
|
|
58
|
+
size: int
|
|
59
|
+
checksums: dict[str, str]
|
|
60
|
+
strip_components: "NotRequired[int]"
|
|
61
|
+
unpack: "NotRequired[UnpackMethod]"
|
|
62
|
+
fetch_restriction: "NotRequired[FetchRestrictionDeclType]"
|
|
63
|
+
prefixes_to_unpack: "NotRequired[list[str]]"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class BinaryFileDeclType(TypedDict):
|
|
67
|
+
host: str
|
|
68
|
+
distfiles: list[str]
|
|
69
|
+
commands: "NotRequired[dict[str, str]]"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
BinaryDeclType = list[BinaryFileDeclType]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class BlobDeclType(TypedDict):
|
|
76
|
+
distfiles: list[str]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SourceDeclType(TypedDict):
|
|
80
|
+
distfiles: list[str]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ToolchainComponentDeclType(TypedDict):
|
|
84
|
+
name: str
|
|
85
|
+
version: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ToolchainDeclType(TypedDict):
|
|
89
|
+
target: str
|
|
90
|
+
quirks: "NotRequired[list[str]]"
|
|
91
|
+
flavors: "NotRequired[list[str]]" # Backward compatibility alias
|
|
92
|
+
components: list[ToolchainComponentDeclType]
|
|
93
|
+
included_sysroot: "NotRequired[str]"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
EmulatorFlavor = Literal["qemu-linux-user"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class EmulatorProgramDeclType(TypedDict):
|
|
100
|
+
path: str
|
|
101
|
+
flavor: EmulatorFlavor
|
|
102
|
+
supported_arches: list[str]
|
|
103
|
+
binfmt_misc: "NotRequired[str]"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class EmulatorDeclType(TypedDict):
|
|
107
|
+
quirks: "NotRequired[list[str]]"
|
|
108
|
+
flavors: "NotRequired[list[str]]" # Backward compatibility alias
|
|
109
|
+
programs: list[EmulatorProgramDeclType]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
PartitionKind = (
|
|
113
|
+
Literal["boot"]
|
|
114
|
+
| Literal["disk"]
|
|
115
|
+
| Literal["live"]
|
|
116
|
+
| Literal["root"]
|
|
117
|
+
| Literal["uboot"]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# error: "<typing special form>" has no attribute "__args__"
|
|
121
|
+
# KNOWN_PARTITION_KINDS = frozenset(kind.__args__[0] for kind in PartitionKind.__args__)
|
|
122
|
+
KNOWN_PARTITION_KINDS: Final = frozenset(("boot", "disk", "live", "root", "uboot"))
|
|
123
|
+
|
|
124
|
+
PartitionMapDecl = dict[PartitionKind, str]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ProvisionableDeclType(TypedDict):
|
|
128
|
+
partition_map: PartitionMapDecl
|
|
129
|
+
strategy: str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
PackageKind = (
|
|
133
|
+
Literal["binary"]
|
|
134
|
+
| Literal["blob"]
|
|
135
|
+
| Literal["source"]
|
|
136
|
+
| Literal["toolchain"]
|
|
137
|
+
| Literal["emulator"]
|
|
138
|
+
| Literal["provisionable"]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
ALL_PACKAGE_KINDS: Final[list[PackageKind]] = [
|
|
142
|
+
"binary",
|
|
143
|
+
"blob",
|
|
144
|
+
"source",
|
|
145
|
+
"toolchain",
|
|
146
|
+
"emulator",
|
|
147
|
+
"provisionable",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
RuyiPkgFormat = Literal["v1"]
|
|
151
|
+
|
|
152
|
+
ServiceLevelKind = Literal["known_issue"] | Literal["untested"]
|
|
153
|
+
|
|
154
|
+
ALL_SERVICE_LEVEL_KINDS: Final[list[ServiceLevelKind]] = ["known_issue", "untested"]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ServiceLevelDeclType(TypedDict):
|
|
158
|
+
level: ServiceLevelKind
|
|
159
|
+
msgid: "NotRequired[str]"
|
|
160
|
+
params: "NotRequired[dict[str, str]]"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class PackageMetadataDeclType(TypedDict):
|
|
164
|
+
slug: "NotRequired[str]" # deprecated for v1+
|
|
165
|
+
desc: str
|
|
166
|
+
doc_uri: "NotRequired[str]"
|
|
167
|
+
vendor: VendorDeclType
|
|
168
|
+
service_level: "NotRequired[list[ServiceLevelDeclType]]"
|
|
169
|
+
upstream_version: "NotRequired[str]"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class InputPackageManifestType(TypedDict):
|
|
173
|
+
format: "NotRequired[RuyiPkgFormat]"
|
|
174
|
+
|
|
175
|
+
# v0 fields
|
|
176
|
+
slug: "NotRequired[str]"
|
|
177
|
+
kind: "NotRequired[list[PackageKind]]" # mandatory in v0
|
|
178
|
+
desc: "NotRequired[str]" # mandatory in v0
|
|
179
|
+
doc_uri: "NotRequired[str]"
|
|
180
|
+
vendor: "NotRequired[VendorDeclType]" # mandatory in v0
|
|
181
|
+
|
|
182
|
+
# v1+ fields
|
|
183
|
+
metadata: "NotRequired[PackageMetadataDeclType]"
|
|
184
|
+
|
|
185
|
+
# common fields
|
|
186
|
+
distfiles: list[DistfileDeclType]
|
|
187
|
+
binary: "NotRequired[BinaryDeclType]"
|
|
188
|
+
blob: "NotRequired[BlobDeclType]"
|
|
189
|
+
source: "NotRequired[SourceDeclType]"
|
|
190
|
+
toolchain: "NotRequired[ToolchainDeclType]"
|
|
191
|
+
emulator: "NotRequired[EmulatorDeclType]"
|
|
192
|
+
provisionable: "NotRequired[ProvisionableDeclType]"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class PackageManifestType(TypedDict):
|
|
196
|
+
format: RuyiPkgFormat
|
|
197
|
+
kind: list[PackageKind]
|
|
198
|
+
metadata: PackageMetadataDeclType
|
|
199
|
+
distfiles: list[DistfileDeclType]
|
|
200
|
+
binary: "NotRequired[BinaryDeclType]"
|
|
201
|
+
blob: "NotRequired[BlobDeclType]"
|
|
202
|
+
source: "NotRequired[SourceDeclType]"
|
|
203
|
+
toolchain: "NotRequired[ToolchainDeclType]"
|
|
204
|
+
emulator: "NotRequired[EmulatorDeclType]"
|
|
205
|
+
provisionable: "NotRequired[ProvisionableDeclType]"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DistfileDecl:
|
|
209
|
+
def __init__(self, data: DistfileDeclType) -> None:
|
|
210
|
+
self._data = data
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def name(self) -> str:
|
|
214
|
+
return self._data["name"]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def urls(self) -> list[str] | None:
|
|
218
|
+
return self._data.get("urls")
|
|
219
|
+
|
|
220
|
+
def is_restricted(self, kind: RestrictKind) -> bool:
|
|
221
|
+
if restricts := self._data.get("restrict"):
|
|
222
|
+
# account for a common oversight in some existing manifests where
|
|
223
|
+
# the field was specified as a string instead of a list, in case the
|
|
224
|
+
# user has not yet synced their repo
|
|
225
|
+
if isinstance(restricts, str):
|
|
226
|
+
return kind == restricts
|
|
227
|
+
return kind in restricts
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def size(self) -> int:
|
|
232
|
+
return self._data["size"]
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def checksums(self) -> dict[str, str]:
|
|
236
|
+
return self._data["checksums"]
|
|
237
|
+
|
|
238
|
+
def get_checksum(self, kind: str) -> str | None:
|
|
239
|
+
return self._data["checksums"].get(kind)
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def prefixes_to_unpack(self) -> list[str] | None:
|
|
243
|
+
return self._data.get("prefixes_to_unpack")
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def strip_components(self) -> int:
|
|
247
|
+
return self._data.get("strip_components", 1)
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def unpack_method(self) -> UnpackMethod:
|
|
251
|
+
x = self._data.get("unpack", UnpackMethod.AUTO)
|
|
252
|
+
if x == UnpackMethod.AUTO:
|
|
253
|
+
return determine_unpack_method(self.name)
|
|
254
|
+
return x
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def fetch_restriction(self) -> FetchRestrictionDeclType | None:
|
|
258
|
+
return self._data.get("fetch_restriction")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class BinaryDecl:
|
|
262
|
+
def __init__(self, data: BinaryDeclType) -> None:
|
|
263
|
+
self._data = {canonicalize_host_str(d["host"]): d for d in data}
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def data(self) -> dict[str, BinaryFileDeclType]:
|
|
267
|
+
return self._data
|
|
268
|
+
|
|
269
|
+
def get_distfile_names_for_host(self, host: str | RuyiHost) -> list[str] | None:
|
|
270
|
+
if data := self._data.get(canonicalize_host_str(host)):
|
|
271
|
+
return data.get("distfiles")
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def is_available_for_current_host(self) -> bool:
|
|
276
|
+
return str(get_native_host()) in self._data
|
|
277
|
+
|
|
278
|
+
def get_commands_for_host(self, host: str) -> dict[str, str]:
|
|
279
|
+
if data := self._data.get(canonicalize_host_str(host)):
|
|
280
|
+
return data.get("commands", {})
|
|
281
|
+
return {}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class BlobDecl:
|
|
285
|
+
def __init__(self, data: BlobDeclType) -> None:
|
|
286
|
+
self._data = data
|
|
287
|
+
|
|
288
|
+
def get_distfile_names(self) -> list[str] | None:
|
|
289
|
+
return self._data["distfiles"]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class SourceDecl:
|
|
293
|
+
def __init__(self, data: SourceDeclType) -> None:
|
|
294
|
+
self._data = data
|
|
295
|
+
|
|
296
|
+
def get_distfile_names_for_host(self, host: str | RuyiHost) -> list[str] | None:
|
|
297
|
+
# currently the host parameter is ignored
|
|
298
|
+
return self._data["distfiles"]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class ToolchainDecl:
|
|
302
|
+
def __init__(self, data: ToolchainDeclType) -> None:
|
|
303
|
+
self._data = data
|
|
304
|
+
self._component_vers_cache: dict[str, str] | None = None
|
|
305
|
+
|
|
306
|
+
# rename "flavors" to "quirks" for compatibility with old data
|
|
307
|
+
if "quirks" not in self._data and "flavors" in self._data:
|
|
308
|
+
self._data["quirks"] = self._data["flavors"]
|
|
309
|
+
del self._data["flavors"]
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def _component_vers(self) -> dict[str, str]:
|
|
313
|
+
if self._component_vers_cache is None:
|
|
314
|
+
self._component_vers_cache = {
|
|
315
|
+
x["name"]: x["version"] for x in self.components
|
|
316
|
+
}
|
|
317
|
+
return self._component_vers_cache
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def target(self) -> str:
|
|
321
|
+
return self._data["target"]
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def target_arch(self) -> str:
|
|
325
|
+
# TODO: switch to proper mapping later; for now this suffices
|
|
326
|
+
return self.target.split("-", 1)[0]
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def quirks(self) -> list[str]:
|
|
330
|
+
return self._data.get("quirks", [])
|
|
331
|
+
|
|
332
|
+
def has_quirk(self, q: str) -> bool:
|
|
333
|
+
return q in self.quirks
|
|
334
|
+
|
|
335
|
+
def satisfies_quirk_set(self, req: set[str]) -> bool:
|
|
336
|
+
# req - my_quirks must be the empty set so that my_quirks >= req
|
|
337
|
+
return len(req.difference(self.quirks)) == 0
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def components(self) -> Iterable[ToolchainComponentDeclType]:
|
|
341
|
+
return self._data["components"]
|
|
342
|
+
|
|
343
|
+
def get_component_version(self, name: str) -> str | None:
|
|
344
|
+
return self._component_vers.get(name)
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def has_binutils(self) -> bool:
|
|
348
|
+
return self.get_component_version("binutils") is not None
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def has_clang(self) -> bool:
|
|
352
|
+
return self.get_component_version("clang") is not None
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def has_gcc(self) -> bool:
|
|
356
|
+
return self.get_component_version("gcc") is not None
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def has_llvm(self) -> bool:
|
|
360
|
+
return self.get_component_version("llvm") is not None
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def included_sysroot(self) -> str | None:
|
|
364
|
+
return self._data.get("included_sysroot")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class EmulatorProgDecl:
|
|
368
|
+
def __init__(self, data: EmulatorProgramDeclType) -> None:
|
|
369
|
+
self.relative_path = data["path"]
|
|
370
|
+
# have to explicitly annotate the type to please the type checker...
|
|
371
|
+
self.flavor: EmulatorFlavor = data["flavor"]
|
|
372
|
+
self.supported_arches = set(data["supported_arches"])
|
|
373
|
+
self.binfmt_misc = data.get("binfmt_misc")
|
|
374
|
+
|
|
375
|
+
def get_binfmt_misc_str(self, install_root: os.PathLike[Any]) -> str | None:
|
|
376
|
+
if self.binfmt_misc is None:
|
|
377
|
+
return None
|
|
378
|
+
binpath = os.path.join(install_root, self.relative_path)
|
|
379
|
+
return self.binfmt_misc.replace("$BIN", binpath)
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def is_qemu(self) -> bool:
|
|
383
|
+
return self.flavor == "qemu-linux-user"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class EmulatorDecl:
|
|
387
|
+
def __init__(self, data: EmulatorDeclType) -> None:
|
|
388
|
+
self._data = data
|
|
389
|
+
self.programs = [EmulatorProgDecl(x) for x in data["programs"]]
|
|
390
|
+
|
|
391
|
+
# rename "flavors" to "quirks" for compatibility with old data
|
|
392
|
+
if "quirks" not in self._data and "flavors" in self._data:
|
|
393
|
+
self._data["quirks"] = self._data["flavors"]
|
|
394
|
+
del self._data["flavors"]
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def quirks(self) -> list[str] | None:
|
|
398
|
+
return self._data.get("quirks")
|
|
399
|
+
|
|
400
|
+
def list_for_arch(self, arch: str) -> Iterable[EmulatorProgDecl]:
|
|
401
|
+
for p in self.programs:
|
|
402
|
+
if arch in p.supported_arches:
|
|
403
|
+
yield p
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class ProvisionableDecl:
|
|
407
|
+
def __init__(self, data: ProvisionableDeclType) -> None:
|
|
408
|
+
self._data = data
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def partition_map(self) -> PartitionMapDecl:
|
|
412
|
+
return self._data["partition_map"]
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def strategy(self) -> str:
|
|
416
|
+
return self._data["strategy"]
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class PackageMetadataDecl:
|
|
420
|
+
def __init__(self, data: PackageMetadataDeclType) -> None:
|
|
421
|
+
self._data = data
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _translate_to_manifest_v1(obj: InputPackageManifestType) -> PackageManifestType:
|
|
425
|
+
fmt = obj.get("format", "")
|
|
426
|
+
if fmt == "v1":
|
|
427
|
+
return cast(PackageManifestType, obj)
|
|
428
|
+
if fmt != "":
|
|
429
|
+
# unrecognized package format
|
|
430
|
+
raise RuntimeError(f"unrecognized Ruyi package format: {fmt}")
|
|
431
|
+
|
|
432
|
+
# translate v0 to v1
|
|
433
|
+
result = deepcopy(obj)
|
|
434
|
+
result["format"] = "v1"
|
|
435
|
+
|
|
436
|
+
md: PackageMetadataDeclType = {"desc": "", "vendor": {"name": "", "eula": None}}
|
|
437
|
+
if "slug" in result:
|
|
438
|
+
md["slug"] = result["slug"]
|
|
439
|
+
del result["slug"]
|
|
440
|
+
if "desc" in result:
|
|
441
|
+
md["desc"] = result["desc"]
|
|
442
|
+
del result["desc"]
|
|
443
|
+
if "vendor" in result:
|
|
444
|
+
md["vendor"] = result["vendor"]
|
|
445
|
+
del result["vendor"]
|
|
446
|
+
if "doc_uri" in result:
|
|
447
|
+
md["doc_uri"] = result["doc_uri"]
|
|
448
|
+
del result["doc_uri"]
|
|
449
|
+
result["metadata"] = md
|
|
450
|
+
|
|
451
|
+
return cast(PackageManifestType, result)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class PackageServiceLevel:
|
|
455
|
+
def __init__(self, data: list[ServiceLevelDeclType]) -> None:
|
|
456
|
+
self._data = data
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def level(self) -> ServiceLevelKind:
|
|
460
|
+
for r in self._data:
|
|
461
|
+
if r["level"] == "untested":
|
|
462
|
+
continue
|
|
463
|
+
return r["level"]
|
|
464
|
+
return "untested"
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def has_known_issues(self) -> bool:
|
|
468
|
+
for r in self._data:
|
|
469
|
+
if r["level"] == "known_issue":
|
|
470
|
+
return True
|
|
471
|
+
return False
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def known_issues(self) -> Iterator[ServiceLevelDeclType]:
|
|
475
|
+
for r in self._data:
|
|
476
|
+
if r["level"] == "known_issue":
|
|
477
|
+
yield r
|
|
478
|
+
|
|
479
|
+
def render_known_issues(
|
|
480
|
+
self,
|
|
481
|
+
msg_store: RepoMessageStore,
|
|
482
|
+
lang_code: str,
|
|
483
|
+
) -> Iterator[str]:
|
|
484
|
+
for x in self.known_issues:
|
|
485
|
+
if "msgid" not in x:
|
|
486
|
+
# malformed known issue declaration, but let's not panic
|
|
487
|
+
yield ""
|
|
488
|
+
continue
|
|
489
|
+
yield msg_store.render_message(x["msgid"], lang_code, x.get("params", {}))
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class PackageManifest:
|
|
493
|
+
def __init__(
|
|
494
|
+
self,
|
|
495
|
+
doc: tomlkit.TOMLDocument | InputPackageManifestType,
|
|
496
|
+
) -> None:
|
|
497
|
+
self._raw_doc = doc if isinstance(doc, tomlkit.TOMLDocument) else None
|
|
498
|
+
self._data = _translate_to_manifest_v1(cast(InputPackageManifestType, doc))
|
|
499
|
+
if "kind" not in self._data:
|
|
500
|
+
self._data["kind"] = [k for k in ALL_PACKAGE_KINDS if k in self._data]
|
|
501
|
+
|
|
502
|
+
@classmethod
|
|
503
|
+
def load_toml(cls, stream: BinaryIO) -> "Self":
|
|
504
|
+
return cls(tomlkit.load(stream))
|
|
505
|
+
|
|
506
|
+
@classmethod
|
|
507
|
+
def load_from_path(cls, p: pathlib.Path) -> "Self":
|
|
508
|
+
suffix = p.suffix.lower()
|
|
509
|
+
match suffix:
|
|
510
|
+
case ".toml":
|
|
511
|
+
with open(p, "rb") as fp:
|
|
512
|
+
return cls.load_toml(fp)
|
|
513
|
+
case _:
|
|
514
|
+
raise RuntimeError(
|
|
515
|
+
f"unrecognized package manifest file extension: '{p.suffix}'"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
def to_raw(self) -> PackageManifestType:
|
|
519
|
+
return deepcopy(self._data)
|
|
520
|
+
|
|
521
|
+
@property
|
|
522
|
+
def raw_doc(self) -> tomlkit.TOMLDocument | None:
|
|
523
|
+
return self._raw_doc
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def slug(self) -> str | None:
|
|
527
|
+
return self._data["metadata"].get("slug")
|
|
528
|
+
|
|
529
|
+
@property
|
|
530
|
+
def kind(self) -> list[PackageKind]:
|
|
531
|
+
return self._data["kind"]
|
|
532
|
+
|
|
533
|
+
def has_kind(self, k: PackageKind) -> bool:
|
|
534
|
+
return k in self._data["kind"]
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def desc(self) -> str:
|
|
538
|
+
return self._data["metadata"]["desc"]
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def doc_uri(self) -> str | None:
|
|
542
|
+
return self._data["metadata"].get("doc_uri")
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def vendor_name(self) -> str:
|
|
546
|
+
return self._data["metadata"]["vendor"]["name"]
|
|
547
|
+
|
|
548
|
+
@property
|
|
549
|
+
def upstream_version(self) -> str | None:
|
|
550
|
+
return self._data["metadata"].get("upstream_version")
|
|
551
|
+
|
|
552
|
+
# TODO: vendor_eula
|
|
553
|
+
|
|
554
|
+
@property
|
|
555
|
+
def service_level(self) -> PackageServiceLevel:
|
|
556
|
+
return PackageServiceLevel(self._data["metadata"].get("service_level", []))
|
|
557
|
+
|
|
558
|
+
@cached_property
|
|
559
|
+
def distfiles(self) -> dict[str, DistfileDecl]:
|
|
560
|
+
return {x["name"]: DistfileDecl(x) for x in self._data["distfiles"]}
|
|
561
|
+
|
|
562
|
+
@cached_property
|
|
563
|
+
def binary_metadata(self) -> BinaryDecl | None:
|
|
564
|
+
if not self.has_kind("binary"):
|
|
565
|
+
return None
|
|
566
|
+
if "binary" not in self._data:
|
|
567
|
+
return None
|
|
568
|
+
return BinaryDecl(self._data["binary"])
|
|
569
|
+
|
|
570
|
+
@cached_property
|
|
571
|
+
def blob_metadata(self) -> BlobDecl | None:
|
|
572
|
+
if not self.has_kind("blob"):
|
|
573
|
+
return None
|
|
574
|
+
if "blob" not in self._data:
|
|
575
|
+
return None
|
|
576
|
+
return BlobDecl(self._data["blob"])
|
|
577
|
+
|
|
578
|
+
@cached_property
|
|
579
|
+
def source_metadata(self) -> SourceDecl | None:
|
|
580
|
+
if not self.has_kind("source"):
|
|
581
|
+
return None
|
|
582
|
+
if "source" not in self._data:
|
|
583
|
+
return None
|
|
584
|
+
return SourceDecl(self._data["source"])
|
|
585
|
+
|
|
586
|
+
@cached_property
|
|
587
|
+
def toolchain_metadata(self) -> ToolchainDecl | None:
|
|
588
|
+
if not self.has_kind("toolchain"):
|
|
589
|
+
return None
|
|
590
|
+
if "toolchain" not in self._data:
|
|
591
|
+
return None
|
|
592
|
+
return ToolchainDecl(self._data["toolchain"])
|
|
593
|
+
|
|
594
|
+
@cached_property
|
|
595
|
+
def emulator_metadata(self) -> EmulatorDecl | None:
|
|
596
|
+
if not self.has_kind("emulator"):
|
|
597
|
+
return None
|
|
598
|
+
if "emulator" not in self._data:
|
|
599
|
+
return None
|
|
600
|
+
return EmulatorDecl(self._data["emulator"])
|
|
601
|
+
|
|
602
|
+
@cached_property
|
|
603
|
+
def provisionable_metadata(self) -> ProvisionableDecl | None:
|
|
604
|
+
if not self.has_kind("provisionable"):
|
|
605
|
+
return None
|
|
606
|
+
if "provisionable" not in self._data:
|
|
607
|
+
return None
|
|
608
|
+
return ProvisionableDecl(self._data["provisionable"])
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class BoundPackageManifest(PackageManifest):
|
|
612
|
+
def __init__(
|
|
613
|
+
self,
|
|
614
|
+
category: str,
|
|
615
|
+
name: str,
|
|
616
|
+
ver: str,
|
|
617
|
+
data: InputPackageManifestType,
|
|
618
|
+
repo: "MetadataRepo",
|
|
619
|
+
) -> None:
|
|
620
|
+
super().__init__(data)
|
|
621
|
+
|
|
622
|
+
self.category = category
|
|
623
|
+
self.name = name
|
|
624
|
+
self.ver = ver
|
|
625
|
+
self._semver = Version.parse(ver)
|
|
626
|
+
self.repo = repo
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def repo_id(self) -> str:
|
|
630
|
+
return self.repo.repo_id
|
|
631
|
+
|
|
632
|
+
@property
|
|
633
|
+
def semver(self) -> Version:
|
|
634
|
+
return self._semver
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def is_prerelease(self) -> bool:
|
|
638
|
+
return is_prerelease(self._semver)
|
|
639
|
+
|
|
640
|
+
@property
|
|
641
|
+
def name_for_installation(self) -> str:
|
|
642
|
+
return f"{self.name}-{self.ver}"
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
PRERELEASE_TAGS_RE: Final = re.compile(r"^(?:alpha|beta|pre|rc)")
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def is_prerelease(sv: Version) -> bool:
|
|
649
|
+
if sv.prerelease is None:
|
|
650
|
+
return False
|
|
651
|
+
|
|
652
|
+
# only consider "(alpha|beta|pre|rc).*" versions as prerelease, to accommodate
|
|
653
|
+
# various semver "hacks" as incorporated by upstream(s), and ourselves
|
|
654
|
+
# ("ruyi.YYYYMMDD" are used as ordinary datestamps that affects sorting
|
|
655
|
+
# order, in contrast to build tags).
|
|
656
|
+
# see https://github.com/ruyisdk/ruyi/issues/156
|
|
657
|
+
return PRERELEASE_TAGS_RE.match(sv.prerelease) is not None
|