lagora-cli 1.1.0
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.
- package/README.md +138 -0
- package/dist/help.txt +70 -0
- package/dist/lagora.js +342 -0
- package/dist/report-help.txt +5 -0
- package/dist/scripts/agora_playground_harness.py +263 -0
- package/dist/scripts/announce.js +41 -0
- package/dist/scripts/check-kernel-submission.py +90 -0
- package/dist/scripts/chunk-2EAJVB5D.js +100 -0
- package/dist/scripts/chunk-2KTLCUFI.js +29 -0
- package/dist/scripts/chunk-AZ3EEBVD.js +137 -0
- package/dist/scripts/chunk-NBJMYAOA.js +2128 -0
- package/dist/scripts/chunk-NCJMUBTG.js +125 -0
- package/dist/scripts/chunk-QJPQHKIO.js +23 -0
- package/dist/scripts/chunk-RIR5KGHC.js +33 -0
- package/dist/scripts/chunk-TJZVQYBL.js +8 -0
- package/dist/scripts/chunk-UHJXD4TG.js +18 -0
- package/dist/scripts/chunk-UQ6I6VTY.js +117 -0
- package/dist/scripts/cli-auth.js +348 -0
- package/dist/scripts/cli-config-IA7EOSYD.js +7 -0
- package/dist/scripts/install-skill.js +199 -0
- package/dist/scripts/issue-local-client-DZUXZOKY.js +22 -0
- package/dist/scripts/issue-search.js +1823 -0
- package/dist/scripts/issue.js +386 -0
- package/dist/scripts/keycloak-provision.js +986 -0
- package/dist/scripts/legato-fsim-runner.py +126 -0
- package/dist/scripts/legato-lowering-runner.py +156 -0
- package/dist/scripts/legato_runner_annotations.py +235 -0
- package/dist/scripts/legato_runner_env.py +91 -0
- package/dist/scripts/legato_runner_launchers.py +287 -0
- package/dist/scripts/legato_runner_script_wrapper.py +193 -0
- package/dist/scripts/notifications-EU43SIEV.js +624 -0
- package/dist/scripts/playground.js +408 -0
- package/dist/scripts/report-bundle-sync-3U7QTP4Z.js +215 -0
- package/dist/scripts/report.js +104 -0
- package/dist/scripts/resolve-sdk-package-version.py +151 -0
- package/dist/scripts/sdk-runtime-JE6H2PB2.js +992 -0
- package/dist/scripts/sdk-runtime-kubernetes-job-KOWL4ITV.js +479 -0
- package/dist/scripts/sdk-runtime-smoke.py +168 -0
- package/dist/scripts/sdk.js +256 -0
- package/dist/scripts/site-feedback-CAPE5MPX.js +136 -0
- package/dist/scripts/site-feedback-rate-limit-5BU2WSFE.js +86 -0
- package/dist/scripts/site-feedback.js +117 -0
- package/dist/scripts/storage-234FBH54.js +67 -0
- package/dist/scripts/submit-issue.sh +489 -0
- package/dist/scripts/verification-3QCY66QW.js +772 -0
- package/dist/scripts/verify-issue.js +144 -0
- package/dist/skills/legato-agora-cli/SKILL.md +556 -0
- package/dist/skills/legato-agora-cli/agents/openai.yaml +7 -0
- package/dist/skills/legato-agora-cli/reference/kernel-with-golden.py +84 -0
- package/dist/skills/legato-site-feedback/SKILL.md +49 -0
- package/dist/skills/legato-site-feedback/agents/openai.yaml +7 -0
- package/package.json +16 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import importlib.util
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from legato_runner_env import launcher_env, launcher_import_paths, launcher_working_directory, script_command
|
|
14
|
+
|
|
15
|
+
CALLABLE_ENTRY_NAMES = ("main", "run", "launch", "lower")
|
|
16
|
+
CALLABLE_ENTRY_PARAMETERS = {"output_type_name", "output_type", "stage", "output_root", "repo_root"}
|
|
17
|
+
LOWERING_ARTIFACT_SUFFIXES = {".mlir", ".ll", ".s", ".asm", ".bin", ".json", ".bc", ".elf", ".o"}
|
|
18
|
+
LEGATO_KERNEL_DECORATORS = {"compile", "primitive"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LauncherError(RuntimeError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LauncherScriptError(LauncherError):
|
|
26
|
+
"""The submitted kernel ran but failed on its own terms.
|
|
27
|
+
|
|
28
|
+
Distinct from LauncherError so callers can report a kernel failure instead
|
|
29
|
+
of blaming the SDK environment.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
ACTIVE_PROCESS: subprocess.Popen[str] | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def statically_decorated_kernel_names(kernel_path: Path) -> set[str]:
|
|
37
|
+
tree = parse_kernel(kernel_path)
|
|
38
|
+
return {
|
|
39
|
+
node.name
|
|
40
|
+
for node in tree.body
|
|
41
|
+
if isinstance(node, ast.FunctionDef)
|
|
42
|
+
and any(is_legato_kernel_decorator(decorator) for decorator in node.decorator_list)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def kernel_has_launcher(kernel_path: Path) -> bool:
|
|
47
|
+
return script_has_main_guard(kernel_path) or bool(callable_entry_names(kernel_path)) or bool(argparse_options(kernel_path))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def launcher_runs_in_subprocess(kernel_path: Path) -> bool:
|
|
51
|
+
"""Whether run_launcher will execute this kernel in a child process.
|
|
52
|
+
|
|
53
|
+
In-process device state (hart's fsim environment) does not cross that
|
|
54
|
+
boundary, so callers that configure a device must know which side runs
|
|
55
|
+
the kernel. Mirrors the dispatch in run_launcher.
|
|
56
|
+
"""
|
|
57
|
+
return script_has_main_guard(kernel_path) or not callable_entry_names(kernel_path)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_launcher(kernel_path: Path, stages: list[str], output_root: Path) -> None:
|
|
61
|
+
options = argparse_options(kernel_path)
|
|
62
|
+
if script_has_main_guard(kernel_path):
|
|
63
|
+
run_cli_launcher(kernel_path, stages, output_root, options)
|
|
64
|
+
return
|
|
65
|
+
if callable_entry_names(kernel_path):
|
|
66
|
+
run_callable_launcher(kernel_path, stages, output_root)
|
|
67
|
+
return
|
|
68
|
+
run_cli_launcher(kernel_path, stages, output_root, options)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_kernel(kernel_path: Path) -> ast.Module:
|
|
72
|
+
with kernel_path.open("r", encoding="utf-8") as source:
|
|
73
|
+
return ast.parse(source.read(), filename=str(kernel_path))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_legato_kernel_decorator(decorator: ast.expr) -> bool:
|
|
77
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
78
|
+
return (
|
|
79
|
+
isinstance(target, ast.Attribute)
|
|
80
|
+
and target.attr in LEGATO_KERNEL_DECORATORS
|
|
81
|
+
) or (
|
|
82
|
+
isinstance(target, ast.Name)
|
|
83
|
+
and target.id in LEGATO_KERNEL_DECORATORS
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def script_has_main_guard(kernel_path: Path) -> bool:
|
|
88
|
+
for node in parse_kernel(kernel_path).body:
|
|
89
|
+
if isinstance(node, ast.If) and is_main_guard(node.test):
|
|
90
|
+
return True
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_main_guard(test: ast.expr) -> bool:
|
|
95
|
+
if not isinstance(test, ast.Compare) or len(test.ops) != 1 or len(test.comparators) != 1:
|
|
96
|
+
return False
|
|
97
|
+
left = test.left
|
|
98
|
+
right = test.comparators[0]
|
|
99
|
+
return (
|
|
100
|
+
isinstance(test.ops[0], ast.Eq)
|
|
101
|
+
and isinstance(left, ast.Name)
|
|
102
|
+
and left.id == "__name__"
|
|
103
|
+
and isinstance(right, ast.Constant)
|
|
104
|
+
and right.value == "__main__"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def argparse_options(kernel_path: Path) -> set[str]:
|
|
109
|
+
options: set[str] = set()
|
|
110
|
+
for node in ast.walk(parse_kernel(kernel_path)):
|
|
111
|
+
if not isinstance(node, ast.Call):
|
|
112
|
+
continue
|
|
113
|
+
if not isinstance(node.func, ast.Attribute) or node.func.attr != "add_argument":
|
|
114
|
+
continue
|
|
115
|
+
for arg in node.args:
|
|
116
|
+
if isinstance(arg, ast.Constant) and isinstance(arg.value, str) and arg.value.startswith("--"):
|
|
117
|
+
options.add(arg.value)
|
|
118
|
+
return options
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def callable_entry_names(kernel_path: Path) -> set[str]:
|
|
122
|
+
return {
|
|
123
|
+
node.name
|
|
124
|
+
for node in parse_kernel(kernel_path).body
|
|
125
|
+
if isinstance(node, ast.FunctionDef)
|
|
126
|
+
and node.name in CALLABLE_ENTRY_NAMES
|
|
127
|
+
and has_supported_launcher_parameters(node)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def has_supported_launcher_parameters(node: ast.FunctionDef) -> bool:
|
|
132
|
+
positional = list(node.args.posonlyargs) + list(node.args.args)
|
|
133
|
+
all_parameters = [arg.arg for arg in positional]
|
|
134
|
+
all_parameters.extend(arg.arg for arg in node.args.kwonlyargs)
|
|
135
|
+
if not any(name in CALLABLE_ENTRY_PARAMETERS for name in all_parameters):
|
|
136
|
+
return False
|
|
137
|
+
required_count = len(positional) - len(node.args.defaults)
|
|
138
|
+
required = [arg.arg for arg in positional[:required_count]]
|
|
139
|
+
required.extend(arg.arg for arg, default in zip(node.args.kwonlyargs, node.args.kw_defaults) if default is None)
|
|
140
|
+
return all(name in CALLABLE_ENTRY_PARAMETERS for name in required)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def run_cli_launcher(kernel_path: Path, stages: list[str], output_root: Path, options: set[str]) -> None:
|
|
144
|
+
if "--output-type" in options:
|
|
145
|
+
for stage in stages:
|
|
146
|
+
run_cli_stage(kernel_path, stage, output_root / stage.lower(), options)
|
|
147
|
+
return
|
|
148
|
+
# One process per stage. legato compiles a kernel once per process and keys
|
|
149
|
+
# what it cached without the output type, so a launcher asked for MLIR,
|
|
150
|
+
# CORE_IR and BACKEND_IR in one run wrote the first and returned the cached
|
|
151
|
+
# result for the other two -- three listings collapsed into one, silently.
|
|
152
|
+
for stage in stages:
|
|
153
|
+
stage_root = output_root / stage.lower()
|
|
154
|
+
stage_root.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
command = script_command(kernel_path)
|
|
156
|
+
if "--repo-root" in options:
|
|
157
|
+
command.extend(["--repo-root", str(Path.cwd())])
|
|
158
|
+
if "--output-root" in options:
|
|
159
|
+
command.extend(["--output-root", str(stage_root)])
|
|
160
|
+
if "--stages" in options:
|
|
161
|
+
command.extend(["--stages", stage])
|
|
162
|
+
run_command(command, launcher_working_directory(), launcher_env(kernel_path, stage_root, [stage]), stage_root, stage)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def run_cli_stage(kernel_path: Path, stage: str, stage_root: Path, options: set[str]) -> None:
|
|
166
|
+
command = [*script_command(kernel_path), "--output-type", stage]
|
|
167
|
+
if "--repo-root" in options:
|
|
168
|
+
command.extend(["--repo-root", str(Path.cwd())])
|
|
169
|
+
if "--output-root" in options:
|
|
170
|
+
command.extend(["--output-root", str(stage_root)])
|
|
171
|
+
run_command(command, launcher_working_directory(), launcher_env(kernel_path, stage_root, [stage]), stage_root, stage)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def run_command(command: list[str], cwd: Path, env: dict[str, str], output_root: Path, stage: str) -> None:
|
|
175
|
+
global ACTIVE_PROCESS
|
|
176
|
+
output_root.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
print("[LEGATO_WORKER] launcher_mode=script", flush=True)
|
|
178
|
+
print(f"[LEGATO_WORKER] stage={stage} status=running", flush=True)
|
|
179
|
+
print(f"[LEGATO_WORKER] cwd={cwd}", flush=True)
|
|
180
|
+
print(f"[LEGATO_WORKER] output_path={output_root}", flush=True)
|
|
181
|
+
print(f"[LEGATO_WORKER] import_paths={env.get('AGORA_LEGATO_IMPORT_PATHS', '')}", flush=True)
|
|
182
|
+
print(f"[LEGATO_WORKER] command={' '.join(command)}", flush=True)
|
|
183
|
+
process = subprocess.Popen(command, cwd=cwd, env=env, start_new_session=True)
|
|
184
|
+
ACTIVE_PROCESS = process
|
|
185
|
+
try:
|
|
186
|
+
return_code = process.wait()
|
|
187
|
+
finally:
|
|
188
|
+
ACTIVE_PROCESS = None
|
|
189
|
+
if return_code != 0:
|
|
190
|
+
raise LauncherScriptError(f"launcher script exited with code {return_code}")
|
|
191
|
+
require_artifacts(output_root, "launcher script completed with no lowering artifacts")
|
|
192
|
+
print(f"[LEGATO_WORKER] stage={stage} status=success", flush=True)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def run_callable_launcher(kernel_path: Path, stages: list[str], output_root: Path) -> None:
|
|
196
|
+
module = load_module(kernel_path)
|
|
197
|
+
entry = first_callable_entry(module)
|
|
198
|
+
for stage in stages:
|
|
199
|
+
stage_root = output_root / stage.lower()
|
|
200
|
+
stage_root.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
print("[LEGATO_WORKER] launcher_mode=callable", flush=True)
|
|
202
|
+
print(f"[LEGATO_WORKER] stage={stage} status=running", flush=True)
|
|
203
|
+
print(f"[LEGATO_WORKER] output_path={stage_root}", flush=True)
|
|
204
|
+
call_entry(entry, stage, stage_root)
|
|
205
|
+
require_artifacts(stage_root, "launcher callable completed without generated artifacts")
|
|
206
|
+
print(f"[LEGATO_WORKER] stage={stage} status=success", flush=True)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def load_module(kernel_path: Path) -> Any:
|
|
210
|
+
spec = importlib.util.spec_from_file_location("agora_launcher_kernel", kernel_path)
|
|
211
|
+
if spec is None or spec.loader is None:
|
|
212
|
+
raise LauncherError(f"cannot import launcher module: {kernel_path}")
|
|
213
|
+
module = importlib.util.module_from_spec(spec)
|
|
214
|
+
import_paths = launcher_import_paths(kernel_path)
|
|
215
|
+
for path in reversed(import_paths):
|
|
216
|
+
sys.path.insert(0, str(path))
|
|
217
|
+
try:
|
|
218
|
+
spec.loader.exec_module(module)
|
|
219
|
+
finally:
|
|
220
|
+
for path in import_paths:
|
|
221
|
+
if sys.path and sys.path[0] == str(path):
|
|
222
|
+
sys.path.pop(0)
|
|
223
|
+
return module
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def require_artifacts(output_root: Path, message: str) -> None:
|
|
227
|
+
artifacts = [
|
|
228
|
+
path for path in output_root.rglob("*")
|
|
229
|
+
if path.is_file()
|
|
230
|
+
]
|
|
231
|
+
lowering_artifacts = [
|
|
232
|
+
path for path in artifacts
|
|
233
|
+
if path.suffix.lower() in LOWERING_ARTIFACT_SUFFIXES
|
|
234
|
+
]
|
|
235
|
+
if not lowering_artifacts:
|
|
236
|
+
if artifacts:
|
|
237
|
+
names = ", ".join(str(path.relative_to(output_root)) for path in artifacts[:5])
|
|
238
|
+
raise LauncherScriptError(f"{message}; found non-lowering files: {names}")
|
|
239
|
+
raise LauncherScriptError(message)
|
|
240
|
+
print(f"[LEGATO_WORKER] generated_artifacts={len(lowering_artifacts)}", flush=True)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def first_callable_entry(module: Any) -> Any:
|
|
244
|
+
for name in CALLABLE_ENTRY_NAMES:
|
|
245
|
+
value = getattr(module, name, None)
|
|
246
|
+
if callable(value) and has_supported_signature(value):
|
|
247
|
+
return value
|
|
248
|
+
raise LauncherError("no launcher entrypoint found; expected main(), run(), launch(), or lower()")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def has_supported_signature(value: Any) -> bool:
|
|
252
|
+
signature = inspect.signature(value)
|
|
253
|
+
if not any(name in CALLABLE_ENTRY_PARAMETERS for name in signature.parameters):
|
|
254
|
+
return False
|
|
255
|
+
for name, parameter in signature.parameters.items():
|
|
256
|
+
if name in CALLABLE_ENTRY_PARAMETERS:
|
|
257
|
+
continue
|
|
258
|
+
if parameter.default is inspect.Signature.empty:
|
|
259
|
+
return False
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def call_entry(entry: Any, stage: str, output_root: Path) -> None:
|
|
264
|
+
signature = inspect.signature(entry)
|
|
265
|
+
kwargs: dict[str, Any] = {}
|
|
266
|
+
for name, parameter in signature.parameters.items():
|
|
267
|
+
if name in {"output_type_name", "output_type", "stage"}:
|
|
268
|
+
kwargs[name] = stage
|
|
269
|
+
elif name == "output_root":
|
|
270
|
+
kwargs[name] = output_root
|
|
271
|
+
elif name == "repo_root":
|
|
272
|
+
kwargs[name] = launcher_working_directory()
|
|
273
|
+
elif parameter.default is inspect.Signature.empty:
|
|
274
|
+
raise LauncherError(f"launcher entrypoint has unsupported required parameter: {name}")
|
|
275
|
+
result = entry(**kwargs)
|
|
276
|
+
if isinstance(result, int) and result != 0:
|
|
277
|
+
raise LauncherError(f"launcher entrypoint returned {result}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def terminate_active_process(signum: int, _frame: Any) -> None:
|
|
281
|
+
if ACTIVE_PROCESS is not None and ACTIVE_PROCESS.poll() is None:
|
|
282
|
+
os.killpg(ACTIVE_PROCESS.pid, signum)
|
|
283
|
+
raise SystemExit(128 + signum)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
signal.signal(signal.SIGTERM, terminate_active_process)
|
|
287
|
+
signal.signal(signal.SIGINT, terminate_active_process)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
import runpy
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def prepare_bertha(ctx: Any) -> Any:
|
|
13
|
+
import legato.model.bertha as bertha_model
|
|
14
|
+
|
|
15
|
+
return bertha_model.Bertha(ctx, "default", 32, False, 8, 128 * pow(1024, 3))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def setup_ha_home(anchor: Path) -> Path:
|
|
19
|
+
return anchor
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def install_compat_modules() -> None:
|
|
23
|
+
install_module("kernels", None)
|
|
24
|
+
install_module("kernels._common", {"prepare_bertha": prepare_bertha})
|
|
25
|
+
install_module("utils", None)
|
|
26
|
+
install_module("utils.legato_cache", {"setup_ha_home": setup_ha_home})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def install_module(name: str, members: dict[str, Any] | None) -> None:
|
|
30
|
+
if name in sys.modules:
|
|
31
|
+
return
|
|
32
|
+
module = types.ModuleType(name)
|
|
33
|
+
if members is None:
|
|
34
|
+
module.__path__ = [] # type: ignore[attr-defined]
|
|
35
|
+
else:
|
|
36
|
+
for key, value in members.items():
|
|
37
|
+
setattr(module, key, value)
|
|
38
|
+
sys.modules[name] = module
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install_legato_session_redirect() -> None:
|
|
42
|
+
output_root = os.environ.get("AGORA_LEGATO_OUTPUT_ROOT", "").strip()
|
|
43
|
+
stages = [stage.strip() for stage in os.environ.get("AGORA_LEGATO_STAGES", "").split(",") if stage.strip()]
|
|
44
|
+
launch_value = os.environ.get("AGORA_LEGATO_SESSION_LAUNCH", "").strip().lower()
|
|
45
|
+
launch_override = session_launch_override(launch_value)
|
|
46
|
+
if not output_root and not stages and launch_override is None:
|
|
47
|
+
return
|
|
48
|
+
import legato
|
|
49
|
+
|
|
50
|
+
original_session = legato.session
|
|
51
|
+
effective_output_path = str(Path(output_root).resolve()) if output_root else None
|
|
52
|
+
effective_output_type = getattr(legato.OutputType, stages[0]) if len(stages) == 1 else None
|
|
53
|
+
accepts_launch = session_accepts_launch(original_session)
|
|
54
|
+
output_type_position = session_parameter_position(original_session, "output_type")
|
|
55
|
+
output_path_position = session_parameter_position(original_session, "output_path")
|
|
56
|
+
launch_position = session_parameter_position(original_session, "launch")
|
|
57
|
+
|
|
58
|
+
def redirected_session(*args: Any, **kwargs: Any) -> Any:
|
|
59
|
+
next_args = list(args)
|
|
60
|
+
next_kwargs = dict(kwargs)
|
|
61
|
+
if effective_output_type is not None:
|
|
62
|
+
original_output_type = session_argument(next_args, next_kwargs, "output_type", output_type_position)
|
|
63
|
+
if original_output_type != effective_output_type:
|
|
64
|
+
print(f"[LEGATO_WORKER] session_output_type_effective={stages[0]}", flush=True)
|
|
65
|
+
replace_session_argument(
|
|
66
|
+
next_args,
|
|
67
|
+
next_kwargs,
|
|
68
|
+
"output_type",
|
|
69
|
+
output_type_position,
|
|
70
|
+
effective_output_type,
|
|
71
|
+
)
|
|
72
|
+
if effective_output_path is not None:
|
|
73
|
+
original_output_path = session_argument(next_args, next_kwargs, "output_path", output_path_position)
|
|
74
|
+
if original_output_path is not None:
|
|
75
|
+
original_text = str(original_output_path)
|
|
76
|
+
if original_text != effective_output_path:
|
|
77
|
+
print(f"[LEGATO_WORKER] session_output_path_original={original_text}", flush=True)
|
|
78
|
+
print(f"[LEGATO_WORKER] session_output_path_effective={effective_output_path}", flush=True)
|
|
79
|
+
else:
|
|
80
|
+
print(f"[LEGATO_WORKER] session_output_path_injected={effective_output_path}", flush=True)
|
|
81
|
+
replace_session_argument(
|
|
82
|
+
next_args,
|
|
83
|
+
next_kwargs,
|
|
84
|
+
"output_path",
|
|
85
|
+
output_path_position,
|
|
86
|
+
effective_output_path,
|
|
87
|
+
)
|
|
88
|
+
if launch_override is not None and accepts_launch:
|
|
89
|
+
original_launch = session_argument(next_args, next_kwargs, "launch", launch_position)
|
|
90
|
+
if original_launch != launch_override:
|
|
91
|
+
print(f"[LEGATO_WORKER] session_launch_effective={str(launch_override).lower()}", flush=True)
|
|
92
|
+
replace_session_argument(next_args, next_kwargs, "launch", launch_position, launch_override)
|
|
93
|
+
return original_session(*next_args, **next_kwargs)
|
|
94
|
+
|
|
95
|
+
legato.session = redirected_session
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def session_launch_override(value: str) -> bool | None:
|
|
99
|
+
if value in {"0", "false", "no", "off"}:
|
|
100
|
+
return False
|
|
101
|
+
if value in {"1", "true", "yes", "on"}:
|
|
102
|
+
return True
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def session_accepts_launch(session_factory: Any) -> bool:
|
|
107
|
+
try:
|
|
108
|
+
signature = inspect.signature(session_factory)
|
|
109
|
+
except (TypeError, ValueError):
|
|
110
|
+
return False
|
|
111
|
+
return "launch" in signature.parameters or any(
|
|
112
|
+
parameter.kind is inspect.Parameter.VAR_KEYWORD
|
|
113
|
+
for parameter in signature.parameters.values()
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def session_parameter_position(session_factory: Any, name: str) -> int | None:
|
|
118
|
+
try:
|
|
119
|
+
signature = inspect.signature(session_factory)
|
|
120
|
+
except (TypeError, ValueError):
|
|
121
|
+
return None
|
|
122
|
+
positional = [
|
|
123
|
+
parameter_name
|
|
124
|
+
for parameter_name, parameter in signature.parameters.items()
|
|
125
|
+
if parameter.kind in {inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD}
|
|
126
|
+
]
|
|
127
|
+
return positional.index(name) if name in positional else None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def session_argument(
|
|
131
|
+
args: list[Any],
|
|
132
|
+
kwargs: dict[str, Any],
|
|
133
|
+
name: str,
|
|
134
|
+
position: int | None,
|
|
135
|
+
) -> Any:
|
|
136
|
+
if name in kwargs:
|
|
137
|
+
return kwargs[name]
|
|
138
|
+
return args[position] if position is not None and position < len(args) else None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def replace_session_argument(
|
|
142
|
+
args: list[Any],
|
|
143
|
+
kwargs: dict[str, Any],
|
|
144
|
+
name: str,
|
|
145
|
+
position: int | None,
|
|
146
|
+
value: Any,
|
|
147
|
+
) -> None:
|
|
148
|
+
if position is not None and position < len(args):
|
|
149
|
+
args[position] = value
|
|
150
|
+
kwargs.pop(name, None)
|
|
151
|
+
return
|
|
152
|
+
kwargs[name] = value
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def configure_fsim_device() -> None:
|
|
156
|
+
"""Open the fsim device in this process when the runner delegated it here.
|
|
157
|
+
|
|
158
|
+
hart keeps device state in the C++ runtime of the process that calls
|
|
159
|
+
set_fsim_env, so a script launcher has to open its own.
|
|
160
|
+
"""
|
|
161
|
+
if os.environ.get("AGORA_FSIM_CONFIGURE_IN_CHILD", "").strip() != "1":
|
|
162
|
+
return
|
|
163
|
+
import hart
|
|
164
|
+
|
|
165
|
+
device_version = getattr(hart.HaDeviceVersion, os.environ["AGORA_FSIM_DEVICE_VERSION"])
|
|
166
|
+
compute_mode = getattr(hart.HaFSimComputeMode, os.environ["AGORA_FSIM_COMPUTE_MODE"])
|
|
167
|
+
hart.set_fsim_env(
|
|
168
|
+
device_version,
|
|
169
|
+
compute_mode,
|
|
170
|
+
os.environ["AGORA_FSIM_LOG_PATH"],
|
|
171
|
+
os.environ.get("AGORA_FSIM_LOG_LEVEL", "trace"),
|
|
172
|
+
)
|
|
173
|
+
if hart.get_device_count() == 0:
|
|
174
|
+
raise RuntimeError("no fsim devices available in launcher process")
|
|
175
|
+
hart.set_device(0)
|
|
176
|
+
print("[LEGATO_WORKER] fsim_device=ready", flush=True)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main() -> None:
|
|
180
|
+
if len(sys.argv) < 2:
|
|
181
|
+
raise SystemExit("usage: legato_runner_script_wrapper.py <script> [args...]")
|
|
182
|
+
install_compat_modules()
|
|
183
|
+
import_paths = [path for path in os.environ.get("AGORA_LEGATO_IMPORT_PATHS", "").split(os.pathsep) if path]
|
|
184
|
+
sys.path[:0] = [path for path in import_paths if path not in sys.path]
|
|
185
|
+
configure_fsim_device()
|
|
186
|
+
install_legato_session_redirect()
|
|
187
|
+
script = sys.argv[1]
|
|
188
|
+
sys.argv = [script, *sys.argv[2:]]
|
|
189
|
+
runpy.run_path(script, run_name="__main__")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
main()
|