ruyi 0.44.0b20251219__py3-none-any.whl → 0.45.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/__main__.py +16 -4
- ruyi/cli/cmd.py +6 -5
- ruyi/cli/config_cli.py +14 -11
- ruyi/cli/main.py +14 -4
- ruyi/cli/oobe.py +7 -3
- ruyi/cli/self_cli.py +48 -34
- ruyi/cli/user_input.py +42 -12
- ruyi/cli/version_cli.py +11 -5
- ruyi/config/__init__.py +26 -2
- ruyi/config/errors.py +19 -7
- ruyi/device/provision.py +116 -55
- ruyi/device/provision_cli.py +6 -3
- ruyi/i18n/__init__.py +129 -0
- ruyi/log/__init__.py +6 -5
- ruyi/mux/runtime.py +19 -6
- ruyi/mux/venv/maker.py +93 -35
- ruyi/mux/venv/venv_cli.py +13 -10
- ruyi/pluginhost/plugin_cli.py +4 -3
- ruyi/resource_bundle/__init__.py +22 -8
- ruyi/resource_bundle/__main__.py +6 -5
- ruyi/resource_bundle/data.py +13 -9
- ruyi/ruyipkg/admin_checksum.py +4 -1
- ruyi/ruyipkg/admin_cli.py +9 -6
- ruyi/ruyipkg/augmented_pkg.py +15 -14
- ruyi/ruyipkg/checksum.py +8 -2
- ruyi/ruyipkg/distfile.py +33 -9
- ruyi/ruyipkg/entity.py +12 -2
- ruyi/ruyipkg/entity_cli.py +20 -12
- ruyi/ruyipkg/entity_provider.py +11 -2
- ruyi/ruyipkg/fetcher.py +38 -9
- ruyi/ruyipkg/install.py +143 -42
- ruyi/ruyipkg/install_cli.py +18 -15
- ruyi/ruyipkg/list.py +27 -20
- ruyi/ruyipkg/list_cli.py +12 -7
- ruyi/ruyipkg/news.py +23 -11
- ruyi/ruyipkg/news_cli.py +10 -7
- ruyi/ruyipkg/profile_cli.py +8 -2
- ruyi/ruyipkg/repo.py +22 -8
- ruyi/ruyipkg/unpack.py +42 -8
- ruyi/ruyipkg/unpack_method.py +5 -1
- ruyi/ruyipkg/update_cli.py +8 -3
- ruyi/telemetry/provider.py +74 -29
- ruyi/telemetry/telemetry_cli.py +9 -8
- ruyi/utils/git.py +18 -11
- ruyi/utils/prereqs.py +10 -5
- ruyi/utils/ssl_patch.py +2 -1
- ruyi/version.py +9 -3
- {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/METADATA +2 -1
- ruyi-0.45.0.dist-info/RECORD +103 -0
- {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/WHEEL +1 -1
- ruyi-0.44.0b20251219.dist-info/RECORD +0 -102
- {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/entry_points.txt +0 -0
- {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/licenses/LICENSE-Apache.txt +0 -0
ruyi/config/__init__.py
CHANGED
|
@@ -18,6 +18,13 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from ..utils.xdg_basedir import XDGPathEntry
|
|
19
19
|
from .news import NewsReadStatusStore
|
|
20
20
|
|
|
21
|
+
import babel
|
|
22
|
+
# not sure why Pyright insists on individual imports
|
|
23
|
+
# otherwise, at the use site (`except babel.core.UnknownLocaleError`):
|
|
24
|
+
# error: "core" is not a known attribute of module "babel" (reportAttributeAccessIssue)
|
|
25
|
+
from babel.core import UnknownLocaleError
|
|
26
|
+
|
|
27
|
+
from ..i18n import _
|
|
21
28
|
from . import errors
|
|
22
29
|
from . import schema
|
|
23
30
|
|
|
@@ -115,7 +122,11 @@ class GlobalConfig:
|
|
|
115
122
|
if iem is not None and not is_global_scope:
|
|
116
123
|
iem_cfg_key = f"{schema.SECTION_INSTALLATION}.{schema.KEY_INSTALLATION_EXTERNALLY_MANAGED}"
|
|
117
124
|
self.logger.W(
|
|
118
|
-
|
|
125
|
+
_(
|
|
126
|
+
"the config key [yellow]{key}[/] cannot be set from user config; ignoring"
|
|
127
|
+
).format(
|
|
128
|
+
key=iem_cfg_key,
|
|
129
|
+
),
|
|
119
130
|
)
|
|
120
131
|
else:
|
|
121
132
|
self.is_installation_externally_managed = bool(iem)
|
|
@@ -133,7 +144,11 @@ class GlobalConfig:
|
|
|
133
144
|
if self.override_repo_dir:
|
|
134
145
|
if not pathlib.Path(self.override_repo_dir).is_absolute():
|
|
135
146
|
self.logger.W(
|
|
136
|
-
|
|
147
|
+
_(
|
|
148
|
+
"the local repo path '{path}' is not absolute; ignoring"
|
|
149
|
+
).format(
|
|
150
|
+
path=self.override_repo_dir,
|
|
151
|
+
)
|
|
137
152
|
)
|
|
138
153
|
self.override_repo_dir = None
|
|
139
154
|
|
|
@@ -274,6 +289,15 @@ class GlobalConfig:
|
|
|
274
289
|
def lang_code(self) -> str:
|
|
275
290
|
return self._lang_code
|
|
276
291
|
|
|
292
|
+
@cached_property
|
|
293
|
+
def babel_locale(self) -> babel.Locale:
|
|
294
|
+
try:
|
|
295
|
+
return babel.Locale.parse(self.lang_code)
|
|
296
|
+
except UnknownLocaleError:
|
|
297
|
+
# this can happen in case of unrecognized locale names, which
|
|
298
|
+
# apparently falls back to "C"
|
|
299
|
+
return babel.Locale.parse("en_US")
|
|
300
|
+
|
|
277
301
|
@property
|
|
278
302
|
def cache_root(self) -> os.PathLike[Any]:
|
|
279
303
|
return self._dirs.app_cache
|
ruyi/config/errors.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from os import PathLike
|
|
2
2
|
from typing import Any, Sequence
|
|
3
3
|
|
|
4
|
+
from ..i18n import _
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class InvalidConfigSectionError(Exception):
|
|
6
8
|
def __init__(self, section: str) -> None:
|
|
@@ -8,7 +10,7 @@ class InvalidConfigSectionError(Exception):
|
|
|
8
10
|
self._section = section
|
|
9
11
|
|
|
10
12
|
def __str__(self) -> str:
|
|
11
|
-
return
|
|
13
|
+
return _("invalid config section: {section}").format(section=self._section)
|
|
12
14
|
|
|
13
15
|
def __repr__(self) -> str:
|
|
14
16
|
return f"InvalidConfigSectionError({self._section!r})"
|
|
@@ -20,7 +22,7 @@ class InvalidConfigKeyError(Exception):
|
|
|
20
22
|
self._key = key
|
|
21
23
|
|
|
22
24
|
def __str__(self) -> str:
|
|
23
|
-
return
|
|
25
|
+
return _("invalid config key: {key}").format(key=self._key)
|
|
24
26
|
|
|
25
27
|
def __repr__(self) -> str:
|
|
26
28
|
return f"InvalidConfigKeyError({self._key:!r})"
|
|
@@ -39,7 +41,13 @@ class InvalidConfigValueTypeError(TypeError):
|
|
|
39
41
|
self._expected = expected
|
|
40
42
|
|
|
41
43
|
def __str__(self) -> str:
|
|
42
|
-
return
|
|
44
|
+
return _(
|
|
45
|
+
"invalid value type for config key {key}: {actual_type}, expected {expected_type}"
|
|
46
|
+
).format(
|
|
47
|
+
key=self._key,
|
|
48
|
+
actual_type=type(self._val),
|
|
49
|
+
expected_type=self._expected,
|
|
50
|
+
)
|
|
43
51
|
|
|
44
52
|
def __repr__(self) -> str:
|
|
45
53
|
return f"InvalidConfigValueTypeError({self._key!r}, {self._val!r}, {self._expected:!r})"
|
|
@@ -58,8 +66,10 @@ class InvalidConfigValueError(ValueError):
|
|
|
58
66
|
self._typ = typ
|
|
59
67
|
|
|
60
68
|
def __str__(self) -> str:
|
|
61
|
-
return (
|
|
62
|
-
|
|
69
|
+
return _("invalid config value for key {key} (type {typ}): {val}").format(
|
|
70
|
+
key=self._key,
|
|
71
|
+
typ=self._typ,
|
|
72
|
+
val=self._val,
|
|
63
73
|
)
|
|
64
74
|
|
|
65
75
|
def __repr__(self) -> str:
|
|
@@ -74,7 +84,7 @@ class MalformedConfigFileError(Exception):
|
|
|
74
84
|
self._path = path
|
|
75
85
|
|
|
76
86
|
def __str__(self) -> str:
|
|
77
|
-
return
|
|
87
|
+
return _("malformed config file: {path}").format(path=self._path)
|
|
78
88
|
|
|
79
89
|
def __repr__(self) -> str:
|
|
80
90
|
return f"MalformedConfigFileError({self._path:!r})"
|
|
@@ -86,7 +96,9 @@ class ProtectedGlobalConfigError(Exception):
|
|
|
86
96
|
self._key = key
|
|
87
97
|
|
|
88
98
|
def __str__(self) -> str:
|
|
89
|
-
return
|
|
99
|
+
return _("attempt to modify protected global config key: {key}").format(
|
|
100
|
+
key=self._key,
|
|
101
|
+
)
|
|
90
102
|
|
|
91
103
|
def __repr__(self) -> str:
|
|
92
104
|
return f"ProtectedGlobalConfigError({self._key!r})"
|
ruyi/device/provision.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, TypedDict, TypeGuard, cast
|
|
|
4
4
|
|
|
5
5
|
from ..cli import user_input
|
|
6
6
|
from ..config import GlobalConfig
|
|
7
|
+
from ..i18n import _
|
|
7
8
|
from ..log import RuyiLogger
|
|
8
9
|
from ..ruyipkg.atom import Atom, ExprAtom, SlugAtom
|
|
9
10
|
from ..ruyipkg.entity_provider import BaseEntity
|
|
@@ -36,7 +37,8 @@ def do_provision_interactive(config: GlobalConfig) -> int:
|
|
|
36
37
|
mr.ensure_git_repo()
|
|
37
38
|
|
|
38
39
|
log.stdout(
|
|
39
|
-
|
|
40
|
+
_(
|
|
41
|
+
"""
|
|
40
42
|
[bold green]RuyiSDK Device Provisioning Wizard[/]
|
|
41
43
|
|
|
42
44
|
This is a wizard intended to help you install a system on your device for your
|
|
@@ -52,11 +54,12 @@ required to flash images, you should arrange to allow your user account [yellow]
|
|
|
52
54
|
access to necessary commands such as [yellow]dd[/]. Flashing will fail if the [yellow]sudo[/]
|
|
53
55
|
configuration does not allow so.
|
|
54
56
|
"""
|
|
57
|
+
)
|
|
55
58
|
)
|
|
56
59
|
|
|
57
|
-
if not user_input.ask_for_yesno_confirmation(log, "Continue?"):
|
|
60
|
+
if not user_input.ask_for_yesno_confirmation(log, _("Continue?")):
|
|
58
61
|
log.stdout(
|
|
59
|
-
"\nExiting. You can restart the wizard whenever prepared.",
|
|
62
|
+
_("\nExiting. You can restart the wizard whenever prepared."),
|
|
60
63
|
end="\n\n",
|
|
61
64
|
)
|
|
62
65
|
return 1
|
|
@@ -68,7 +71,9 @@ configuration does not allow so.
|
|
|
68
71
|
dev_choices = {k: v.display_name or "" for k, v in devices_by_id.items()}
|
|
69
72
|
dev_id = user_input.ask_for_kv_choice(
|
|
70
73
|
log,
|
|
71
|
-
|
|
74
|
+
_(
|
|
75
|
+
"\nThe following devices are currently supported by the wizard. Please pick your device:"
|
|
76
|
+
),
|
|
72
77
|
dev_choices,
|
|
73
78
|
)
|
|
74
79
|
dev = devices_by_id[dev_id]
|
|
@@ -84,7 +89,9 @@ configuration does not allow so.
|
|
|
84
89
|
variant_choices = [get_variant_display_name(dev, i) for i in variants]
|
|
85
90
|
variant_idx = user_input.ask_for_choice(
|
|
86
91
|
log,
|
|
87
|
-
|
|
92
|
+
_(
|
|
93
|
+
"\nThe device has the following variants. Please choose the one corresponding to your hardware at hand:"
|
|
94
|
+
),
|
|
88
95
|
variant_choices,
|
|
89
96
|
)
|
|
90
97
|
variant = variants[variant_idx]
|
|
@@ -101,7 +108,9 @@ configuration does not allow so.
|
|
|
101
108
|
combo_choices = [combo.display_name or "" for combo in supported_combos]
|
|
102
109
|
combo_idx = user_input.ask_for_choice(
|
|
103
110
|
log,
|
|
104
|
-
|
|
111
|
+
_(
|
|
112
|
+
"\nThe following system configurations are supported by the device variant you have chosen. Please pick the one you want to put on the device:"
|
|
113
|
+
),
|
|
105
114
|
combo_choices,
|
|
106
115
|
)
|
|
107
116
|
combo = supported_combos[combo_idx]
|
|
@@ -132,7 +141,8 @@ def do_provision_combo_interactive(
|
|
|
132
141
|
combo: BaseEntity,
|
|
133
142
|
) -> int:
|
|
134
143
|
logger = config.logger
|
|
135
|
-
|
|
144
|
+
devid = f"{dev_decl.id}@{variant_decl.id}"
|
|
145
|
+
logger.D(f"provisioning device variant '{devid}'")
|
|
136
146
|
|
|
137
147
|
# download packages
|
|
138
148
|
pkg_atoms = combo.data.get("package_atoms", [])
|
|
@@ -141,28 +151,36 @@ def do_provision_combo_interactive(
|
|
|
141
151
|
return 0
|
|
142
152
|
|
|
143
153
|
logger.F(
|
|
144
|
-
|
|
154
|
+
_(
|
|
155
|
+
"malformed config: device variant '{devid}' asks for no packages but provides no messages either"
|
|
156
|
+
).format(
|
|
157
|
+
devid=devid,
|
|
158
|
+
)
|
|
145
159
|
)
|
|
146
160
|
return 1
|
|
147
161
|
|
|
148
162
|
new_pkg_atoms = customize_package_versions(config, mr, pkg_atoms)
|
|
149
163
|
if new_pkg_atoms is None:
|
|
150
|
-
logger.stdout(
|
|
164
|
+
logger.stdout(
|
|
165
|
+
_("\nExiting. You may restart the wizard at any time."), end="\n\n"
|
|
166
|
+
)
|
|
151
167
|
return 1
|
|
152
168
|
else:
|
|
153
169
|
pkg_atoms = new_pkg_atoms
|
|
154
170
|
|
|
155
171
|
pkg_names_for_display = "\n".join(f" * [green]{i}[/]" for i in pkg_atoms)
|
|
156
172
|
logger.stdout(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"""
|
|
173
|
+
_(
|
|
174
|
+
"\nWe are about to download and install the following packages for your device:"
|
|
175
|
+
),
|
|
176
|
+
end="\n\n",
|
|
162
177
|
)
|
|
178
|
+
logger.stdout(pkg_names_for_display, end="\n\n")
|
|
163
179
|
|
|
164
|
-
if not user_input.ask_for_yesno_confirmation(logger, "Proceed?"):
|
|
165
|
-
logger.stdout(
|
|
180
|
+
if not user_input.ask_for_yesno_confirmation(logger, _("Proceed?")):
|
|
181
|
+
logger.stdout(
|
|
182
|
+
_("\nExiting. You may restart the wizard at any time."), end="\n\n"
|
|
183
|
+
)
|
|
166
184
|
return 1
|
|
167
185
|
|
|
168
186
|
ret = do_install_atoms(
|
|
@@ -174,8 +192,8 @@ We are about to download and install the following packages for your device:
|
|
|
174
192
|
reinstall=False,
|
|
175
193
|
)
|
|
176
194
|
if ret != 0:
|
|
177
|
-
logger.F("failed to download and install packages")
|
|
178
|
-
logger.I("your device was not touched")
|
|
195
|
+
logger.F(_("failed to download and install packages"))
|
|
196
|
+
logger.I(_("your device was not touched"))
|
|
179
197
|
return 2
|
|
180
198
|
|
|
181
199
|
strat_provider = ProvisionStrategyProvider(mr)
|
|
@@ -198,7 +216,8 @@ We are about to download and install the following packages for your device:
|
|
|
198
216
|
host_blkdev_map: PartitionMapDecl = {}
|
|
199
217
|
if requested_host_blkdevs:
|
|
200
218
|
logger.stdout(
|
|
201
|
-
|
|
219
|
+
_(
|
|
220
|
+
"""
|
|
202
221
|
For initializing this target device, you should plug into this host system the
|
|
203
222
|
device's storage (e.g. SD card or NVMe SSD), or a removable disk to be
|
|
204
223
|
reformatted as a live medium, and note down the corresponding device file
|
|
@@ -206,6 +225,7 @@ path(s), e.g. /dev/sdX, /dev/nvmeXnY for whole disks; /dev/sdXY, /dev/nvmeXnYpZ
|
|
|
206
225
|
for partitions. You may consult e.g. [yellow]sudo blkid[/] output for the
|
|
207
226
|
information you will need later.
|
|
208
227
|
"""
|
|
228
|
+
)
|
|
209
229
|
)
|
|
210
230
|
for part in requested_host_blkdevs:
|
|
211
231
|
part_desc = get_part_desc(part)
|
|
@@ -213,7 +233,9 @@ information you will need later.
|
|
|
213
233
|
while True:
|
|
214
234
|
path = user_input.ask_for_file(
|
|
215
235
|
logger,
|
|
216
|
-
|
|
236
|
+
_("Please give the path for the {part_desc}:").format(
|
|
237
|
+
part_desc=part_desc,
|
|
238
|
+
),
|
|
217
239
|
)
|
|
218
240
|
|
|
219
241
|
# Retrieve the latest mount info in case the user un-mounts
|
|
@@ -224,10 +246,17 @@ information you will need later.
|
|
|
224
246
|
for m in blkdev_mounts:
|
|
225
247
|
if m.source_path.samefile(path):
|
|
226
248
|
logger.W(
|
|
227
|
-
|
|
249
|
+
_(
|
|
250
|
+
"path [cyan]'{path}'[/] is currently mounted at [yellow]'{target}'[/]"
|
|
251
|
+
).format(
|
|
252
|
+
path=path,
|
|
253
|
+
target=m.target,
|
|
254
|
+
)
|
|
228
255
|
)
|
|
229
256
|
logger.I(
|
|
230
|
-
|
|
257
|
+
_(
|
|
258
|
+
"rejecting the path for safety; please double-check and retry"
|
|
259
|
+
)
|
|
231
260
|
)
|
|
232
261
|
path_valid = False
|
|
233
262
|
break
|
|
@@ -238,12 +267,14 @@ information you will need later.
|
|
|
238
267
|
|
|
239
268
|
# final confirmation
|
|
240
269
|
logger.stdout(
|
|
241
|
-
|
|
270
|
+
_(
|
|
271
|
+
"""
|
|
242
272
|
We have collected enough information for the actual flashing. Now is the last
|
|
243
273
|
chance to re-check and confirm everything is fine.
|
|
244
274
|
|
|
245
275
|
We are about to:
|
|
246
276
|
"""
|
|
277
|
+
)
|
|
247
278
|
)
|
|
248
279
|
|
|
249
280
|
pretend_steps = "\n".join(
|
|
@@ -257,9 +288,11 @@ We are about to:
|
|
|
257
288
|
)
|
|
258
289
|
logger.stdout(pretend_steps, end="\n\n")
|
|
259
290
|
|
|
260
|
-
if not user_input.ask_for_yesno_confirmation(logger, "Proceed with flashing?"):
|
|
291
|
+
if not user_input.ask_for_yesno_confirmation(logger, _("Proceed with flashing?")):
|
|
261
292
|
logger.stdout(
|
|
262
|
-
|
|
293
|
+
_(
|
|
294
|
+
"\nExiting. The device is not touched and you may re-start the wizard at will."
|
|
295
|
+
),
|
|
263
296
|
end="\n\n",
|
|
264
297
|
)
|
|
265
298
|
return 1
|
|
@@ -273,18 +306,20 @@ We are about to:
|
|
|
273
306
|
# ask the user to ensure the device shows up
|
|
274
307
|
# TODO: automate doing so
|
|
275
308
|
logger.stdout(
|
|
276
|
-
"""
|
|
309
|
+
_("""
|
|
277
310
|
Some flashing steps require the use of fastboot, in which case you should
|
|
278
311
|
ensure the target device is showing up in [yellow]fastboot devices[/] output.
|
|
279
312
|
Please [bold red]confirm it yourself before continuing[/].
|
|
280
|
-
"""
|
|
313
|
+
""")
|
|
281
314
|
)
|
|
282
315
|
if not user_input.ask_for_yesno_confirmation(
|
|
283
316
|
logger,
|
|
284
|
-
"Is the device identified by fastboot now?",
|
|
317
|
+
_("Is the device identified by fastboot now?"),
|
|
285
318
|
):
|
|
286
319
|
logger.stdout(
|
|
287
|
-
|
|
320
|
+
_(
|
|
321
|
+
"\nAborting. The device is not touched. You may re-start the wizard after [yellow]fastboot[/] is fixed for the device."
|
|
322
|
+
),
|
|
288
323
|
end="\n\n",
|
|
289
324
|
)
|
|
290
325
|
return 1
|
|
@@ -294,16 +329,16 @@ Please [bold red]confirm it yourself before continuing[/].
|
|
|
294
329
|
logger.D(f"flashing {pkg} with strategy {strat}")
|
|
295
330
|
ret = strat.flash(pkg_part_maps[pkg], host_blkdev_map)
|
|
296
331
|
if ret != 0:
|
|
297
|
-
logger.F("flashing failed, check your device right now")
|
|
332
|
+
logger.F(_("flashing failed, check your device right now"))
|
|
298
333
|
return ret
|
|
299
334
|
|
|
300
335
|
# parting words
|
|
301
336
|
logger.stdout(
|
|
302
|
-
"""
|
|
337
|
+
_("""
|
|
303
338
|
It seems the flashing has finished without errors.
|
|
304
339
|
|
|
305
340
|
[bold green]Happy hacking![/]
|
|
306
|
-
"""
|
|
341
|
+
""")
|
|
307
342
|
)
|
|
308
343
|
|
|
309
344
|
maybe_render_postinst_msg(logger, mr, combo, config.lang_code)
|
|
@@ -314,11 +349,11 @@ It seems the flashing has finished without errors.
|
|
|
314
349
|
def get_part_desc(part: PartitionKind) -> str:
|
|
315
350
|
match part:
|
|
316
351
|
case "disk":
|
|
317
|
-
return "target's whole disk"
|
|
352
|
+
return _("target's whole disk")
|
|
318
353
|
case "live":
|
|
319
|
-
return "removable disk to use as live medium"
|
|
354
|
+
return _("removable disk to use as live medium")
|
|
320
355
|
case _:
|
|
321
|
-
return
|
|
356
|
+
return _("target's '{part}' partition").format(part=part)
|
|
322
357
|
|
|
323
358
|
|
|
324
359
|
class PackageProvisionStrategyDecl(TypedDict):
|
|
@@ -489,17 +524,19 @@ def customize_package_versions(
|
|
|
489
524
|
|
|
490
525
|
# Ask if the user wants to customize package versions
|
|
491
526
|
logger.stdout(
|
|
492
|
-
|
|
527
|
+
_(
|
|
528
|
+
"By default, we'll install the latest version of each package, but in this case, other choices are possible."
|
|
529
|
+
)
|
|
493
530
|
)
|
|
494
531
|
if not user_input.ask_for_yesno_confirmation(
|
|
495
532
|
logger,
|
|
496
|
-
"Would you like to customize package versions?",
|
|
533
|
+
_("Would you like to customize package versions?"),
|
|
497
534
|
):
|
|
498
535
|
return pkg_atoms
|
|
499
536
|
|
|
500
537
|
while True: # Loop to allow restarting the selection process
|
|
501
538
|
result: list[str] = []
|
|
502
|
-
logger.stdout("\n[bold]Package Version Selection[/]")
|
|
539
|
+
logger.stdout(_("\n[bold]Package Version Selection[/]"))
|
|
503
540
|
|
|
504
541
|
for atom_str in pkg_atoms:
|
|
505
542
|
# Parse the atom to get package name
|
|
@@ -507,18 +544,26 @@ def customize_package_versions(
|
|
|
507
544
|
if isinstance(a, ExprAtom):
|
|
508
545
|
# If it's already an expression with version constraints, show the constraints
|
|
509
546
|
logger.stdout(
|
|
510
|
-
|
|
547
|
+
_(
|
|
548
|
+
"\nPackage [green]{atom}[/] already has version constraints."
|
|
549
|
+
).format(
|
|
550
|
+
atom=atom_str,
|
|
551
|
+
)
|
|
511
552
|
)
|
|
512
553
|
if not user_input.ask_for_yesno_confirmation(
|
|
513
554
|
logger,
|
|
514
|
-
"Would you like to change them?",
|
|
555
|
+
_("Would you like to change them?"),
|
|
515
556
|
):
|
|
516
557
|
result.append(atom_str)
|
|
517
558
|
continue
|
|
518
559
|
elif isinstance(a, SlugAtom):
|
|
519
560
|
# Slugs already fix the version, so we can't change them
|
|
520
561
|
logger.W(
|
|
521
|
-
|
|
562
|
+
_(
|
|
563
|
+
"version cannot be overridden for slug atom [green]{atom}[/]"
|
|
564
|
+
).format(
|
|
565
|
+
atom=atom_str,
|
|
566
|
+
)
|
|
522
567
|
)
|
|
523
568
|
result.append(atom_str)
|
|
524
569
|
continue
|
|
@@ -526,19 +571,24 @@ def customize_package_versions(
|
|
|
526
571
|
# Get all available versions for this package
|
|
527
572
|
package_name = a.name
|
|
528
573
|
category = a.category
|
|
574
|
+
pkg_fullname = f"{category}/{package_name}" if category else package_name
|
|
529
575
|
|
|
530
576
|
available_versions: "list[BoundPackageManifest]" = []
|
|
531
577
|
try:
|
|
532
578
|
available_versions = list(mr.iter_pkg_vers(package_name, category))
|
|
533
579
|
except KeyError:
|
|
534
580
|
logger.W(
|
|
535
|
-
|
|
581
|
+
_("could not find package [yellow]{pkg}[/] in repository").format(
|
|
582
|
+
pkg=pkg_fullname
|
|
583
|
+
)
|
|
536
584
|
)
|
|
537
585
|
result.append(atom_str)
|
|
538
586
|
|
|
539
587
|
if not available_versions:
|
|
540
588
|
logger.W(
|
|
541
|
-
|
|
589
|
+
_("no versions found for package [yellow]{pkg}[/]").format(
|
|
590
|
+
pkg=pkg_fullname
|
|
591
|
+
)
|
|
542
592
|
)
|
|
543
593
|
result.append(atom_str)
|
|
544
594
|
continue
|
|
@@ -547,7 +597,12 @@ def customize_package_versions(
|
|
|
547
597
|
# If there's only one version available, use it
|
|
548
598
|
selected_version = available_versions[0]
|
|
549
599
|
logger.stdout(
|
|
550
|
-
|
|
600
|
+
_(
|
|
601
|
+
"Only one version available for [green]{pkg}[/]: [blue]{ver}[/], using it."
|
|
602
|
+
).format(
|
|
603
|
+
pkg=pkg_fullname,
|
|
604
|
+
ver=selected_version.ver,
|
|
605
|
+
)
|
|
551
606
|
)
|
|
552
607
|
result.append(atom_str)
|
|
553
608
|
continue
|
|
@@ -562,11 +617,15 @@ def customize_package_versions(
|
|
|
562
617
|
remarks = []
|
|
563
618
|
|
|
564
619
|
if pm.is_prerelease:
|
|
565
|
-
remarks.append("prerelease")
|
|
620
|
+
remarks.append(_("prerelease"))
|
|
566
621
|
if pm.service_level.has_known_issues:
|
|
567
|
-
remarks.append("has known issues")
|
|
622
|
+
remarks.append(_("has known issues"))
|
|
568
623
|
if pm.upstream_version:
|
|
569
|
-
remarks.append(
|
|
624
|
+
remarks.append(
|
|
625
|
+
_("upstream: {upstream_ver}").format(
|
|
626
|
+
upstream_ver=pm.upstream_version
|
|
627
|
+
)
|
|
628
|
+
)
|
|
570
629
|
|
|
571
630
|
remark_str = f" ({', '.join(remarks)})" if remarks else ""
|
|
572
631
|
version_choices.append(f"{version_str}{remark_str}")
|
|
@@ -574,7 +633,9 @@ def customize_package_versions(
|
|
|
574
633
|
# Ask the user to select a version
|
|
575
634
|
version_idx = user_input.ask_for_choice(
|
|
576
635
|
logger,
|
|
577
|
-
|
|
636
|
+
_("\nSelect a version for package [green]{pkg}[/]:").format(
|
|
637
|
+
pkg=pkg_fullname,
|
|
638
|
+
),
|
|
578
639
|
version_choices,
|
|
579
640
|
)
|
|
580
641
|
|
|
@@ -586,27 +647,27 @@ def customize_package_versions(
|
|
|
586
647
|
else:
|
|
587
648
|
new_atom = f"{package_name}(=={selected_version.ver})"
|
|
588
649
|
|
|
589
|
-
logger.stdout(
|
|
650
|
+
logger.stdout(_("Selected: [blue]{new_atom}[/]").format(new_atom=new_atom))
|
|
590
651
|
result.append(new_atom)
|
|
591
652
|
|
|
592
|
-
logger.stdout("\nPackage versions to be installed:")
|
|
653
|
+
logger.stdout(_("\nPackage versions to be installed:"))
|
|
593
654
|
for atom in result:
|
|
594
655
|
logger.stdout(f" * [green]{atom}[/]")
|
|
595
656
|
|
|
596
657
|
confirmation = user_input.ask_for_choice(
|
|
597
658
|
logger,
|
|
598
|
-
"\nHow would you like to proceed?",
|
|
659
|
+
_("\nHow would you like to proceed?"),
|
|
599
660
|
[
|
|
600
|
-
"Continue with these versions",
|
|
601
|
-
"Restart version selection",
|
|
602
|
-
"Abort device provisioning",
|
|
661
|
+
_("Continue with these versions"),
|
|
662
|
+
_("Restart version selection"),
|
|
663
|
+
_("Abort device provisioning"),
|
|
603
664
|
],
|
|
604
665
|
)
|
|
605
666
|
|
|
606
667
|
if confirmation == 0: # Continue with these versions
|
|
607
668
|
return result
|
|
608
669
|
elif confirmation == 1: # Restart version selection
|
|
609
|
-
logger.stdout("\nRestarting package version selection...")
|
|
670
|
+
logger.stdout(_("\nRestarting package version selection..."))
|
|
610
671
|
continue
|
|
611
672
|
else: # Abort installation
|
|
612
673
|
return None
|
ruyi/device/provision_cli.py
CHANGED
|
@@ -2,6 +2,7 @@ import argparse
|
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from ..cli.cmd import RootCommand
|
|
5
|
+
from ..i18n import _
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
7
8
|
from ..cli.completion import ArgumentParser
|
|
@@ -12,7 +13,7 @@ class DeviceCommand(
|
|
|
12
13
|
RootCommand,
|
|
13
14
|
cmd="device",
|
|
14
15
|
has_subcommands=True,
|
|
15
|
-
help="Manage devices",
|
|
16
|
+
help=_("Manage devices"),
|
|
16
17
|
):
|
|
17
18
|
@classmethod
|
|
18
19
|
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
@@ -23,7 +24,7 @@ class DeviceProvisionCommand(
|
|
|
23
24
|
DeviceCommand,
|
|
24
25
|
cmd="provision",
|
|
25
26
|
aliases=["flash"],
|
|
26
|
-
help="Interactively initialize a device for development",
|
|
27
|
+
help=_("Interactively initialize a device for development"),
|
|
27
28
|
):
|
|
28
29
|
@classmethod
|
|
29
30
|
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
@@ -36,5 +37,7 @@ class DeviceProvisionCommand(
|
|
|
36
37
|
try:
|
|
37
38
|
return do_provision_interactive(cfg)
|
|
38
39
|
except KeyboardInterrupt:
|
|
39
|
-
cfg.logger.stdout(
|
|
40
|
+
cfg.logger.stdout(
|
|
41
|
+
_("\n\nKeyboard interrupt received, exiting."), end="\n\n"
|
|
42
|
+
)
|
|
40
43
|
return 1
|