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,91 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
RE_TARBALL: Final = re.compile(r"\.tar(?:\.gz|\.bz2|\.lz4|\.xz|\.zst)?$")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 11):
|
|
10
|
+
|
|
11
|
+
class UnpackMethod(enum.StrEnum):
|
|
12
|
+
UNKNOWN = ""
|
|
13
|
+
AUTO = "auto"
|
|
14
|
+
TAR_AUTO = "tar.auto"
|
|
15
|
+
|
|
16
|
+
RAW = "raw"
|
|
17
|
+
GZ = "gz"
|
|
18
|
+
BZ2 = "bz2"
|
|
19
|
+
LZ4 = "lz4"
|
|
20
|
+
XZ = "xz"
|
|
21
|
+
ZST = "zst"
|
|
22
|
+
|
|
23
|
+
TAR = "tar"
|
|
24
|
+
TAR_GZ = "tar.gz"
|
|
25
|
+
TAR_BZ2 = "tar.bz2"
|
|
26
|
+
TAR_LZ4 = "tar.lz4"
|
|
27
|
+
TAR_XZ = "tar.xz"
|
|
28
|
+
TAR_ZST = "tar.zst"
|
|
29
|
+
|
|
30
|
+
ZIP = "zip"
|
|
31
|
+
DEB = "deb"
|
|
32
|
+
|
|
33
|
+
else:
|
|
34
|
+
|
|
35
|
+
class UnpackMethod(str, enum.Enum):
|
|
36
|
+
UNKNOWN = ""
|
|
37
|
+
AUTO = "auto"
|
|
38
|
+
TAR_AUTO = "tar.auto"
|
|
39
|
+
|
|
40
|
+
RAW = "raw"
|
|
41
|
+
GZ = "gz"
|
|
42
|
+
BZ2 = "bz2"
|
|
43
|
+
LZ4 = "lz4"
|
|
44
|
+
XZ = "xz"
|
|
45
|
+
ZST = "zst"
|
|
46
|
+
|
|
47
|
+
TAR = "tar"
|
|
48
|
+
TAR_GZ = "tar.gz"
|
|
49
|
+
TAR_BZ2 = "tar.bz2"
|
|
50
|
+
TAR_LZ4 = "tar.lz4"
|
|
51
|
+
TAR_XZ = "tar.xz"
|
|
52
|
+
TAR_ZST = "tar.zst"
|
|
53
|
+
|
|
54
|
+
ZIP = "zip"
|
|
55
|
+
DEB = "deb"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UnrecognizedPackFormatError(Exception):
|
|
59
|
+
def __init__(self, filename: str) -> None:
|
|
60
|
+
self.filename = filename
|
|
61
|
+
|
|
62
|
+
def __str__(self) -> str:
|
|
63
|
+
return f"don't know how to unpack file {self.filename}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def determine_unpack_method(
|
|
67
|
+
filename: str,
|
|
68
|
+
) -> UnpackMethod:
|
|
69
|
+
filename_lower = filename.lower()
|
|
70
|
+
if m := RE_TARBALL.search(filename_lower):
|
|
71
|
+
return UnpackMethod(m.group(0)[1:])
|
|
72
|
+
if filename_lower.endswith(".deb"):
|
|
73
|
+
return UnpackMethod.DEB
|
|
74
|
+
if filename_lower.endswith(".zip"):
|
|
75
|
+
return UnpackMethod.ZIP
|
|
76
|
+
if filename_lower.endswith(".gz"):
|
|
77
|
+
# bare gzip file
|
|
78
|
+
return UnpackMethod.GZ
|
|
79
|
+
if filename_lower.endswith(".bz2"):
|
|
80
|
+
# bare bzip2 file
|
|
81
|
+
return UnpackMethod.BZ2
|
|
82
|
+
if filename_lower.endswith(".lz4"):
|
|
83
|
+
# bare lz4 file
|
|
84
|
+
return UnpackMethod.LZ4
|
|
85
|
+
if filename_lower.endswith(".xz"):
|
|
86
|
+
# bare xz file
|
|
87
|
+
return UnpackMethod.XZ
|
|
88
|
+
if filename_lower.endswith(".zst"):
|
|
89
|
+
# bare zstd file
|
|
90
|
+
return UnpackMethod.ZST
|
|
91
|
+
return UnpackMethod.UNKNOWN
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from ..cli.cmd import RootCommand
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..cli.completion import ArgumentParser
|
|
8
|
+
from ..config import GlobalConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UpdateCommand(
|
|
12
|
+
RootCommand,
|
|
13
|
+
cmd="update",
|
|
14
|
+
help="Update RuyiSDK repo and packages",
|
|
15
|
+
):
|
|
16
|
+
@classmethod
|
|
17
|
+
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def main(cls, cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
22
|
+
from . import news
|
|
23
|
+
from .state import BoundInstallationStateStore
|
|
24
|
+
|
|
25
|
+
logger = cfg.logger
|
|
26
|
+
mr = cfg.repo
|
|
27
|
+
mr.sync()
|
|
28
|
+
|
|
29
|
+
# check for upgradable packages
|
|
30
|
+
bis = BoundInstallationStateStore(cfg.ruyipkg_global_state, mr)
|
|
31
|
+
upgradable = list(bis.iter_upgradable_pkgs(cfg.include_prereleases))
|
|
32
|
+
|
|
33
|
+
if upgradable:
|
|
34
|
+
logger.stdout(
|
|
35
|
+
"\nNewer versions are available for some of your installed packages:\n"
|
|
36
|
+
)
|
|
37
|
+
for pm, new_ver in upgradable:
|
|
38
|
+
logger.stdout(
|
|
39
|
+
f" - [bold]{pm.category}/{pm.name}[/]: [yellow]{pm.ver}[/] -> [green]{new_ver}[/]"
|
|
40
|
+
)
|
|
41
|
+
logger.stdout(
|
|
42
|
+
"""
|
|
43
|
+
Re-run [yellow]ruyi install[/] to upgrade, and don't forget to re-create any affected
|
|
44
|
+
virtual environments."""
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# check if there are new newsitems
|
|
48
|
+
unread_newsitems = mr.news_store().list(True)
|
|
49
|
+
if unread_newsitems:
|
|
50
|
+
logger.stdout(f"\nThere are {len(unread_newsitems)} new news item(s):\n")
|
|
51
|
+
news.print_news_item_titles(logger, unread_newsitems, cfg.lang_code)
|
|
52
|
+
logger.stdout("\nYou can read them with [yellow]ruyi news read[/].")
|
|
53
|
+
|
|
54
|
+
return 0
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Iterable, TypeAlias, TypedDict, TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from typing_extensions import NotRequired
|
|
5
|
+
|
|
6
|
+
from .event import TelemetryEvent
|
|
7
|
+
from .node_info import NodeInfo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AggregatedTelemetryEvent(TypedDict):
|
|
11
|
+
time_bucket: str
|
|
12
|
+
kind: str
|
|
13
|
+
params: list[tuple[str, str]]
|
|
14
|
+
count: int
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UploadPayload(TypedDict):
|
|
18
|
+
fmt: int
|
|
19
|
+
nonce: str
|
|
20
|
+
ruyi_version: str
|
|
21
|
+
installation: "NotRequired[NodeInfo | None]"
|
|
22
|
+
events: list[AggregatedTelemetryEvent]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def stringify_param_val(v: object) -> str:
|
|
26
|
+
if v is None:
|
|
27
|
+
return "null"
|
|
28
|
+
if isinstance(v, bool):
|
|
29
|
+
return "1" if v else "0"
|
|
30
|
+
if isinstance(v, bytes):
|
|
31
|
+
return v.decode("utf-8")
|
|
32
|
+
if isinstance(v, str):
|
|
33
|
+
return v
|
|
34
|
+
return str(v)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
AggregateKey: TypeAlias = tuple[tuple[str, str], ...]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _make_aggregate_key(ev: TelemetryEvent) -> AggregateKey:
|
|
41
|
+
param_list = [(k, stringify_param_val(v)) for k, v in ev["params"].items()]
|
|
42
|
+
param_list.sort()
|
|
43
|
+
return tuple([("", ev["kind"])] + param_list)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def aggregate_events(
|
|
47
|
+
events: Iterable[TelemetryEvent],
|
|
48
|
+
) -> Iterable[AggregatedTelemetryEvent]:
|
|
49
|
+
# dict[time_bucket, dict[AggregateKey, count]]
|
|
50
|
+
buf: dict[str, dict[AggregateKey, int]] = {}
|
|
51
|
+
for raw_ev in events:
|
|
52
|
+
time_bucket = raw_ev.get("time_bucket")
|
|
53
|
+
if time_bucket is None:
|
|
54
|
+
continue
|
|
55
|
+
if time_bucket not in buf:
|
|
56
|
+
buf[time_bucket] = {}
|
|
57
|
+
|
|
58
|
+
agg_key = _make_aggregate_key(raw_ev)
|
|
59
|
+
if agg_key not in buf[time_bucket]:
|
|
60
|
+
buf[time_bucket][agg_key] = 1
|
|
61
|
+
else:
|
|
62
|
+
buf[time_bucket][agg_key] += 1
|
|
63
|
+
|
|
64
|
+
for time_bucket in sorted(buf.keys()):
|
|
65
|
+
bucket_events = buf[time_bucket]
|
|
66
|
+
for agg_key in sorted(bucket_events.keys()):
|
|
67
|
+
yield {
|
|
68
|
+
"time_bucket": time_bucket,
|
|
69
|
+
"kind": agg_key[0][1],
|
|
70
|
+
"params": list(agg_key[1:]),
|
|
71
|
+
"count": bucket_events[agg_key],
|
|
72
|
+
}
|
ruyi/telemetry/event.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import TypedDict, TypeGuard, TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from typing_extensions import NotRequired
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TelemetryEvent(TypedDict):
|
|
8
|
+
fmt: int
|
|
9
|
+
time_bucket: "NotRequired[str]" # canonically "YYYYMMDDHHMM"
|
|
10
|
+
kind: str
|
|
11
|
+
params: dict[str, object]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_telemetry_event(x: object) -> TypeGuard[TelemetryEvent]:
|
|
15
|
+
if not isinstance(x, dict):
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
if not 3 <= len(x.keys()) <= 4:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
if not isinstance(x["fmt"], int):
|
|
23
|
+
return False
|
|
24
|
+
if not isinstance(x["kind"], str):
|
|
25
|
+
return False
|
|
26
|
+
if not isinstance(x["params"], dict):
|
|
27
|
+
return False
|
|
28
|
+
except KeyError:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if not isinstance(x["time_bucket"], str):
|
|
33
|
+
return False
|
|
34
|
+
if len(x["time_bucket"]) != 12:
|
|
35
|
+
return False
|
|
36
|
+
if not x["time_bucket"].isdigit():
|
|
37
|
+
return False
|
|
38
|
+
except KeyError:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
return True
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Final, Mapping, TypedDict, TYPE_CHECKING
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing_extensions import NotRequired
|
|
12
|
+
|
|
13
|
+
from ..utils.ci import probe_for_ci
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NodeInfo(TypedDict):
|
|
17
|
+
v: int
|
|
18
|
+
report_uuid: str
|
|
19
|
+
|
|
20
|
+
arch: str
|
|
21
|
+
ci: str
|
|
22
|
+
libc_name: str
|
|
23
|
+
libc_ver: str
|
|
24
|
+
os: str
|
|
25
|
+
os_release_id: str
|
|
26
|
+
os_release_version_id: str
|
|
27
|
+
shell: str
|
|
28
|
+
|
|
29
|
+
riscv_machine: "NotRequired[RISCVMachineInfo]"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RISCVMachineInfo(TypedDict):
|
|
33
|
+
model_name: str
|
|
34
|
+
cpu_count: int
|
|
35
|
+
isa: str
|
|
36
|
+
uarch: str
|
|
37
|
+
uarch_csr: str
|
|
38
|
+
mmu: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def probe_for_libc() -> tuple[str, str]:
|
|
42
|
+
r = platform.libc_ver()
|
|
43
|
+
if r[0] and r[1]:
|
|
44
|
+
return r
|
|
45
|
+
|
|
46
|
+
# check for musl ld.so at the upstream standard paths, because
|
|
47
|
+
# platform.libc_ver() as of Python 3.12 does not know how to handle musl
|
|
48
|
+
#
|
|
49
|
+
# see https://wiki.musl-libc.org/guidelines-for-distributions
|
|
50
|
+
musl_lds = glob.glob("/lib/ld-musl-*.so.1")
|
|
51
|
+
if musl_lds:
|
|
52
|
+
# run it and check for "Version *.*.*"
|
|
53
|
+
# in case of multiple hits (hybrid-architecture sysroot?), hope the
|
|
54
|
+
# first one that successfully returns something is the native one
|
|
55
|
+
for p in musl_lds:
|
|
56
|
+
if ver := _try_get_musl_ver(p):
|
|
57
|
+
return ("musl", ver)
|
|
58
|
+
|
|
59
|
+
return ("unknown", "unknown")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_MUSL_VERSION_RE: Final = re.compile(rb"(?m)^Version ([0-9.]+)$")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _try_get_musl_ver(ldso_path: str) -> str | None:
|
|
66
|
+
res = subprocess.run([ldso_path], stderr=subprocess.PIPE)
|
|
67
|
+
if m := _MUSL_VERSION_RE.search(res.stderr):
|
|
68
|
+
return m.group(1).decode("ascii", "ignore")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _try_parse_hex(v: str) -> int | None:
|
|
73
|
+
if not v.startswith("0x"):
|
|
74
|
+
return None
|
|
75
|
+
try:
|
|
76
|
+
return int(v[2:], 16)
|
|
77
|
+
except ValueError:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def probe_for_riscv_machine_info(
|
|
82
|
+
model_name: str | None = None,
|
|
83
|
+
cpuinfo_data: str | None = None,
|
|
84
|
+
) -> RISCVMachineInfo | None:
|
|
85
|
+
if model_name is None:
|
|
86
|
+
try:
|
|
87
|
+
with open(
|
|
88
|
+
"/sys/firmware/devicetree/base/model",
|
|
89
|
+
"r",
|
|
90
|
+
encoding="utf-8",
|
|
91
|
+
) as fp:
|
|
92
|
+
model_name = fp.read().strip(" \n\t\x00")
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
if not model_name:
|
|
97
|
+
model_name = "unknown"
|
|
98
|
+
|
|
99
|
+
if cpuinfo_data is None:
|
|
100
|
+
try:
|
|
101
|
+
with open("/proc/cpuinfo", "r", encoding="utf-8") as fp:
|
|
102
|
+
cpuinfo_data = fp.read()
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
cpu_count = 0
|
|
107
|
+
isa, mmu, uarch = "unknown", "unknown", "unknown"
|
|
108
|
+
mvendorid: int | None = None
|
|
109
|
+
marchid: int | None = None
|
|
110
|
+
mimpid: int | None = None
|
|
111
|
+
if cpuinfo_data is not None:
|
|
112
|
+
for line in cpuinfo_data.split("\n"):
|
|
113
|
+
if not line:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
k, v = line.split(": ", 1)
|
|
118
|
+
except ValueError:
|
|
119
|
+
# malformed line: non-empty but no ": "
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
k = k.strip(" \t")
|
|
123
|
+
v = v.strip()
|
|
124
|
+
|
|
125
|
+
match k:
|
|
126
|
+
case "processor":
|
|
127
|
+
cpu_count += 1
|
|
128
|
+
case "isa":
|
|
129
|
+
isa = v
|
|
130
|
+
case "mmu":
|
|
131
|
+
mmu = v
|
|
132
|
+
case "uarch":
|
|
133
|
+
uarch = v
|
|
134
|
+
case "mvendorid":
|
|
135
|
+
mvendorid = _try_parse_hex(v)
|
|
136
|
+
case "marchid":
|
|
137
|
+
marchid = _try_parse_hex(v)
|
|
138
|
+
case "mimpid":
|
|
139
|
+
mimpid = _try_parse_hex(v)
|
|
140
|
+
case _:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
if mvendorid is not None and marchid is not None and mimpid is not None:
|
|
144
|
+
uarch_csr = f"{mvendorid:x}:{marchid:x}:{mimpid:x}"
|
|
145
|
+
else:
|
|
146
|
+
uarch_csr = "unknown"
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"model_name": model_name,
|
|
150
|
+
"cpu_count": cpu_count,
|
|
151
|
+
"isa": isa,
|
|
152
|
+
"mmu": mmu,
|
|
153
|
+
"uarch": uarch,
|
|
154
|
+
"uarch_csr": uarch_csr,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def probe_for_shell(os_environ: Mapping[str, str]) -> str:
|
|
159
|
+
if x := os_environ.get("SHELL"):
|
|
160
|
+
return os.path.basename(x)
|
|
161
|
+
return "unknown"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def gather_node_info(report_uuid: uuid.UUID | None = None) -> NodeInfo:
|
|
165
|
+
arch = platform.machine()
|
|
166
|
+
libc = probe_for_libc()
|
|
167
|
+
os_release = platform.freedesktop_os_release()
|
|
168
|
+
|
|
169
|
+
os_version = os_release.get("VERSION_CODENAME") # works on e.g. Debian
|
|
170
|
+
if not os_version:
|
|
171
|
+
os_version = os_release.get("VERSION_ID") # works on e.g. openEuler, Gentoo
|
|
172
|
+
if not os_version:
|
|
173
|
+
os_version = "unknown"
|
|
174
|
+
|
|
175
|
+
data: NodeInfo = {
|
|
176
|
+
"v": 1,
|
|
177
|
+
"report_uuid": report_uuid.hex if report_uuid is not None else uuid.uuid4().hex,
|
|
178
|
+
"arch": arch,
|
|
179
|
+
"ci": probe_for_ci(os.environ) or "maybe-not",
|
|
180
|
+
"libc_name": libc[0],
|
|
181
|
+
"libc_ver": libc[1],
|
|
182
|
+
"os": sys.platform,
|
|
183
|
+
"os_release_id": os_release.get("ID", "unknown"),
|
|
184
|
+
"os_release_version_id": os_version,
|
|
185
|
+
"shell": probe_for_shell(os.environ),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if arch.startswith("riscv"):
|
|
189
|
+
if riscv_machine := probe_for_riscv_machine_info():
|
|
190
|
+
data["riscv_machine"] = riscv_machine
|
|
191
|
+
|
|
192
|
+
return data
|