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.
Files changed (101) hide show
  1. ruyi/__init__.py +21 -0
  2. ruyi/__main__.py +98 -0
  3. ruyi/cli/__init__.py +5 -0
  4. ruyi/cli/builtin_commands.py +14 -0
  5. ruyi/cli/cmd.py +224 -0
  6. ruyi/cli/completer.py +50 -0
  7. ruyi/cli/completion.py +26 -0
  8. ruyi/cli/config_cli.py +153 -0
  9. ruyi/cli/main.py +111 -0
  10. ruyi/cli/self_cli.py +295 -0
  11. ruyi/cli/user_input.py +127 -0
  12. ruyi/cli/version_cli.py +45 -0
  13. ruyi/config/__init__.py +401 -0
  14. ruyi/config/editor.py +92 -0
  15. ruyi/config/errors.py +76 -0
  16. ruyi/config/news.py +39 -0
  17. ruyi/config/schema.py +197 -0
  18. ruyi/device/__init__.py +0 -0
  19. ruyi/device/provision.py +591 -0
  20. ruyi/device/provision_cli.py +40 -0
  21. ruyi/log/__init__.py +272 -0
  22. ruyi/mux/.gitignore +1 -0
  23. ruyi/mux/__init__.py +0 -0
  24. ruyi/mux/runtime.py +213 -0
  25. ruyi/mux/venv/__init__.py +12 -0
  26. ruyi/mux/venv/emulator_cfg.py +41 -0
  27. ruyi/mux/venv/maker.py +782 -0
  28. ruyi/mux/venv/venv_cli.py +92 -0
  29. ruyi/mux/venv_cfg.py +214 -0
  30. ruyi/pluginhost/__init__.py +0 -0
  31. ruyi/pluginhost/api.py +206 -0
  32. ruyi/pluginhost/ctx.py +222 -0
  33. ruyi/pluginhost/paths.py +135 -0
  34. ruyi/pluginhost/plugin_cli.py +37 -0
  35. ruyi/pluginhost/unsandboxed.py +246 -0
  36. ruyi/py.typed +0 -0
  37. ruyi/resource_bundle/__init__.py +20 -0
  38. ruyi/resource_bundle/__main__.py +55 -0
  39. ruyi/resource_bundle/data.py +26 -0
  40. ruyi/ruyipkg/__init__.py +0 -0
  41. ruyi/ruyipkg/admin_checksum.py +88 -0
  42. ruyi/ruyipkg/admin_cli.py +83 -0
  43. ruyi/ruyipkg/atom.py +184 -0
  44. ruyi/ruyipkg/augmented_pkg.py +212 -0
  45. ruyi/ruyipkg/canonical_dump.py +320 -0
  46. ruyi/ruyipkg/checksum.py +39 -0
  47. ruyi/ruyipkg/cli_completion.py +42 -0
  48. ruyi/ruyipkg/distfile.py +208 -0
  49. ruyi/ruyipkg/entity.py +387 -0
  50. ruyi/ruyipkg/entity_cli.py +123 -0
  51. ruyi/ruyipkg/entity_provider.py +273 -0
  52. ruyi/ruyipkg/fetch.py +271 -0
  53. ruyi/ruyipkg/host.py +55 -0
  54. ruyi/ruyipkg/install.py +554 -0
  55. ruyi/ruyipkg/install_cli.py +150 -0
  56. ruyi/ruyipkg/list.py +126 -0
  57. ruyi/ruyipkg/list_cli.py +79 -0
  58. ruyi/ruyipkg/list_filter.py +173 -0
  59. ruyi/ruyipkg/msg.py +99 -0
  60. ruyi/ruyipkg/news.py +123 -0
  61. ruyi/ruyipkg/news_cli.py +78 -0
  62. ruyi/ruyipkg/news_store.py +183 -0
  63. ruyi/ruyipkg/pkg_manifest.py +657 -0
  64. ruyi/ruyipkg/profile.py +208 -0
  65. ruyi/ruyipkg/profile_cli.py +33 -0
  66. ruyi/ruyipkg/protocols.py +55 -0
  67. ruyi/ruyipkg/repo.py +763 -0
  68. ruyi/ruyipkg/state.py +345 -0
  69. ruyi/ruyipkg/unpack.py +369 -0
  70. ruyi/ruyipkg/unpack_method.py +91 -0
  71. ruyi/ruyipkg/update_cli.py +54 -0
  72. ruyi/telemetry/__init__.py +0 -0
  73. ruyi/telemetry/aggregate.py +72 -0
  74. ruyi/telemetry/event.py +41 -0
  75. ruyi/telemetry/node_info.py +192 -0
  76. ruyi/telemetry/provider.py +411 -0
  77. ruyi/telemetry/scope.py +43 -0
  78. ruyi/telemetry/store.py +238 -0
  79. ruyi/telemetry/telemetry_cli.py +127 -0
  80. ruyi/utils/__init__.py +0 -0
  81. ruyi/utils/ar.py +74 -0
  82. ruyi/utils/ci.py +63 -0
  83. ruyi/utils/frontmatter.py +38 -0
  84. ruyi/utils/git.py +169 -0
  85. ruyi/utils/global_mode.py +204 -0
  86. ruyi/utils/l10n.py +83 -0
  87. ruyi/utils/markdown.py +73 -0
  88. ruyi/utils/nuitka.py +33 -0
  89. ruyi/utils/porcelain.py +51 -0
  90. ruyi/utils/prereqs.py +77 -0
  91. ruyi/utils/ssl_patch.py +170 -0
  92. ruyi/utils/templating.py +34 -0
  93. ruyi/utils/toml.py +115 -0
  94. ruyi/utils/url.py +7 -0
  95. ruyi/utils/xdg_basedir.py +80 -0
  96. ruyi/version.py +67 -0
  97. ruyi-0.39.0.dist-info/LICENSE-Apache.txt +201 -0
  98. ruyi-0.39.0.dist-info/METADATA +403 -0
  99. ruyi-0.39.0.dist-info/RECORD +101 -0
  100. ruyi-0.39.0.dist-info/WHEEL +4 -0
  101. 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