xt-cli 0.2.1__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.
__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """xt-cli package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.2.1"
__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from cli import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())
cli.py ADDED
@@ -0,0 +1,342 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from collections.abc import Callable
6
+ from typing import Sequence
7
+
8
+ from __init__ import __version__
9
+ from errors import XtCliError
10
+
11
+ _TASK_REGISTRY: dict[str, tuple[Callable[..., int], bool, Callable[[argparse.ArgumentParser], None] | None, str | None, str, int]] = {}
12
+ _SCOPE_PARSERS: dict[str, Callable[[argparse.ArgumentParser], None]] = {}
13
+
14
+
15
+ def register_scope_parser(source: str, fn: Callable[[argparse.ArgumentParser], None]) -> None:
16
+ """注册 scope 级 parser。"""
17
+ _SCOPE_PARSERS[source] = fn
18
+
19
+
20
+ def register_task(
21
+ name: str,
22
+ handler: Callable[..., int],
23
+ *,
24
+ independent: bool = False,
25
+ add_parser: Callable[[argparse.ArgumentParser], None] | None = None,
26
+ help: str | None = None,
27
+ source: str = "builtin",
28
+ priority: int = 100,
29
+ ) -> None:
30
+ """注册 task 及其处理函数。"""
31
+ _TASK_REGISTRY[name] = (handler, independent, add_parser, help, source, priority)
32
+
33
+
34
+ def add_shared_args_builtin(parser: argparse.ArgumentParser) -> None:
35
+ """内置共享 option——所有 builtin task 均可访问。
36
+
37
+ 当前暂无内置共享 option 需求,此处仅做示例。
38
+ 取消注释即可激活:
39
+ parser.add_argument("--builtin_shared", action="store_true", help="builtin shared option example")
40
+ """
41
+ pass
42
+
43
+ # 注册内置 scope parser
44
+ register_scope_parser("builtin", add_shared_args_builtin)
45
+
46
+
47
+ def _build_global_parser(add_help: bool) -> argparse.ArgumentParser:
48
+ """构建仅含全局 option 的 parser(无 subparser),用于 pass 1 预解析。"""
49
+ parser = argparse.ArgumentParser(
50
+ prog="xt",
51
+ usage="xt [GLOBAL OPTIONS] TASK [TASK OPTIONS] ...",
52
+ description=f"xt command line interface\nversion: {__version__}",
53
+ formatter_class=argparse.RawTextHelpFormatter,
54
+ epilog="The sdk directory must contain .xt-sdk-version.",
55
+ add_help=add_help,
56
+ )
57
+ parser.add_argument("--version", action="version", version=f"xt cli {__version__}")
58
+
59
+ global_group = parser.add_argument_group("global options")
60
+ global_group.add_argument("--target", "-t", metavar="<target>", help="Target name or path\ne.g. windows/simulator")
61
+ global_group.add_argument("--sdk", metavar="<path>", help="SDK directory path\ne.g. D:/work/xt-sdk")
62
+ global_group.add_argument("--toolchain-path", dest="toolchain_path", metavar="<path>", help="Toolchain directory path")
63
+ global_group.add_argument("--project", "-p", metavar="<path>", help="Project directory path")
64
+ global_group.add_argument("--board", metavar="<board>", help="Board name, if add_deps(\"xt_board\")\ne.g. e837n_v01")
65
+ global_group.add_argument("--dirty", action="store_true", help="Allow building with dirty repositories")
66
+ global_group.add_argument("--no-track", action="store_true",
67
+ help="Skip updating xt_vers.jsonc for this build")
68
+ global_group.add_argument("--strict", action="store_true",
69
+ help="Strict mode: enforce all constraints, abort on dirty")
70
+ global_group.add_argument("--sync-deps", action="store_true",
71
+ help="Checkout repos to match xt_deps.jsonc versions before build")
72
+
73
+ return parser
74
+
75
+
76
+ def _source_rank(source: str) -> int:
77
+ """返回 source 在 help 列表中的排序权重。"""
78
+ if source == "builtin":
79
+ return 0
80
+ if source.startswith("platform"):
81
+ return 1
82
+ if source == "project":
83
+ return 2
84
+ return 99
85
+
86
+
87
+ def _build_task_parser(add_help: bool) -> argparse.ArgumentParser:
88
+ """构建带 subparser 的 parser——全局 option + 各 task 作为 subcommand。
89
+
90
+ 用于:1) 预扫描 --help 显示含 subcommand 的全局帮助
91
+ 2) 逐段解析 task_args
92
+
93
+ task 按 source 分组(builtin → platform → project),每组内按 priority 排序。
94
+ """
95
+ parser = _build_global_parser(add_help)
96
+
97
+ # 顶层 scope options 分组(xt --help 可见)
98
+ for source in sorted(_SCOPE_PARSERS.keys(), key=lambda s: _source_rank(s)):
99
+ group = parser.add_argument_group(f"{source} shared options")
100
+ _SCOPE_PARSERS[source](group)
101
+
102
+ subs = parser.add_subparsers(dest="task", title="tasks")
103
+ sorted_tasks = sorted(
104
+ _TASK_REGISTRY.items(),
105
+ key=lambda item: (_source_rank(item[1][4]), item[1][5], item[0]),
106
+ )
107
+ for name, (_, _independent, add_parser, help_desc, source, _priority) in sorted_tasks:
108
+ tagged_help = f"[{source}] {help_desc}" if help_desc else f"[{source}]"
109
+ sub = subs.add_parser(name, help=tagged_help, add_help=True,
110
+ formatter_class=argparse.RawTextHelpFormatter)
111
+
112
+ if add_parser is not None:
113
+ add_parser(sub)
114
+ elif source not in _SCOPE_PARSERS:
115
+ sub.epilog = "No task-specific options."
116
+
117
+ return parser
118
+
119
+
120
+ def build_parser() -> argparse.ArgumentParser:
121
+ """全局 parser(仅含 global option,无 subparser),用于 pass 1。"""
122
+ return _build_global_parser(add_help=False)
123
+
124
+
125
+ def build_help_parser() -> argparse.ArgumentParser:
126
+ """带顶层 --help + subcommand 的 parser,用于预扫描路由。"""
127
+ return _build_task_parser(add_help=True)
128
+
129
+
130
+ def _split_task_segments(unknown_args: list[str]) -> list[tuple[str, list[str]]]:
131
+ """按 task 名切分 unknown_args 为 (task_name, task_args) 段。
132
+
133
+ 首个 task 之前的参数作为共享前缀追加到所有 segment。
134
+ """
135
+ segments: list[tuple[str, list[str]]] = []
136
+ current_task: str | None = None
137
+ current_args: list[str] = []
138
+ pre_task_args: list[str] = []
139
+
140
+ for arg in unknown_args:
141
+ if arg in _TASK_REGISTRY:
142
+ if current_task is not None:
143
+ segments.append((current_task, current_args))
144
+ else:
145
+ pre_task_args = list(current_args)
146
+ current_task = arg
147
+ current_args = []
148
+ else:
149
+ current_args.append(arg)
150
+
151
+ if current_task is not None:
152
+ segments.append((current_task, current_args))
153
+
154
+ if pre_task_args:
155
+ for i in range(len(segments)):
156
+ segments[i] = (segments[i][0], pre_task_args + segments[i][1])
157
+
158
+ return segments
159
+
160
+
161
+ def _apply_independent(segments: list[tuple[str, list[str]]]) -> list[tuple[str, list[str]]]:
162
+ """若存在 independent task,仅保留该 task 段。"""
163
+ for name, _task_args in segments:
164
+ _, independent, _add_parser, _help, _source, _priority = _TASK_REGISTRY[name]
165
+ if independent:
166
+ return [(name, _task_args)]
167
+ return segments
168
+
169
+
170
+ def _sort_tasks(segments: list[tuple[str, list[str]]]) -> list[tuple[str, list[str]]]:
171
+ """按 priority 排序(数值越小越先执行),同优先级保持用户输入顺序(stable sort)。"""
172
+ return sorted(segments, key=lambda x: _TASK_REGISTRY.get(x[0], (None, None, None, None, None, 99))[5])
173
+
174
+
175
+ def _discover_hook_tasks(parsed_args: argparse.Namespace) -> None:
176
+ """扫描 hook 文件,自动注册自定义 task。"""
177
+ from context import build_context_from_options
178
+ from hooks import discover_hook_tasks
179
+
180
+ try:
181
+ context = build_context_from_options(
182
+ cwd=parsed_args.project,
183
+ project_dir=parsed_args.project,
184
+ sdk_dir=parsed_args.sdk,
185
+ target=parsed_args.target,
186
+ toolchain_path=parsed_args.toolchain_path,
187
+ board=parsed_args.board,
188
+ )
189
+ except Exception:
190
+ return
191
+
192
+ hook_results, scope_parsers = discover_hook_tasks(context)
193
+ for task_name, add_parser, source, help_desc in hook_results:
194
+ if task_name not in _TASK_REGISTRY:
195
+ _register_hook_task(task_name, source=source, add_parser=add_parser, help_desc=help_desc)
196
+ for source, fn in scope_parsers.items():
197
+ if source not in _SCOPE_PARSERS:
198
+ register_scope_parser(source, fn)
199
+
200
+
201
+ def _register_hook_task(task_name: str, source: str = "project", add_parser: Callable[[argparse.ArgumentParser], None] | None = None, help_desc: str | None = None) -> None:
202
+ """将 hook 中的 handle_<task> 封装为标准 handler 并注册。"""
203
+ from constants import PRIORITY_DEFAULT
204
+
205
+ def hook_handler(context, global_args: argparse.Namespace, shared_args: argparse.Namespace, self_args: argparse.Namespace, unknown_args: list[str]) -> int:
206
+ from hooks import dispatch_hook
207
+
208
+ result = dispatch_hook(context, task_name, unknown_args,
209
+ global_args=global_args,
210
+ shared_args=shared_args,
211
+ self_args=self_args)
212
+ if not result.handled:
213
+ print(f"Unknown task: {task_name}", file=sys.stderr)
214
+ return 1
215
+ return result.returncode
216
+
217
+ register_task(task_name, hook_handler, independent=False, source=source, add_parser=add_parser,
218
+ help=help_desc, priority=PRIORITY_DEFAULT)
219
+
220
+
221
+ def _execute_tasks(
222
+ parsed_args: argparse.Namespace,
223
+ segments: list[tuple[str, list[str]]],
224
+ ) -> int:
225
+ """按顺序执行 task 段,每段用 subparser 解析。"""
226
+ from context import build_context_from_options
227
+
228
+ # 先逐段解析(--help 在此阶段触发 SystemExit,避免不必要的 context 构建)
229
+ # 使用 bare parser(不含全局 option),确保 self_args 仅含 task 专属值
230
+ task_parser = argparse.ArgumentParser(add_help=False)
231
+ task_subs = task_parser.add_subparsers(dest="task")
232
+ for name, (_, _, add_parser, _, _, _) in _TASK_REGISTRY.items():
233
+ sub = task_subs.add_parser(name, add_help=True)
234
+ if add_parser:
235
+ add_parser(sub)
236
+ else:
237
+ sub.epilog = "No task-specific options."
238
+
239
+ parsed_segments: list[tuple[str, argparse.Namespace, argparse.Namespace, list[str]]] = []
240
+ shared_hook_args: list[str] = []
241
+
242
+ # 合并所有 task_args,一次性解析 shared_args——确保 shared option 不限制位置
243
+ combined_args: list[str] = []
244
+ for _, task_args in segments:
245
+ combined_args.extend(task_args)
246
+ shared_args = argparse.Namespace()
247
+ if _SCOPE_PARSERS:
248
+ scope_parser = argparse.ArgumentParser(add_help=False)
249
+ for source in sorted(_SCOPE_PARSERS.keys(), key=lambda s: _source_rank(s)):
250
+ _SCOPE_PARSERS[source](scope_parser)
251
+ shared_args, _ = scope_parser.parse_known_args(combined_args)
252
+
253
+ for name, task_args in segments:
254
+ parsed_task, hook_args = task_parser.parse_known_args([name] + task_args)
255
+ parsed_segments.append((name, shared_args, parsed_task, hook_args))
256
+ for a in hook_args:
257
+ if a not in shared_hook_args:
258
+ shared_hook_args.append(a)
259
+
260
+ # 为第一个非 independent task 构建 context
261
+ ctx: object = None
262
+ for name, _shared_args, _parsed_task, _hook_args in parsed_segments:
263
+ _, independent, _add_parser, _help, _source, _priority = _TASK_REGISTRY[name]
264
+ if not independent:
265
+ ctx = build_context_from_options(
266
+ cwd=parsed_args.project,
267
+ project_dir=parsed_args.project,
268
+ sdk_dir=parsed_args.sdk,
269
+ target=parsed_args.target,
270
+ toolchain_path=parsed_args.toolchain_path,
271
+ board=parsed_args.board,
272
+ )
273
+ break
274
+
275
+ for name, shared_args, parsed_task, _hook_args in parsed_segments:
276
+ handler, _independent, _add_parser, _help, _source, _priority = _TASK_REGISTRY[name]
277
+
278
+ # 平台专属 task 校验:source=platform/xxx 时检查当前平台匹配
279
+ if _source.startswith("platform/") and ctx is not None:
280
+ expected_platform = _source.split("/", 1)[1]
281
+ if getattr(ctx, "platform_name", "") != expected_platform:
282
+ print(f"[xt cli] Task '{name}' is only available on platform "
283
+ f"'{expected_platform}', current platform is '{ctx.platform_name}'")
284
+ continue
285
+
286
+ rc = handler(ctx, parsed_args, shared_args, parsed_task, shared_hook_args)
287
+ if rc != 0:
288
+ return rc
289
+ return 0
290
+
291
+
292
+ def _main(argv: Sequence[str] | None = None) -> int:
293
+ args = list(sys.argv[1:] if argv is None else argv)
294
+
295
+ # 预扫描:-h/--help 出现在任何 task 名之前 → 全局 help
296
+ task_idx = next((i for i, a in enumerate(args) if a in _TASK_REGISTRY), len(args))
297
+ if any(a in {"-h", "--help"} for a in args[:task_idx]):
298
+ parser = build_parser()
299
+ parsed_args, _ = parser.parse_known_args(args)
300
+ _discover_hook_tasks(parsed_args)
301
+ build_help_parser().parse_args(args) # raises SystemExit
302
+ return 0
303
+
304
+ # 第一步:全局 parse_known_args(add_help=False,--help 流入 task 段)
305
+ parser = build_parser()
306
+ parsed_args, unknown_args = parser.parse_known_args(args)
307
+
308
+ # 扫 hook 注册自定义 task
309
+ _discover_hook_tasks(parsed_args)
310
+
311
+ segments = _split_task_segments(unknown_args)
312
+
313
+ if not segments:
314
+ if unknown_args:
315
+ if set(unknown_args) & {"-h", "--help"}:
316
+ build_help_parser().parse_args(args)
317
+ return 0
318
+ parser.error(f"unrecognized arguments: {' '.join(unknown_args)}")
319
+ parser.print_help()
320
+ return 0
321
+
322
+ segments = _apply_independent(segments)
323
+ segments = _sort_tasks(segments)
324
+
325
+ return _execute_tasks(parsed_args, segments)
326
+
327
+ def main(argv: Sequence[str] | None = None) -> int:
328
+ try:
329
+ sys.stdout.reconfigure(line_buffering=True)
330
+ except Exception:
331
+ pass
332
+ try:
333
+ return _main(argv)
334
+ except XtCliError as e:
335
+ print(f"Error: {e}", file=sys.stderr)
336
+ return 1
337
+ except KeyboardInterrupt:
338
+ print("KeyboardInterrupt")
339
+ return 1
340
+
341
+ # 导入命令模块触发 register_task 注册
342
+ from commands import build_cmd, clean_cmd, config_cmd, deps_cmd, fullclean_cmd # noqa: F401, E402
commands/build_cmd.py ADDED
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from cli import register_task
6
+ from hooks import dispatch_hook
7
+ from models import BuildContext
8
+ from output import print_step
9
+ from xmake import build_command, run_xmake
10
+
11
+
12
+ def handle_build_command(context: BuildContext, global_args: argparse.Namespace, shared_args: argparse.Namespace, self_args: argparse.Namespace, unknown_args: list[str]) -> int:
13
+ """处理 build 命令。"""
14
+ # --sync-deps: 在依赖检查之前 checkout 仓库
15
+ sync_deps = getattr(global_args, "sync_deps", False)
16
+ if sync_deps:
17
+ from dependencies import sync_dependencies
18
+ dirty_allowed = getattr(global_args, "dirty", False)
19
+ sync_dependencies(context, dirty_allowed=dirty_allowed)
20
+
21
+ # 在 dispatch_hook 之前检查依赖版本(防止 hook 绕过)
22
+ from dependencies import check_dependencies, _print_warnings_box
23
+ dirty_allowed = getattr(global_args, "dirty", False)
24
+ strict = getattr(global_args, "strict", False)
25
+ passed, warnings = check_dependencies(context, dirty_allowed=dirty_allowed, strict=strict)
26
+ if not passed:
27
+ return 1
28
+
29
+ hook_result = dispatch_hook(context, "build", unknown_args, global_args=global_args, shared_args=shared_args, self_args=self_args)
30
+ if hook_result.handled:
31
+ return hook_result.returncode
32
+
33
+ from hooks import run_lifecycle_hook
34
+
35
+ before_ret = run_lifecycle_hook(context, "before_build", unknown_args, global_args, shared_args, self_args)
36
+ if before_ret != 0:
37
+ return before_ret
38
+
39
+ print_step("build")
40
+ if warnings:
41
+ _print_warnings_box(warnings)
42
+ result = run_xmake(context, build_command(context))
43
+
44
+ run_lifecycle_hook(context, "after_build", unknown_args, global_args, shared_args, self_args)
45
+
46
+ # 在 after_build hook 之后更新版本追踪(反映最终仓库状态)
47
+ if result.returncode == 0:
48
+ from dependencies import update_dependencies, _is_track_enabled
49
+ no_track = getattr(global_args, "no_track", False)
50
+ if not no_track and _is_track_enabled(context):
51
+ update_dependencies(context)
52
+
53
+ return result.returncode
54
+
55
+
56
+ def add_args_build(parser: argparse.ArgumentParser) -> None:
57
+ """注册 build 专属 option 说明。"""
58
+ parser.epilog = (
59
+ "Global flags affecting build:\n"
60
+ " --dirty Allow building with dirty repositories (always warns)\n"
61
+ " --no-track Skip updating xt_vers.jsonc for this build\n"
62
+ " --strict Enforce all constraints as error, abort on dirty\n"
63
+ " --sync-deps Checkout repos to match xt_deps.jsonc versions before build"
64
+ )
65
+
66
+
67
+ register_task("build", handle_build_command, independent=False, add_parser=add_args_build, help="Build the project", priority=20)
commands/clean_cmd.py ADDED
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from cli import register_task
6
+ from hooks import dispatch_hook
7
+ from models import BuildContext
8
+ from output import print_step
9
+ from xmake import build_clean_command, run_xmake
10
+
11
+
12
+ def handle_clean_command(context: BuildContext, global_args: argparse.Namespace, shared_args: argparse.Namespace, self_args: argparse.Namespace, unknown_args: list[str]) -> int:
13
+ """处理 clean 命令。"""
14
+ hook_result = dispatch_hook(context, "clean", unknown_args, global_args=global_args, shared_args=shared_args, self_args=self_args)
15
+ if hook_result.handled:
16
+ return hook_result.returncode
17
+
18
+ from hooks import run_lifecycle_hook
19
+
20
+ before_ret = run_lifecycle_hook(context, "before_clean", unknown_args, global_args, shared_args, self_args)
21
+ if before_ret != 0:
22
+ return before_ret
23
+
24
+ print_step("clean")
25
+ result = run_xmake(context, build_clean_command(context))
26
+
27
+ run_lifecycle_hook(context, "after_clean", unknown_args, global_args, shared_args, self_args)
28
+ return result.returncode
29
+
30
+
31
+ register_task("clean", handle_clean_command, independent=False, help="Clean build artifacts", priority=10)
commands/config_cmd.py ADDED
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+
6
+ from cli import register_task
7
+ from config import get_config_value, reset_config_key, set_config_value
8
+ from config import _resolve_config_store
9
+ from constants import PRIORITY_INDEPENDENT
10
+ from errors import CliUsageError
11
+ from models import BuildContext
12
+
13
+
14
+ def add_args_config(parser: argparse.ArgumentParser) -> None:
15
+ """注册 config 专属 option。"""
16
+ action_group = parser.add_mutually_exclusive_group()
17
+ action_group.add_argument("--get", dest="get_key", metavar="<key>", help="Get config value")
18
+ action_group.add_argument("--reset", dest="reset_key", metavar="<key>", help="Reset config value")
19
+ action_group.add_argument("--show", dest="show_config", action="store_true", help="Show all config values")
20
+ parser.add_argument("--platform", metavar="<platform>", help="Select platform scope")
21
+ parser.add_argument("--local", action="store_true", help="Use local config file")
22
+ parser.add_argument(
23
+ "--debug", dest="config_debug", metavar="<0|1>", choices=("0", "1"),
24
+ help="Set debug flag, 0 disables and 1 enables debug output",
25
+ )
26
+ parser.add_argument(
27
+ "--deps-on-version-fail", dest="deps_on_version_fail",
28
+ metavar="<warn|error|ignore>", choices=("warn", "error", "ignore"),
29
+ help="Default behavior for version constraint mismatch",
30
+ )
31
+ parser.add_argument(
32
+ "--deps-on-target-fail", dest="deps_on_target_fail",
33
+ metavar="<warn|error|ignore>", choices=("warn", "error", "ignore"),
34
+ help="Default behavior for target constraint mismatch",
35
+ )
36
+ parser.add_argument(
37
+ "--deps-track", dest="deps_track",
38
+ metavar="<true|false>", choices=("true", "false"),
39
+ help="Enable or disable xt_vers.jsonc tracking",
40
+ )
41
+
42
+
43
+ def handle_config_command(context: BuildContext, global_args: argparse.Namespace, shared_args: argparse.Namespace, self_args: argparse.Namespace, unknown_args: list[str]) -> int:
44
+ """处理 config 命令。"""
45
+ cwd = Path.cwd()
46
+
47
+ if self_args.get_key is not None:
48
+ value, source = get_config_value(
49
+ key=self_args.get_key,
50
+ platform=self_args.platform,
51
+ use_local=self_args.local,
52
+ cwd=cwd,
53
+ )
54
+ print(f"{value} (from {source})")
55
+ return 0
56
+
57
+ if self_args.reset_key is not None:
58
+ source = reset_config_key(
59
+ key=self_args.reset_key,
60
+ platform=self_args.platform,
61
+ use_local=self_args.local,
62
+ cwd=cwd,
63
+ )
64
+ print(f"{self_args.reset_key} reset (from {source})")
65
+ return 0
66
+
67
+ set_key, set_value = _resolve_set_action(global_args, self_args)
68
+ if set_key is not None and set_value is not None:
69
+ source = set_config_value(
70
+ key=set_key,
71
+ value=set_value,
72
+ platform=self_args.platform if self_args else None,
73
+ use_local=self_args.local if self_args else False,
74
+ cwd=cwd,
75
+ )
76
+ stored_value, _ = get_config_value(
77
+ key=set_key,
78
+ platform=self_args.platform if self_args else None,
79
+ use_local=self_args.local if self_args else False,
80
+ cwd=cwd,
81
+ )
82
+ print(f"{set_key} set to {stored_value} (from {source})")
83
+ return 0
84
+
85
+ _show_all_config(cwd)
86
+ return 0
87
+
88
+
89
+ def _resolve_set_action(parsed_global: argparse.Namespace, parsed_task) -> tuple[str | None, str | None]:
90
+ """解析设置动作。"""
91
+ set_actions = [
92
+ ("sdk", parsed_global.sdk),
93
+ ("target", parsed_global.target),
94
+ ("board", parsed_global.board),
95
+ ("debug", getattr(parsed_task, "config_debug", None) if parsed_task else None),
96
+ ("toolchain-path", parsed_global.toolchain_path),
97
+ ("deps.on_version_fail", getattr(parsed_task, "deps_on_version_fail", None) if parsed_task else None),
98
+ ("deps.on_target_fail", getattr(parsed_task, "deps_on_target_fail", None) if parsed_task else None),
99
+ ("deps.track", getattr(parsed_task, "deps_track", None) if parsed_task else None),
100
+ ]
101
+ selected_actions = [(key, value) for key, value in set_actions if value is not None]
102
+ if len(selected_actions) > 1:
103
+ raise CliUsageError("Only one config set action is allowed")
104
+ if not selected_actions:
105
+ return None, None
106
+ return selected_actions[0]
107
+
108
+
109
+ def _show_all_config(cwd: Path) -> None:
110
+ """展示所有配置(分区:local / global / defaults)。"""
111
+ from config import _LOCAL_CONFIG_FILENAME
112
+
113
+ local_store = _resolve_config_store(use_local=True, cwd=cwd, home=None)
114
+ global_store = _resolve_config_store(use_local=False, cwd=cwd, home=None)
115
+
116
+ local_data = local_store.load_or_default()
117
+ global_data = global_store.load_or_default()
118
+
119
+ local_path = cwd / _LOCAL_CONFIG_FILENAME
120
+ global_path = global_store.path
121
+
122
+ print(f"--- Local config ({local_path}) ---")
123
+ if local_data:
124
+ _print_config_section(local_data)
125
+ else:
126
+ print("(empty)")
127
+
128
+ print(f"\n--- Global config ({global_path}) ---")
129
+ if global_data:
130
+ _print_config_section(global_data)
131
+ else:
132
+ print("(empty)")
133
+
134
+ _print_defaults_section(local_data, global_data)
135
+
136
+
137
+ def _print_config_section(data: dict) -> None:
138
+ """输出配置段内容。"""
139
+ for key in sorted(data):
140
+ if key == "config_version":
141
+ continue
142
+ value = data[key]
143
+ if key == "toolchains" and isinstance(value, dict):
144
+ for platform in sorted(value):
145
+ print(f"toolchain-path ({platform}) = {value[platform]}")
146
+ elif key == "deps_configs" and isinstance(value, dict):
147
+ for sub_key in sorted(value):
148
+ print(f"deps.{sub_key} = {value[sub_key]}")
149
+ else:
150
+ print(f"{key} = {value}")
151
+
152
+
153
+ _REGISTERED_KEYS = [
154
+ "sdk", "target", "board", "debug", "toolchain-path",
155
+ "deps.on_version_fail", "deps.on_target_fail", "deps.track",
156
+ ]
157
+
158
+ _DEFAULT_VALUES = {
159
+ "target": "windows/simulator",
160
+ "board": "(not set)",
161
+ "debug": 0,
162
+ "deps.on_version_fail": "warn",
163
+ "deps.on_target_fail": "warn",
164
+ "deps.track": False,
165
+ }
166
+
167
+
168
+ def _print_defaults_section(local_data: dict, global_data: dict) -> None:
169
+ """输出默认值段(仅显示本地和全局中均未设置的键)。"""
170
+ lines: list[str] = []
171
+ for key in _REGISTERED_KEYS:
172
+ if key == "toolchain-path":
173
+ continue
174
+ value = _resolve_best_value(key, local_data, global_data)
175
+ if value is not None:
176
+ continue
177
+ default = _DEFAULT_VALUES.get(key, "(not set)")
178
+ lines.append(f"{key} = {default}")
179
+
180
+ if lines:
181
+ print("\n--- Defaults ---")
182
+ for line in lines:
183
+ print(line)
184
+
185
+
186
+ def _resolve_best_value(key: str, local_data: dict, global_data: dict):
187
+ """从 local/global 数据中解析最佳值。"""
188
+ if key.startswith("deps."):
189
+ sub_key = key[5:]
190
+ for data in (local_data, global_data):
191
+ deps = data.get("deps_configs")
192
+ if isinstance(deps, dict) and sub_key in deps:
193
+ return deps[sub_key]
194
+ return None
195
+
196
+ for data in (local_data, global_data):
197
+ if key in data:
198
+ return data[key]
199
+ return None
200
+
201
+
202
+ register_task("config", handle_config_command, independent=True, add_parser=add_args_config, help="Configure settings", priority=PRIORITY_INDEPENDENT)