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
ruyi/mux/venv/maker.py
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
from os import PathLike
|
|
4
|
+
import pathlib
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
from typing import Any, Final, Iterator, TypedDict
|
|
8
|
+
|
|
9
|
+
from ...config import GlobalConfig
|
|
10
|
+
from ...log import RuyiLogger, humanize_list
|
|
11
|
+
from ...ruyipkg.atom import Atom
|
|
12
|
+
from ...ruyipkg.pkg_manifest import EmulatorProgDecl
|
|
13
|
+
from ...ruyipkg.profile import ProfileProxy
|
|
14
|
+
from ...utils.global_mode import ProvidesGlobalMode
|
|
15
|
+
from ...utils.templating import render_template_str
|
|
16
|
+
from . import ConfiguredTargetTuple
|
|
17
|
+
from .emulator_cfg import ResolvedEmulatorProg
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def do_make_venv(
|
|
21
|
+
config: GlobalConfig,
|
|
22
|
+
host: str,
|
|
23
|
+
profile_name: str,
|
|
24
|
+
dest: pathlib.Path,
|
|
25
|
+
with_sysroot: bool,
|
|
26
|
+
override_name: str | None = None,
|
|
27
|
+
tc_atoms_str: list[str] | None = None,
|
|
28
|
+
emu_atom_str: str | None = None,
|
|
29
|
+
sysroot_atom_str: str | None = None,
|
|
30
|
+
extra_cmd_atoms_str: list[str] | None = None,
|
|
31
|
+
) -> int:
|
|
32
|
+
logger = config.logger
|
|
33
|
+
|
|
34
|
+
# TODO: support omitting this if user only has one toolchain installed
|
|
35
|
+
# this should come after implementation of local state cache
|
|
36
|
+
if tc_atoms_str is None:
|
|
37
|
+
logger.F(
|
|
38
|
+
"You have to specify at least one toolchain atom for now, e.g. [yellow]`-t gnu-plct`[/]"
|
|
39
|
+
)
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
mr = config.repo
|
|
43
|
+
|
|
44
|
+
profile = mr.get_profile(profile_name)
|
|
45
|
+
if profile is None:
|
|
46
|
+
logger.F(f"profile '{profile_name}' not found")
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
target_arch = ""
|
|
50
|
+
seen_target_tuples: set[str] = set()
|
|
51
|
+
targets: list[ConfiguredTargetTuple] = []
|
|
52
|
+
warn_differing_target_arch = False
|
|
53
|
+
|
|
54
|
+
for tc_atom_str in tc_atoms_str:
|
|
55
|
+
tc_atom = Atom.parse(tc_atom_str)
|
|
56
|
+
tc_pm = tc_atom.match_in_repo(mr, config.include_prereleases)
|
|
57
|
+
if tc_pm is None:
|
|
58
|
+
logger.F(f"cannot match a toolchain package with [yellow]{tc_atom_str}[/]")
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
if tc_pm.toolchain_metadata is None:
|
|
62
|
+
logger.F(f"the package [yellow]{tc_atom_str}[/] is not a toolchain")
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
if not tc_pm.toolchain_metadata.satisfies_quirk_set(profile.need_quirks):
|
|
66
|
+
logger.F(
|
|
67
|
+
f"the package [yellow]{tc_atom_str}[/] does not support all necessary features for the profile [yellow]{profile_name}[/]"
|
|
68
|
+
)
|
|
69
|
+
logger.I(
|
|
70
|
+
f"quirks needed by profile: {humanize_list(profile.need_quirks, item_color='cyan')}"
|
|
71
|
+
)
|
|
72
|
+
logger.I(
|
|
73
|
+
f"quirks provided by package: {humanize_list(tc_pm.toolchain_metadata.quirks, item_color='yellow')}"
|
|
74
|
+
)
|
|
75
|
+
return 1
|
|
76
|
+
|
|
77
|
+
target_tuple = tc_pm.toolchain_metadata.target
|
|
78
|
+
if target_tuple in seen_target_tuples:
|
|
79
|
+
logger.F(
|
|
80
|
+
f"the target tuple [yellow]{target_tuple}[/] is already covered by one of the requested toolchains"
|
|
81
|
+
)
|
|
82
|
+
logger.I(
|
|
83
|
+
"for now, only toolchains with differing target tuples can co-exist in one virtual environment"
|
|
84
|
+
)
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
toolchain_root = config.lookup_binary_install_dir(
|
|
88
|
+
host,
|
|
89
|
+
tc_pm.name_for_installation,
|
|
90
|
+
)
|
|
91
|
+
if toolchain_root is None:
|
|
92
|
+
logger.F("cannot find the installed directory for the toolchain")
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
tc_sysroot_dir: PathLike[Any] | None = None
|
|
96
|
+
gcc_install_dir: PathLike[Any] | None = None
|
|
97
|
+
if with_sysroot:
|
|
98
|
+
if tc_sysroot_relpath := tc_pm.toolchain_metadata.included_sysroot:
|
|
99
|
+
tc_sysroot_dir = pathlib.Path(toolchain_root) / tc_sysroot_relpath
|
|
100
|
+
else:
|
|
101
|
+
if sysroot_atom_str is None:
|
|
102
|
+
logger.F(
|
|
103
|
+
"sysroot is requested but the toolchain package does not include one, and [yellow]--sysroot-from[/] is not given"
|
|
104
|
+
)
|
|
105
|
+
return 1
|
|
106
|
+
|
|
107
|
+
# try extracting from the sysroot package
|
|
108
|
+
# for now only GCC toolchain packages can provide sysroots, so this is
|
|
109
|
+
# okay
|
|
110
|
+
gcc_pkg_atom = Atom.parse(sysroot_atom_str)
|
|
111
|
+
gcc_pkg_pm = gcc_pkg_atom.match_in_repo(mr, config.include_prereleases)
|
|
112
|
+
if gcc_pkg_pm is None:
|
|
113
|
+
logger.F(
|
|
114
|
+
f"cannot match a toolchain package with [yellow]{sysroot_atom_str}[/]"
|
|
115
|
+
)
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
if gcc_pkg_pm.toolchain_metadata is None:
|
|
119
|
+
logger.F(
|
|
120
|
+
f"the package [yellow]{sysroot_atom_str}[/] is not a toolchain"
|
|
121
|
+
)
|
|
122
|
+
return 1
|
|
123
|
+
|
|
124
|
+
gcc_pkg_root = config.lookup_binary_install_dir(
|
|
125
|
+
host,
|
|
126
|
+
gcc_pkg_pm.name_for_installation,
|
|
127
|
+
)
|
|
128
|
+
if gcc_pkg_root is None:
|
|
129
|
+
logger.F(
|
|
130
|
+
"cannot find the installed directory for the sysroot package"
|
|
131
|
+
)
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
tc_sysroot_relpath = gcc_pkg_pm.toolchain_metadata.included_sysroot
|
|
135
|
+
if tc_sysroot_relpath is None:
|
|
136
|
+
logger.F(
|
|
137
|
+
f"sysroot is requested but the package [yellow]{sysroot_atom_str}[/] does not contain one"
|
|
138
|
+
)
|
|
139
|
+
return 1
|
|
140
|
+
|
|
141
|
+
tc_sysroot_dir = pathlib.Path(gcc_pkg_root) / tc_sysroot_relpath
|
|
142
|
+
|
|
143
|
+
# also figure the GCC include/libs path out for Clang to be able to
|
|
144
|
+
# locate them
|
|
145
|
+
gcc_install_dir = find_gcc_install_dir(
|
|
146
|
+
gcc_pkg_root,
|
|
147
|
+
# we should use the GCC-providing package's target tuple as that's
|
|
148
|
+
# not guaranteed to be the same as llvm's
|
|
149
|
+
gcc_pkg_pm.toolchain_metadata.target,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# for now, require this directory to be present (or clang would barely work)
|
|
153
|
+
if gcc_install_dir is None:
|
|
154
|
+
logger.F(
|
|
155
|
+
"cannot find a GCC include & lib directory in the sysroot package"
|
|
156
|
+
)
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
# derive flags for (the quirks of) this toolchain
|
|
160
|
+
tc_flags = profile.get_common_flags(tc_pm.toolchain_metadata.quirks)
|
|
161
|
+
|
|
162
|
+
# record the target tuple info to configure in the venv
|
|
163
|
+
configured_target: ConfiguredTargetTuple = {
|
|
164
|
+
"target": target_tuple,
|
|
165
|
+
"toolchain_root": toolchain_root,
|
|
166
|
+
"toolchain_sysroot": tc_sysroot_dir,
|
|
167
|
+
"toolchain_flags": tc_flags,
|
|
168
|
+
# assume clang is preferred if package contains clang
|
|
169
|
+
# this is mostly true given most packages don't contain both
|
|
170
|
+
"cc_flavor": "clang" if tc_pm.toolchain_metadata.has_clang else "gcc",
|
|
171
|
+
# same for binutils provider flavor
|
|
172
|
+
"binutils_flavor": (
|
|
173
|
+
"llvm" if tc_pm.toolchain_metadata.has_llvm else "binutils"
|
|
174
|
+
),
|
|
175
|
+
"gcc_install_dir": gcc_install_dir,
|
|
176
|
+
}
|
|
177
|
+
logger.D(f"configuration for {target_tuple}: {configured_target}")
|
|
178
|
+
targets.append(configured_target)
|
|
179
|
+
seen_target_tuples.add(target_tuple)
|
|
180
|
+
|
|
181
|
+
# record the target architecture for use in emulator package matching
|
|
182
|
+
if not target_arch:
|
|
183
|
+
target_arch = tc_pm.toolchain_metadata.target_arch
|
|
184
|
+
elif target_arch != tc_pm.toolchain_metadata.target_arch:
|
|
185
|
+
# first one wins
|
|
186
|
+
warn_differing_target_arch = True
|
|
187
|
+
|
|
188
|
+
if warn_differing_target_arch:
|
|
189
|
+
logger.W("multiple toolchains specified with differing target architecture")
|
|
190
|
+
logger.I(
|
|
191
|
+
f"using the target architecture of the first toolchain: [yellow]{target_arch}[/]"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Now handle the emulator.
|
|
195
|
+
emu_progs = None
|
|
196
|
+
emu_root: PathLike[Any] | None = None
|
|
197
|
+
if emu_atom_str:
|
|
198
|
+
emu_atom = Atom.parse(emu_atom_str)
|
|
199
|
+
emu_pm = emu_atom.match_in_repo(mr, config.include_prereleases)
|
|
200
|
+
if emu_pm is None:
|
|
201
|
+
logger.F(f"cannot match an emulator package with [yellow]{emu_atom_str}[/]")
|
|
202
|
+
return 1
|
|
203
|
+
|
|
204
|
+
if emu_pm.emulator_metadata is None:
|
|
205
|
+
logger.F(f"the package [yellow]{emu_atom_str}[/] is not an emulator")
|
|
206
|
+
return 1
|
|
207
|
+
|
|
208
|
+
emu_progs = list(emu_pm.emulator_metadata.list_for_arch(target_arch))
|
|
209
|
+
if not emu_progs:
|
|
210
|
+
logger.F(
|
|
211
|
+
f"the emulator package [yellow]{emu_atom_str}[/] does not support the target architecture [yellow]{target_arch}[/]"
|
|
212
|
+
)
|
|
213
|
+
return 1
|
|
214
|
+
|
|
215
|
+
for prog in emu_progs:
|
|
216
|
+
if not profile.check_emulator_flavor(
|
|
217
|
+
prog.flavor,
|
|
218
|
+
emu_pm.emulator_metadata.quirks,
|
|
219
|
+
):
|
|
220
|
+
logger.F(
|
|
221
|
+
f"the package [yellow]{emu_atom_str}[/] does not support all necessary features for the profile [yellow]{profile_name}[/]"
|
|
222
|
+
)
|
|
223
|
+
logger.I(
|
|
224
|
+
f"quirks needed by profile: {humanize_list(profile.get_needed_emulator_pkg_flavors(prog.flavor), item_color='cyan')}"
|
|
225
|
+
)
|
|
226
|
+
logger.I(
|
|
227
|
+
f"quirks provided by package: {humanize_list(emu_pm.emulator_metadata.quirks or [], item_color='yellow')}"
|
|
228
|
+
)
|
|
229
|
+
return 1
|
|
230
|
+
|
|
231
|
+
emu_root = config.lookup_binary_install_dir(
|
|
232
|
+
host,
|
|
233
|
+
emu_pm.name_for_installation,
|
|
234
|
+
)
|
|
235
|
+
if emu_root is None:
|
|
236
|
+
logger.F("cannot find the installed directory for the emulator")
|
|
237
|
+
return 1
|
|
238
|
+
|
|
239
|
+
# Now resolve extra commands to provide in the venv.
|
|
240
|
+
extra_cmds: dict[str, str] = {}
|
|
241
|
+
if extra_cmd_atoms_str:
|
|
242
|
+
for extra_cmd_atom_str in extra_cmd_atoms_str:
|
|
243
|
+
extra_cmd_atom = Atom.parse(extra_cmd_atom_str)
|
|
244
|
+
extra_cmd_pm = extra_cmd_atom.match_in_repo(
|
|
245
|
+
mr,
|
|
246
|
+
config.include_prereleases,
|
|
247
|
+
)
|
|
248
|
+
if extra_cmd_pm is None:
|
|
249
|
+
logger.F(
|
|
250
|
+
f"cannot match an extra command package with [yellow]{extra_cmd_atom_str}[/]"
|
|
251
|
+
)
|
|
252
|
+
return 1
|
|
253
|
+
|
|
254
|
+
extra_cmd_bm = extra_cmd_pm.binary_metadata
|
|
255
|
+
if not extra_cmd_bm:
|
|
256
|
+
logger.F(
|
|
257
|
+
f"the package [yellow]{extra_cmd_atom_str}[/] is not a binary-providing package"
|
|
258
|
+
)
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
extra_cmds_decl = extra_cmd_bm.get_commands_for_host(host)
|
|
262
|
+
if not extra_cmds_decl:
|
|
263
|
+
logger.W(
|
|
264
|
+
f"the package [yellow]{extra_cmd_atom_str}[/] does not provide any command for host [yellow]{host}[/], ignoring"
|
|
265
|
+
)
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
cmd_root = config.lookup_binary_install_dir(
|
|
269
|
+
host,
|
|
270
|
+
extra_cmd_pm.name_for_installation,
|
|
271
|
+
)
|
|
272
|
+
if cmd_root is None:
|
|
273
|
+
logger.F(
|
|
274
|
+
f"cannot find the installed directory for the package [yellow]{extra_cmd_pm.name_for_installation}[/]"
|
|
275
|
+
)
|
|
276
|
+
return 1
|
|
277
|
+
cmd_root = pathlib.Path(cmd_root)
|
|
278
|
+
|
|
279
|
+
for cmd, cmd_rel_path in extra_cmds_decl.items():
|
|
280
|
+
# resolve the command path
|
|
281
|
+
cmd_path = (cmd_root / cmd_rel_path).resolve()
|
|
282
|
+
if not cmd_path.is_relative_to(cmd_root):
|
|
283
|
+
# we don't allow commands to resolve outside of the
|
|
284
|
+
# providing package's install root
|
|
285
|
+
logger.F(
|
|
286
|
+
"internal error: resolved command path is outside of the providing package"
|
|
287
|
+
)
|
|
288
|
+
return 1
|
|
289
|
+
|
|
290
|
+
# add the command to the list
|
|
291
|
+
extra_cmds[cmd] = str(cmd_path)
|
|
292
|
+
|
|
293
|
+
if override_name is not None:
|
|
294
|
+
logger.I(
|
|
295
|
+
f"Creating a Ruyi virtual environment [cyan]'{override_name}'[/] at [green]{dest}[/]..."
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
logger.I(f"Creating a Ruyi virtual environment at [green]{dest}[/]...")
|
|
299
|
+
|
|
300
|
+
maker = VenvMaker(
|
|
301
|
+
config,
|
|
302
|
+
profile,
|
|
303
|
+
targets,
|
|
304
|
+
dest.resolve(),
|
|
305
|
+
emu_progs,
|
|
306
|
+
emu_root,
|
|
307
|
+
extra_cmds,
|
|
308
|
+
override_name,
|
|
309
|
+
)
|
|
310
|
+
maker.provision()
|
|
311
|
+
|
|
312
|
+
logger.I(
|
|
313
|
+
render_template_str(
|
|
314
|
+
"prompt.venv-created.txt",
|
|
315
|
+
{
|
|
316
|
+
"sysroot": maker.sysroot_destdir(None),
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def find_gcc_install_dir(
|
|
325
|
+
install_root: PathLike[Any],
|
|
326
|
+
target_tuple: str,
|
|
327
|
+
) -> PathLike[Any] | None:
|
|
328
|
+
# check $PREFIX/lib/gcc/$TARGET/*
|
|
329
|
+
search_root = pathlib.Path(install_root) / "lib" / "gcc" / target_tuple
|
|
330
|
+
try:
|
|
331
|
+
for p in search_root.iterdir():
|
|
332
|
+
# only want the first one (should be the only one)
|
|
333
|
+
return p
|
|
334
|
+
except FileNotFoundError:
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
# nothing?
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class VenvMaker:
|
|
342
|
+
"""Performs the actual creation of a Ruyi virtual environment."""
|
|
343
|
+
|
|
344
|
+
def __init__(
|
|
345
|
+
self,
|
|
346
|
+
gc: GlobalConfig,
|
|
347
|
+
profile: ProfileProxy,
|
|
348
|
+
targets: list[ConfiguredTargetTuple],
|
|
349
|
+
dest: PathLike[Any],
|
|
350
|
+
emulator_progs: list[EmulatorProgDecl] | None,
|
|
351
|
+
emulator_root: PathLike[Any] | None,
|
|
352
|
+
extra_cmds: dict[str, str] | None,
|
|
353
|
+
override_name: str | None = None,
|
|
354
|
+
) -> None:
|
|
355
|
+
self.gc = gc
|
|
356
|
+
self.profile = profile
|
|
357
|
+
self.targets = targets
|
|
358
|
+
self.venv_root = pathlib.Path(dest)
|
|
359
|
+
self.emulator_progs = emulator_progs
|
|
360
|
+
self.emulator_root = emulator_root
|
|
361
|
+
self.extra_cmds = extra_cmds or {}
|
|
362
|
+
self.override_name = override_name
|
|
363
|
+
|
|
364
|
+
self.bindir = self.venv_root / "bin"
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def logger(self) -> RuyiLogger:
|
|
368
|
+
return self.gc.logger
|
|
369
|
+
|
|
370
|
+
def render_and_write(
|
|
371
|
+
self,
|
|
372
|
+
dest: PathLike[Any],
|
|
373
|
+
template_name: str,
|
|
374
|
+
data: dict[str, Any],
|
|
375
|
+
) -> None:
|
|
376
|
+
self.logger.D(f"rendering template '{template_name}' with data {data}")
|
|
377
|
+
content = render_template_str(template_name, data).encode("utf-8")
|
|
378
|
+
self.logger.D(f"writing {dest}")
|
|
379
|
+
with open(dest, "wb") as fp:
|
|
380
|
+
fp.write(content)
|
|
381
|
+
|
|
382
|
+
def sysroot_srcdir(self, target_tuple: str | None) -> pathlib.Path | None:
|
|
383
|
+
if target_tuple is None:
|
|
384
|
+
# check the primary target
|
|
385
|
+
if s := self.targets[0]["toolchain_sysroot"]:
|
|
386
|
+
return pathlib.Path(s)
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
# check if we have this target
|
|
390
|
+
for t in self.targets:
|
|
391
|
+
if t["target"] != target_tuple:
|
|
392
|
+
continue
|
|
393
|
+
if s := t["toolchain_sysroot"]:
|
|
394
|
+
return pathlib.Path(s)
|
|
395
|
+
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
def has_sysroot_for(self, target_tuple: str | None) -> bool:
|
|
399
|
+
return self.sysroot_srcdir(target_tuple) is not None
|
|
400
|
+
|
|
401
|
+
def sysroot_destdir(self, target_tuple: str | None) -> pathlib.Path | None:
|
|
402
|
+
if not self.has_sysroot_for(target_tuple):
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
dirname = f"sysroot.{target_tuple}" if target_tuple is not None else "sysroot"
|
|
406
|
+
return self.venv_root / dirname
|
|
407
|
+
|
|
408
|
+
def provision(self) -> None:
|
|
409
|
+
venv_root = self.venv_root
|
|
410
|
+
bindir = self.bindir
|
|
411
|
+
|
|
412
|
+
venv_root.mkdir()
|
|
413
|
+
bindir.mkdir()
|
|
414
|
+
|
|
415
|
+
env_data = {
|
|
416
|
+
"profile": self.profile.id,
|
|
417
|
+
"sysroot": self.sysroot_destdir(None),
|
|
418
|
+
}
|
|
419
|
+
self.render_and_write(
|
|
420
|
+
venv_root / "ruyi-venv.toml",
|
|
421
|
+
"ruyi-venv.toml",
|
|
422
|
+
env_data,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
for i, tgt in enumerate(self.targets):
|
|
426
|
+
is_primary = i == 0
|
|
427
|
+
self.provision_target(tgt, is_primary)
|
|
428
|
+
|
|
429
|
+
if self.extra_cmds:
|
|
430
|
+
symlink_binaries(
|
|
431
|
+
self.gc,
|
|
432
|
+
self.logger,
|
|
433
|
+
bindir,
|
|
434
|
+
src_cmds_names=list(self.extra_cmds.keys()),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
template_data = {
|
|
438
|
+
"RUYI_VENV": str(venv_root),
|
|
439
|
+
"RUYI_VENV_NAME": self.override_name,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
self.render_and_write(
|
|
443
|
+
bindir / "ruyi-activate",
|
|
444
|
+
"ruyi-activate.bash",
|
|
445
|
+
template_data,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
qemu_bin: PathLike[Any] | None = None
|
|
449
|
+
profile_emu_env: dict[str, str] | None = None
|
|
450
|
+
if self.emulator_root is not None and self.emulator_progs:
|
|
451
|
+
resolved_emu_progs = [
|
|
452
|
+
ResolvedEmulatorProg.new(
|
|
453
|
+
p,
|
|
454
|
+
self.emulator_root,
|
|
455
|
+
self.profile,
|
|
456
|
+
self.sysroot_destdir(None),
|
|
457
|
+
)
|
|
458
|
+
for p in self.emulator_progs
|
|
459
|
+
]
|
|
460
|
+
binfmt_data = {
|
|
461
|
+
"resolved_progs": resolved_emu_progs,
|
|
462
|
+
}
|
|
463
|
+
self.render_and_write(
|
|
464
|
+
venv_root / "binfmt.conf",
|
|
465
|
+
"binfmt.conf",
|
|
466
|
+
binfmt_data,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
for i, p in enumerate(self.emulator_progs):
|
|
470
|
+
if not p.is_qemu:
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
qemu_bin = pathlib.Path(self.emulator_root) / p.relative_path
|
|
474
|
+
profile_emu_env = resolved_emu_progs[i].env
|
|
475
|
+
|
|
476
|
+
self.logger.D("symlinking the ruyi-qemu wrapper")
|
|
477
|
+
os.symlink(self.gc.self_exe, bindir / "ruyi-qemu")
|
|
478
|
+
|
|
479
|
+
# provide initial cached configuration to venv
|
|
480
|
+
self.render_and_write(
|
|
481
|
+
venv_root / "ruyi-cache.v2.toml",
|
|
482
|
+
"ruyi-cache.toml",
|
|
483
|
+
self.make_venv_cache_data(
|
|
484
|
+
qemu_bin,
|
|
485
|
+
self.extra_cmds,
|
|
486
|
+
profile_emu_env,
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
def make_venv_cache_data(
|
|
491
|
+
self,
|
|
492
|
+
qemu_bin: PathLike[Any] | None,
|
|
493
|
+
extra_cmds: dict[str, str],
|
|
494
|
+
profile_emu_env: dict[str, str] | None,
|
|
495
|
+
) -> dict[str, object]:
|
|
496
|
+
targets_cache_data: dict[str, object] = {
|
|
497
|
+
tgt["target"]: {
|
|
498
|
+
"toolchain_bindir": str(pathlib.Path(tgt["toolchain_root"]) / "bin"),
|
|
499
|
+
"toolchain_sysroot": self.sysroot_destdir(tgt["target"]),
|
|
500
|
+
"toolchain_flags": tgt["toolchain_flags"],
|
|
501
|
+
"gcc_install_dir": tgt["gcc_install_dir"],
|
|
502
|
+
}
|
|
503
|
+
for tgt in self.targets
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
cmd_metadata_map = make_cmd_metadata_map(self.logger, self.targets)
|
|
507
|
+
|
|
508
|
+
# add extra cmds that are not associated with any target
|
|
509
|
+
for cmd, dest in extra_cmds.items():
|
|
510
|
+
if cmd in cmd_metadata_map:
|
|
511
|
+
self.logger.W(
|
|
512
|
+
f"extra command {cmd} is already provided by another package, overriding it"
|
|
513
|
+
)
|
|
514
|
+
cmd_metadata_map[cmd] = {
|
|
515
|
+
"dest": dest,
|
|
516
|
+
"target_tuple": "",
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
"profile_emu_env": profile_emu_env,
|
|
521
|
+
"qemu_bin": qemu_bin,
|
|
522
|
+
"targets": targets_cache_data,
|
|
523
|
+
"cmd_metadata_map": cmd_metadata_map,
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
def provision_target(
|
|
527
|
+
self,
|
|
528
|
+
tgt: ConfiguredTargetTuple,
|
|
529
|
+
is_primary: bool,
|
|
530
|
+
) -> None:
|
|
531
|
+
venv_root = self.venv_root
|
|
532
|
+
bindir = self.bindir
|
|
533
|
+
target_tuple = tgt["target"]
|
|
534
|
+
|
|
535
|
+
# getting the destdir this way ensures it's suffixed with the target
|
|
536
|
+
# tuple
|
|
537
|
+
if sysroot_destdir := self.sysroot_destdir(target_tuple):
|
|
538
|
+
sysroot_srcdir = tgt["toolchain_sysroot"]
|
|
539
|
+
assert sysroot_srcdir is not None
|
|
540
|
+
|
|
541
|
+
self.logger.D(f"copying sysroot for {target_tuple}")
|
|
542
|
+
shutil.copytree(
|
|
543
|
+
sysroot_srcdir,
|
|
544
|
+
sysroot_destdir,
|
|
545
|
+
symlinks=True,
|
|
546
|
+
ignore_dangling_symlinks=True,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
if is_primary:
|
|
550
|
+
self.logger.D("symlinking primary sysroot into place")
|
|
551
|
+
primary_sysroot_destdir = self.sysroot_destdir(None)
|
|
552
|
+
assert primary_sysroot_destdir is not None
|
|
553
|
+
os.symlink(sysroot_destdir.name, primary_sysroot_destdir)
|
|
554
|
+
|
|
555
|
+
self.logger.D(f"symlinking {target_tuple} binaries into venv")
|
|
556
|
+
toolchain_bindir = pathlib.Path(tgt["toolchain_root"]) / "bin"
|
|
557
|
+
symlink_binaries(self.gc, self.logger, bindir, src_bindir=toolchain_bindir)
|
|
558
|
+
|
|
559
|
+
make_llvm_tool_aliases(
|
|
560
|
+
self.logger,
|
|
561
|
+
bindir,
|
|
562
|
+
target_tuple,
|
|
563
|
+
tgt["binutils_flavor"] == "llvm",
|
|
564
|
+
tgt["cc_flavor"] == "clang",
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# CMake toolchain file & Meson cross file
|
|
568
|
+
if tgt["cc_flavor"] == "clang":
|
|
569
|
+
cc_path = bindir / "clang"
|
|
570
|
+
cxx_path = bindir / "clang++"
|
|
571
|
+
elif tgt["cc_flavor"] == "gcc":
|
|
572
|
+
cc_path = bindir / f"{target_tuple}-gcc"
|
|
573
|
+
cxx_path = bindir / f"{target_tuple}-g++"
|
|
574
|
+
else:
|
|
575
|
+
raise NotImplementedError
|
|
576
|
+
|
|
577
|
+
if tgt["binutils_flavor"] == "binutils":
|
|
578
|
+
meson_additional_binaries = {
|
|
579
|
+
"ar": bindir / f"{target_tuple}-ar",
|
|
580
|
+
"nm": bindir / f"{target_tuple}-nm",
|
|
581
|
+
"objcopy": bindir / f"{target_tuple}-objcopy",
|
|
582
|
+
"objdump": bindir / f"{target_tuple}-objdump",
|
|
583
|
+
"ranlib": bindir / f"{target_tuple}-ranlib",
|
|
584
|
+
"readelf": bindir / f"{target_tuple}-readelf",
|
|
585
|
+
"strip": bindir / f"{target_tuple}-strip",
|
|
586
|
+
}
|
|
587
|
+
elif tgt["binutils_flavor"] == "llvm":
|
|
588
|
+
meson_additional_binaries = {
|
|
589
|
+
"ar": bindir / "llvm-ar",
|
|
590
|
+
"nm": bindir / "llvm-nm",
|
|
591
|
+
"objcopy": bindir / "llvm-objcopy",
|
|
592
|
+
"objdump": bindir / "llvm-objdump",
|
|
593
|
+
"ranlib": bindir / "llvm-ranlib",
|
|
594
|
+
"readelf": bindir / "llvm-readelf",
|
|
595
|
+
"strip": bindir / "llvm-strip",
|
|
596
|
+
}
|
|
597
|
+
else:
|
|
598
|
+
raise NotImplementedError
|
|
599
|
+
|
|
600
|
+
cmake_toolchain_file_path = venv_root / f"toolchain.{target_tuple}.cmake"
|
|
601
|
+
toolchain_file_data = {
|
|
602
|
+
"cc": cc_path,
|
|
603
|
+
"cxx": cxx_path,
|
|
604
|
+
"processor": self.profile.arch,
|
|
605
|
+
"sysroot": self.sysroot_destdir(target_tuple),
|
|
606
|
+
"venv_root": venv_root,
|
|
607
|
+
"cmake_toolchain_file": str(cmake_toolchain_file_path),
|
|
608
|
+
"meson_additional_binaries": meson_additional_binaries,
|
|
609
|
+
}
|
|
610
|
+
self.render_and_write(
|
|
611
|
+
cmake_toolchain_file_path,
|
|
612
|
+
"toolchain.cmake",
|
|
613
|
+
toolchain_file_data,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
meson_cross_file_path = venv_root / f"meson-cross.{target_tuple}.ini"
|
|
617
|
+
self.render_and_write(
|
|
618
|
+
meson_cross_file_path,
|
|
619
|
+
"meson-cross.ini",
|
|
620
|
+
toolchain_file_data,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
if is_primary:
|
|
624
|
+
self.logger.D(
|
|
625
|
+
f"making cmake & meson file symlinks to primary target {target_tuple}"
|
|
626
|
+
)
|
|
627
|
+
primary_cmake_toolchain_file_path = venv_root / "toolchain.cmake"
|
|
628
|
+
primary_meson_cross_file_path = venv_root / "meson-cross.ini"
|
|
629
|
+
os.symlink(
|
|
630
|
+
cmake_toolchain_file_path.name,
|
|
631
|
+
primary_cmake_toolchain_file_path,
|
|
632
|
+
)
|
|
633
|
+
os.symlink(meson_cross_file_path.name, primary_meson_cross_file_path)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def iter_binaries_to_symlink(
|
|
637
|
+
logger: RuyiLogger,
|
|
638
|
+
bindir: pathlib.Path,
|
|
639
|
+
) -> Iterator[pathlib.Path]:
|
|
640
|
+
for filename in glob.iglob("*", root_dir=bindir):
|
|
641
|
+
src_cmd_path = bindir / filename
|
|
642
|
+
if not is_executable(src_cmd_path):
|
|
643
|
+
logger.D(f"skipping non-executable {filename} in src bindir")
|
|
644
|
+
continue
|
|
645
|
+
|
|
646
|
+
if should_ignore_symlinking(filename):
|
|
647
|
+
logger.D(f"skipping command {filename} explicitly")
|
|
648
|
+
continue
|
|
649
|
+
|
|
650
|
+
yield bindir / filename
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
class CmdMetadataEntry(TypedDict):
|
|
654
|
+
dest: str
|
|
655
|
+
target_tuple: str
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def make_cmd_metadata_map(
|
|
659
|
+
logger: RuyiLogger,
|
|
660
|
+
targets: list[ConfiguredTargetTuple],
|
|
661
|
+
) -> dict[str, CmdMetadataEntry]:
|
|
662
|
+
result: dict[str, CmdMetadataEntry] = {}
|
|
663
|
+
for tgt in targets:
|
|
664
|
+
# TODO: dedup this and provision_target
|
|
665
|
+
toolchain_bindir = pathlib.Path(tgt["toolchain_root"]) / "bin"
|
|
666
|
+
for cmd in iter_binaries_to_symlink(logger, toolchain_bindir):
|
|
667
|
+
result[cmd.name] = {
|
|
668
|
+
"dest": str(cmd),
|
|
669
|
+
"target_tuple": tgt["target"],
|
|
670
|
+
}
|
|
671
|
+
return result
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def symlink_binaries(
|
|
675
|
+
gm: ProvidesGlobalMode,
|
|
676
|
+
logger: RuyiLogger,
|
|
677
|
+
dest_bindir: PathLike[Any],
|
|
678
|
+
*,
|
|
679
|
+
src_bindir: PathLike[Any] | None = None,
|
|
680
|
+
src_cmds_names: list[str] | None = None,
|
|
681
|
+
) -> None:
|
|
682
|
+
dest_binpath = pathlib.Path(dest_bindir)
|
|
683
|
+
self_exe_path = gm.self_exe
|
|
684
|
+
|
|
685
|
+
if src_bindir is not None:
|
|
686
|
+
src_binpath = pathlib.Path(src_bindir)
|
|
687
|
+
for src_cmd_path in iter_binaries_to_symlink(logger, src_binpath):
|
|
688
|
+
filename = src_cmd_path.name
|
|
689
|
+
|
|
690
|
+
# symlink self to dest with the name of this command
|
|
691
|
+
dest_path = dest_binpath / filename
|
|
692
|
+
logger.D(f"making ruyi symlink to {self_exe_path} at {dest_path}")
|
|
693
|
+
os.symlink(self_exe_path, dest_path)
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
if src_cmds_names is not None:
|
|
697
|
+
for cmd in src_cmds_names:
|
|
698
|
+
# symlink self to dest with the name of this command
|
|
699
|
+
dest_path = dest_binpath / cmd
|
|
700
|
+
logger.D(f"making ruyi symlink to {self_exe_path} at {dest_path}")
|
|
701
|
+
os.symlink(self_exe_path, dest_path)
|
|
702
|
+
return
|
|
703
|
+
|
|
704
|
+
raise ValueError(
|
|
705
|
+
"internal error: either src_bindir or src_cmds_names must be provided"
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
LLVM_BINUTILS_ALIASES: Final = {
|
|
710
|
+
"addr2line": "llvm-addr2line",
|
|
711
|
+
"ar": "llvm-ar",
|
|
712
|
+
"as": "llvm-as",
|
|
713
|
+
"c++filt": "llvm-cxxfilt",
|
|
714
|
+
"gcc-ar": "llvm-ar",
|
|
715
|
+
"gcc-nm": "llvm-nm",
|
|
716
|
+
"gcc-ranlib": "llvm-ranlib",
|
|
717
|
+
# 'gcov': 'llvm-cov', # I'm not sure if this is correct
|
|
718
|
+
"ld": "ld.lld",
|
|
719
|
+
"nm": "llvm-nm",
|
|
720
|
+
"objcopy": "llvm-objcopy",
|
|
721
|
+
"objdump": "llvm-objdump",
|
|
722
|
+
"ranlib": "llvm-ranlib",
|
|
723
|
+
"readelf": "llvm-readelf",
|
|
724
|
+
"size": "llvm-size",
|
|
725
|
+
"strings": "llvm-strings",
|
|
726
|
+
"strip": "llvm-strip",
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
CLANG_GCC_ALIASES: Final = {
|
|
730
|
+
"c++": "clang++",
|
|
731
|
+
"cc": "clang",
|
|
732
|
+
"cpp": "clang-cpp",
|
|
733
|
+
"g++": "clang++",
|
|
734
|
+
"gcc": "clang",
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def make_llvm_tool_aliases(
|
|
739
|
+
logger: RuyiLogger,
|
|
740
|
+
dest_bindir: PathLike[Any],
|
|
741
|
+
target_tuple: str,
|
|
742
|
+
do_binutils: bool,
|
|
743
|
+
do_clang: bool,
|
|
744
|
+
) -> None:
|
|
745
|
+
if do_binutils:
|
|
746
|
+
make_compat_symlinks(logger, dest_bindir, target_tuple, LLVM_BINUTILS_ALIASES)
|
|
747
|
+
if do_clang:
|
|
748
|
+
make_compat_symlinks(logger, dest_bindir, target_tuple, CLANG_GCC_ALIASES)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def make_compat_symlinks(
|
|
752
|
+
logger: RuyiLogger,
|
|
753
|
+
dest_bindir: PathLike[Any],
|
|
754
|
+
target_tuple: str,
|
|
755
|
+
aliases: dict[str, str],
|
|
756
|
+
) -> None:
|
|
757
|
+
destdir = pathlib.Path(dest_bindir)
|
|
758
|
+
for compat_basename, symlink_target in aliases.items():
|
|
759
|
+
compat_name = f"{target_tuple}-{compat_basename}"
|
|
760
|
+
logger.D(f"making compat symlink: {compat_name} -> {symlink_target}")
|
|
761
|
+
os.symlink(symlink_target, destdir / compat_name)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def is_executable(p: PathLike[Any]) -> bool:
|
|
765
|
+
return os.access(p, os.F_OK | os.X_OK)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def should_ignore_symlinking(c: str) -> bool:
|
|
769
|
+
return is_command_specific_to_ct_ng(c) or is_command_versioned_cc(c)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def is_command_specific_to_ct_ng(c: str) -> bool:
|
|
773
|
+
return c.endswith("populate") or c.endswith("ct-ng.config")
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
VERSIONED_CC_RE: Final = re.compile(
|
|
777
|
+
r"(?:^|-)(?:g?cc|c\+\+|g\+\+|cpp|clang|clang\+\+)-[0-9.]+$"
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def is_command_versioned_cc(c: str) -> bool:
|
|
782
|
+
return VERSIONED_CC_RE.search(c) is not None
|