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,92 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ...cli.cmd import RootCommand
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ...cli.completion import ArgumentParser
|
|
9
|
+
from ...config import GlobalConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VenvCommand(
|
|
13
|
+
RootCommand,
|
|
14
|
+
cmd="venv",
|
|
15
|
+
help="Generate a virtual environment adapted to the chosen toolchain and profile",
|
|
16
|
+
):
|
|
17
|
+
@classmethod
|
|
18
|
+
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
19
|
+
p.add_argument("profile", type=str, help="Profile to use for the environment")
|
|
20
|
+
p.add_argument("dest", type=str, help="Path to the new virtual environment")
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--name",
|
|
23
|
+
"-n",
|
|
24
|
+
type=str,
|
|
25
|
+
default=None,
|
|
26
|
+
help="Override the venv's name",
|
|
27
|
+
)
|
|
28
|
+
p.add_argument(
|
|
29
|
+
"--toolchain",
|
|
30
|
+
"-t",
|
|
31
|
+
type=str,
|
|
32
|
+
action="append",
|
|
33
|
+
help="Specifier(s) (atoms) of the toolchain package(s) to use",
|
|
34
|
+
)
|
|
35
|
+
p.add_argument(
|
|
36
|
+
"--emulator",
|
|
37
|
+
"-e",
|
|
38
|
+
type=str,
|
|
39
|
+
help="Specifier (atom) of the emulator package to use",
|
|
40
|
+
)
|
|
41
|
+
p.add_argument(
|
|
42
|
+
"--with-sysroot",
|
|
43
|
+
action="store_true",
|
|
44
|
+
dest="with_sysroot",
|
|
45
|
+
default=True,
|
|
46
|
+
help="Provision a fresh sysroot inside the new virtual environment (default)",
|
|
47
|
+
)
|
|
48
|
+
p.add_argument(
|
|
49
|
+
"--without-sysroot",
|
|
50
|
+
action="store_false",
|
|
51
|
+
dest="with_sysroot",
|
|
52
|
+
help="Do not include a sysroot inside the new virtual environment",
|
|
53
|
+
)
|
|
54
|
+
p.add_argument(
|
|
55
|
+
"--sysroot-from",
|
|
56
|
+
type=str,
|
|
57
|
+
help="Specifier (atom) of the sysroot package to use, in favor of the toolchain-included one if applicable",
|
|
58
|
+
)
|
|
59
|
+
p.add_argument(
|
|
60
|
+
"--extra-commands-from",
|
|
61
|
+
type=str,
|
|
62
|
+
action="append",
|
|
63
|
+
help="Specifier(s) (atoms) of extra package(s) to add commands to the new virtual environment",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def main(cls, cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
68
|
+
from ...ruyipkg.host import get_native_host
|
|
69
|
+
from .maker import do_make_venv
|
|
70
|
+
|
|
71
|
+
profile_name: str = args.profile
|
|
72
|
+
dest = pathlib.Path(args.dest)
|
|
73
|
+
with_sysroot: bool = args.with_sysroot
|
|
74
|
+
override_name: str | None = args.name
|
|
75
|
+
tc_atoms_str: list[str] | None = args.toolchain
|
|
76
|
+
emu_atom_str: str | None = args.emulator
|
|
77
|
+
sysroot_atom_str: str | None = args.sysroot_from
|
|
78
|
+
extra_cmd_atoms_str: list[str] | None = args.extra_commands_from
|
|
79
|
+
host = str(get_native_host())
|
|
80
|
+
|
|
81
|
+
return do_make_venv(
|
|
82
|
+
cfg,
|
|
83
|
+
host,
|
|
84
|
+
profile_name,
|
|
85
|
+
dest,
|
|
86
|
+
with_sysroot,
|
|
87
|
+
override_name,
|
|
88
|
+
tc_atoms_str,
|
|
89
|
+
emu_atom_str,
|
|
90
|
+
sysroot_atom_str,
|
|
91
|
+
extra_cmd_atoms_str,
|
|
92
|
+
)
|
ruyi/mux/venv_cfg.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import os.path
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, TypedDict, TYPE_CHECKING, cast
|
|
6
|
+
|
|
7
|
+
if sys.version_info >= (3, 11):
|
|
8
|
+
import tomllib
|
|
9
|
+
else:
|
|
10
|
+
import tomli as tomllib
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from typing_extensions import NotRequired, Self
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from ..log import RuyiLogger
|
|
17
|
+
from ..utils.global_mode import ProvidesGlobalMode
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VenvConfigType(TypedDict):
|
|
21
|
+
profile: str
|
|
22
|
+
sysroot: "NotRequired[str]"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class VenvConfigRootType(TypedDict):
|
|
26
|
+
config: VenvConfigType
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VenvCacheV0Type(TypedDict):
|
|
30
|
+
target_tuple: str
|
|
31
|
+
toolchain_bindir: str
|
|
32
|
+
gcc_install_dir: "NotRequired[str]"
|
|
33
|
+
profile_common_flags: str
|
|
34
|
+
qemu_bin: "NotRequired[str]"
|
|
35
|
+
profile_emu_env: "NotRequired[dict[str, str]]"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class VenvCacheV1TargetType(TypedDict):
|
|
39
|
+
toolchain_bindir: str
|
|
40
|
+
toolchain_sysroot: "NotRequired[str]"
|
|
41
|
+
gcc_install_dir: "NotRequired[str]"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VenvCacheV2TargetType(VenvCacheV1TargetType):
|
|
45
|
+
toolchain_flags: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class VenvCacheV1CmdMetadataEntryType(TypedDict):
|
|
49
|
+
dest: str
|
|
50
|
+
target_tuple: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class VenvCacheV1Type(TypedDict):
|
|
54
|
+
profile_common_flags: str
|
|
55
|
+
profile_emu_env: "NotRequired[dict[str, str]]"
|
|
56
|
+
qemu_bin: "NotRequired[str]"
|
|
57
|
+
targets: dict[str, VenvCacheV1TargetType]
|
|
58
|
+
cmd_metadata_map: "NotRequired[dict[str, VenvCacheV1CmdMetadataEntryType]]"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class VenvCacheV2Type(TypedDict):
|
|
62
|
+
profile_emu_env: "NotRequired[dict[str, str]]"
|
|
63
|
+
qemu_bin: "NotRequired[str]"
|
|
64
|
+
targets: dict[str, VenvCacheV2TargetType]
|
|
65
|
+
cmd_metadata_map: "NotRequired[dict[str, VenvCacheV1CmdMetadataEntryType]]"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class VenvCacheRootType(TypedDict):
|
|
69
|
+
cached: "NotRequired[VenvCacheV0Type]"
|
|
70
|
+
cached_v1: "NotRequired[VenvCacheV1Type]"
|
|
71
|
+
cached_v2: "NotRequired[VenvCacheV2Type]"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def parse_venv_cache(
|
|
75
|
+
cache: VenvCacheRootType,
|
|
76
|
+
global_sysroot: str | None,
|
|
77
|
+
) -> VenvCacheV2Type:
|
|
78
|
+
if "cached_v2" in cache:
|
|
79
|
+
return cache["cached_v2"]
|
|
80
|
+
if "cached_v1" in cache:
|
|
81
|
+
return upgrade_venv_cache_v1(cache["cached_v1"])
|
|
82
|
+
if "cached" in cache:
|
|
83
|
+
return upgrade_venv_cache_v0(cache["cached"], global_sysroot)
|
|
84
|
+
raise RuntimeError("unsupported venv cache version")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def upgrade_venv_cache_v1(x: VenvCacheV1Type) -> VenvCacheV2Type:
|
|
88
|
+
profile_common_flags = x["profile_common_flags"]
|
|
89
|
+
tmp = cast(dict[str, Any], copy.deepcopy(x))
|
|
90
|
+
del tmp["profile_common_flags"]
|
|
91
|
+
v2 = cast(VenvCacheV2Type, tmp)
|
|
92
|
+
for tgt in v2["targets"].values():
|
|
93
|
+
tgt["toolchain_flags"] = profile_common_flags
|
|
94
|
+
return v2
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def upgrade_venv_cache_v0(
|
|
98
|
+
x: VenvCacheV0Type,
|
|
99
|
+
global_sysroot: str | None,
|
|
100
|
+
) -> VenvCacheV2Type:
|
|
101
|
+
# v0 only supports one single target so upgrading is trivial
|
|
102
|
+
v1_target: VenvCacheV1TargetType = {
|
|
103
|
+
"toolchain_bindir": x["toolchain_bindir"],
|
|
104
|
+
}
|
|
105
|
+
if "gcc_install_dir" in x:
|
|
106
|
+
v1_target["gcc_install_dir"] = x["gcc_install_dir"]
|
|
107
|
+
if global_sysroot is not None:
|
|
108
|
+
v1_target["toolchain_sysroot"] = global_sysroot
|
|
109
|
+
|
|
110
|
+
y: VenvCacheV1Type = {
|
|
111
|
+
"profile_common_flags": x["profile_common_flags"],
|
|
112
|
+
"targets": {x["target_tuple"]: v1_target},
|
|
113
|
+
}
|
|
114
|
+
if "profile_emu_env" in x:
|
|
115
|
+
y["profile_emu_env"] = x["profile_emu_env"]
|
|
116
|
+
if "qemu_bin" in x:
|
|
117
|
+
y["qemu_bin"] = x["qemu_bin"]
|
|
118
|
+
|
|
119
|
+
return upgrade_venv_cache_v1(y)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class RuyiVenvConfig:
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
venv_root: pathlib.Path,
|
|
126
|
+
cfg: VenvConfigRootType,
|
|
127
|
+
cache: VenvCacheRootType,
|
|
128
|
+
) -> None:
|
|
129
|
+
self.venv_root = venv_root
|
|
130
|
+
self.profile = cfg["config"]["profile"]
|
|
131
|
+
self.sysroot = cfg["config"].get("sysroot")
|
|
132
|
+
|
|
133
|
+
parsed_cache = parse_venv_cache(cache, self.sysroot)
|
|
134
|
+
self.targets = parsed_cache["targets"]
|
|
135
|
+
self.qemu_bin = parsed_cache.get("qemu_bin")
|
|
136
|
+
self.profile_emu_env = parsed_cache.get("profile_emu_env")
|
|
137
|
+
self.cmd_metadata_map = parsed_cache.get("cmd_metadata_map")
|
|
138
|
+
|
|
139
|
+
# this must be in sync with provision.py
|
|
140
|
+
self._ruyi_priv_dir = self.venv_root / "ruyi-private"
|
|
141
|
+
self._cached_cmd_targets_dir = self._ruyi_priv_dir / "cached-cmd-targets"
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def explicit_ruyi_venv_root(cls, gm: ProvidesGlobalMode) -> str | None:
|
|
145
|
+
return gm.venv_root
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def probe_venv_root(cls, gm: ProvidesGlobalMode) -> pathlib.Path | None:
|
|
149
|
+
if explicit_root := cls.explicit_ruyi_venv_root(gm):
|
|
150
|
+
return pathlib.Path(explicit_root)
|
|
151
|
+
|
|
152
|
+
# check ../.. from argv[0]
|
|
153
|
+
# this only works if it contains a path separator, otherwise it's really
|
|
154
|
+
# hard without an explicit root (/proc/*/exe points to the resolved file,
|
|
155
|
+
# but we want the path to the first symlink without any symlink dereference)
|
|
156
|
+
argv0_path = gm.argv0
|
|
157
|
+
if os.path.sep not in argv0_path:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
implied_root = pathlib.Path(os.path.dirname(os.path.dirname(argv0_path)))
|
|
161
|
+
if (implied_root / "ruyi-venv.toml").exists():
|
|
162
|
+
return implied_root
|
|
163
|
+
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def load_from_venv(
|
|
168
|
+
cls,
|
|
169
|
+
gm: ProvidesGlobalMode,
|
|
170
|
+
logger: RuyiLogger,
|
|
171
|
+
) -> "Self | None":
|
|
172
|
+
venv_root = cls.probe_venv_root(gm)
|
|
173
|
+
if venv_root is None:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
if cls.explicit_ruyi_venv_root(gm) is not None:
|
|
177
|
+
logger.D(f"using explicit venv root {venv_root}")
|
|
178
|
+
else:
|
|
179
|
+
logger.D(f"detected implicit venv root {venv_root}")
|
|
180
|
+
|
|
181
|
+
venv_config_path = venv_root / "ruyi-venv.toml"
|
|
182
|
+
with open(venv_config_path, "rb") as fp:
|
|
183
|
+
cfg: Any = tomllib.load(fp) # in order to cast to our stricter type
|
|
184
|
+
|
|
185
|
+
cache: Any # in order to cast to our stricter type
|
|
186
|
+
venv_cache_v2_path = venv_root / "ruyi-cache.v2.toml"
|
|
187
|
+
try:
|
|
188
|
+
with open(venv_cache_v2_path, "rb") as fp:
|
|
189
|
+
cache = tomllib.load(fp)
|
|
190
|
+
except FileNotFoundError:
|
|
191
|
+
venv_cache_v1_path = venv_root / "ruyi-cache.v1.toml"
|
|
192
|
+
try:
|
|
193
|
+
with open(venv_cache_v1_path, "rb") as fp:
|
|
194
|
+
cache = tomllib.load(fp)
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
venv_cache_v0_path = venv_root / "ruyi-cache.toml"
|
|
197
|
+
with open(venv_cache_v0_path, "rb") as fp:
|
|
198
|
+
cache = tomllib.load(fp)
|
|
199
|
+
|
|
200
|
+
# NOTE: for now it's not prohibited to have cache data of a different
|
|
201
|
+
# version in a certain version's cache path, but this situation is
|
|
202
|
+
# harmless
|
|
203
|
+
return cls(venv_root, cfg, cache)
|
|
204
|
+
|
|
205
|
+
def resolve_cmd_metadata_with_cache(
|
|
206
|
+
self,
|
|
207
|
+
basename: str,
|
|
208
|
+
) -> VenvCacheV1CmdMetadataEntryType | None:
|
|
209
|
+
if self.cmd_metadata_map is None:
|
|
210
|
+
# we are operating in a venv created with an older ruyi, thus no
|
|
211
|
+
# cmd_metadata_map in cache
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
return self.cmd_metadata_map.get(basename)
|
|
File without changes
|
ruyi/pluginhost/api.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from contextlib import AbstractContextManager
|
|
2
|
+
import pathlib
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 11):
|
|
9
|
+
import tomllib
|
|
10
|
+
else:
|
|
11
|
+
import tomli as tomllib
|
|
12
|
+
|
|
13
|
+
from rich.console import Console, RenderableType
|
|
14
|
+
|
|
15
|
+
from ..cli import user_input
|
|
16
|
+
from ..log import RuyiLogger
|
|
17
|
+
from ..version import RUYI_SEMVER
|
|
18
|
+
from .paths import resolve_ruyi_load_path
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .ctx import PluginHostContext, SupportsEvalFunction, SupportsGetOption
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
U = TypeVar("U")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RuyiHostAPI:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
phctx: "PluginHostContext[SupportsGetOption, SupportsEvalFunction]",
|
|
31
|
+
this_file: pathlib.Path,
|
|
32
|
+
this_plugin_dir: pathlib.Path,
|
|
33
|
+
allow_host_fs_access: bool,
|
|
34
|
+
) -> None:
|
|
35
|
+
self._phctx = phctx
|
|
36
|
+
self._this_file = this_file
|
|
37
|
+
self._this_plugin_dir = this_plugin_dir
|
|
38
|
+
self._ev = phctx.make_evaluator()
|
|
39
|
+
self._allow_host_fs_access = allow_host_fs_access
|
|
40
|
+
|
|
41
|
+
self._logger = RuyiPluginLogger(self._phctx.host_logger)
|
|
42
|
+
# TODO: unify into the plugin logger
|
|
43
|
+
self._host_logger = self._phctx.host_logger
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def ruyi_version(self) -> str:
|
|
47
|
+
return str(RUYI_SEMVER)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def ruyi_plugin_api_rev(self) -> int:
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
def load_toml(self, path: str) -> object:
|
|
54
|
+
resolved_path = resolve_ruyi_load_path(
|
|
55
|
+
path,
|
|
56
|
+
self._phctx.plugin_root,
|
|
57
|
+
True,
|
|
58
|
+
self._this_file,
|
|
59
|
+
self._allow_host_fs_access,
|
|
60
|
+
)
|
|
61
|
+
with open(resolved_path, "rb") as f:
|
|
62
|
+
return tomllib.load(f)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def log(self) -> "RuyiPluginLogger":
|
|
66
|
+
return self._logger
|
|
67
|
+
|
|
68
|
+
def cli_ask_for_choice(self, prompt: str, choice_texts: list[str]) -> int:
|
|
69
|
+
return user_input.ask_for_choice(self._host_logger, prompt, choice_texts)
|
|
70
|
+
|
|
71
|
+
def cli_ask_for_file(self, prompt: str) -> str:
|
|
72
|
+
return user_input.ask_for_file(self._host_logger, prompt)
|
|
73
|
+
|
|
74
|
+
def cli_ask_for_kv_choice(self, prompt: str, choices_kv: dict[str, str]) -> str:
|
|
75
|
+
return user_input.ask_for_kv_choice(self._host_logger, prompt, choices_kv)
|
|
76
|
+
|
|
77
|
+
def cli_ask_for_yesno_confirmation(
|
|
78
|
+
self,
|
|
79
|
+
prompt: str,
|
|
80
|
+
default: bool = False,
|
|
81
|
+
) -> bool:
|
|
82
|
+
return user_input.ask_for_yesno_confirmation(self._host_logger, prompt, default)
|
|
83
|
+
|
|
84
|
+
def call_subprocess_argv(
|
|
85
|
+
self,
|
|
86
|
+
argv: list[str],
|
|
87
|
+
) -> int:
|
|
88
|
+
# TODO: restrictions on this
|
|
89
|
+
return subprocess.call(argv)
|
|
90
|
+
|
|
91
|
+
def sleep(self, seconds: float, /) -> None:
|
|
92
|
+
return time.sleep(seconds)
|
|
93
|
+
|
|
94
|
+
def with_(
|
|
95
|
+
self,
|
|
96
|
+
cm: AbstractContextManager[T],
|
|
97
|
+
fn: object | Callable[[T], U],
|
|
98
|
+
) -> U:
|
|
99
|
+
with cm as obj:
|
|
100
|
+
return cast(U, self._ev.eval_function(fn, obj))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _ensure_str(message: RenderableType) -> None:
|
|
104
|
+
if not isinstance(message, str):
|
|
105
|
+
raise TypeError("message must be str in plugins")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RuyiPluginLogger(RuyiLogger):
|
|
109
|
+
def __init__(self, host_logger: RuyiLogger) -> None:
|
|
110
|
+
self._h = host_logger
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def log_console(self) -> Console:
|
|
114
|
+
return self._h.log_console
|
|
115
|
+
|
|
116
|
+
def stdout(
|
|
117
|
+
self,
|
|
118
|
+
message: RenderableType,
|
|
119
|
+
*objects: Any,
|
|
120
|
+
sep: str = " ",
|
|
121
|
+
end: str = "\n",
|
|
122
|
+
) -> None:
|
|
123
|
+
_ensure_str(message)
|
|
124
|
+
self._h.stdout(message, *objects, sep=sep, end=end)
|
|
125
|
+
|
|
126
|
+
def D(
|
|
127
|
+
self,
|
|
128
|
+
message: RenderableType,
|
|
129
|
+
*objects: Any,
|
|
130
|
+
sep: str = " ",
|
|
131
|
+
end: str = "\n",
|
|
132
|
+
_stack_offset_delta: int = 0,
|
|
133
|
+
) -> None:
|
|
134
|
+
_ensure_str(message)
|
|
135
|
+
if _stack_offset_delta != 0:
|
|
136
|
+
raise ValueError("_stack_offset_delta is not supported in plugins")
|
|
137
|
+
self._h.D(message, *objects, sep=sep, end=end, _stack_offset_delta=1)
|
|
138
|
+
|
|
139
|
+
def W(
|
|
140
|
+
self,
|
|
141
|
+
message: RenderableType,
|
|
142
|
+
*objects: Any,
|
|
143
|
+
sep: str = " ",
|
|
144
|
+
end: str = "\n",
|
|
145
|
+
) -> None:
|
|
146
|
+
_ensure_str(message)
|
|
147
|
+
self._h.W(message, *objects, sep=sep, end=end)
|
|
148
|
+
|
|
149
|
+
def I( # noqa: E743 # the name intentionally mimics Android logging for brevity
|
|
150
|
+
self,
|
|
151
|
+
message: RenderableType,
|
|
152
|
+
*objects: Any,
|
|
153
|
+
sep: str = " ",
|
|
154
|
+
end: str = "\n",
|
|
155
|
+
) -> None:
|
|
156
|
+
_ensure_str(message)
|
|
157
|
+
self._h.I(message, *objects, sep=sep, end=end)
|
|
158
|
+
|
|
159
|
+
def F(
|
|
160
|
+
self,
|
|
161
|
+
message: RenderableType,
|
|
162
|
+
*objects: Any,
|
|
163
|
+
sep: str = " ",
|
|
164
|
+
end: str = "\n",
|
|
165
|
+
) -> None:
|
|
166
|
+
_ensure_str(message)
|
|
167
|
+
self._h.F(message, *objects, sep=sep, end=end)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _ruyi_plugin_rev(
|
|
171
|
+
phctx: "PluginHostContext[SupportsGetOption, SupportsEvalFunction]",
|
|
172
|
+
this_file: pathlib.Path,
|
|
173
|
+
this_plugin_dir: pathlib.Path,
|
|
174
|
+
allow_host_fs_access: bool,
|
|
175
|
+
rev: object,
|
|
176
|
+
) -> RuyiHostAPI:
|
|
177
|
+
if not isinstance(rev, int):
|
|
178
|
+
raise TypeError("rev must be int in ruyi_plugin_rev calls")
|
|
179
|
+
if rev != 1:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Ruyi plugin API revision {rev} is not supported by this Ruyi"
|
|
182
|
+
)
|
|
183
|
+
return RuyiHostAPI(
|
|
184
|
+
phctx,
|
|
185
|
+
this_file,
|
|
186
|
+
this_plugin_dir,
|
|
187
|
+
allow_host_fs_access,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def make_ruyi_plugin_api_for_module(
|
|
192
|
+
phctx: "PluginHostContext[SupportsGetOption, SupportsEvalFunction]",
|
|
193
|
+
this_file: pathlib.Path,
|
|
194
|
+
this_plugin_dir: pathlib.Path,
|
|
195
|
+
is_cmd: bool,
|
|
196
|
+
) -> Callable[[object], RuyiHostAPI]:
|
|
197
|
+
# Only allow access to host FS when we're being loaded as a command plugin
|
|
198
|
+
allow_host_fs_access = is_cmd
|
|
199
|
+
|
|
200
|
+
return lambda rev: _ruyi_plugin_rev(
|
|
201
|
+
phctx,
|
|
202
|
+
this_file,
|
|
203
|
+
this_plugin_dir,
|
|
204
|
+
allow_host_fs_access,
|
|
205
|
+
rev,
|
|
206
|
+
)
|