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 +5 -0
- __main__.py +7 -0
- cli.py +342 -0
- commands/build_cmd.py +67 -0
- commands/clean_cmd.py +31 -0
- commands/config_cmd.py +202 -0
- commands/deps_cmd.py +128 -0
- commands/fullclean_cmd.py +32 -0
- config.py +356 -0
- constants.py +6 -0
- context.py +238 -0
- dependencies.py +1109 -0
- errors.py +29 -0
- hooks.py +317 -0
- models.py +107 -0
- output.py +16 -0
- paths.py +61 -0
- project.py +28 -0
- xmake.py +92 -0
- xt_cli-0.2.1.dist-info/METADATA +125 -0
- xt_cli-0.2.1.dist-info/RECORD +26 -0
- xt_cli-0.2.1.dist-info/WHEEL +5 -0
- xt_cli-0.2.1.dist-info/entry_points.txt +2 -0
- xt_cli-0.2.1.dist-info/licenses/LICENSE +202 -0
- xt_cli-0.2.1.dist-info/top_level.txt +16 -0
- xt_cli.py +10 -0
context.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from config import ConfigStore
|
|
8
|
+
from errors import ConfigError, ProjectError
|
|
9
|
+
from models import BuildContext, ConfigSource, ResolvedValue
|
|
10
|
+
from paths import build_global_config_path, normalize_path, resolve_platform_name, resolve_target_path
|
|
11
|
+
from project import has_xmake_project, has_xt_main_target
|
|
12
|
+
|
|
13
|
+
_DEFAULT_TARGET = "windows/simulator"
|
|
14
|
+
_LOCAL_CONFIG_FILENAME = "xt_conf.jsonc"
|
|
15
|
+
_SDK_MARKER_RELATIVE_PATH = Path(".xt-sdk-version")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_context(
|
|
19
|
+
project_dir: str | Path,
|
|
20
|
+
sdk_dir: str | Path,
|
|
21
|
+
target: str,
|
|
22
|
+
*,
|
|
23
|
+
board: str = "",
|
|
24
|
+
toolchain_path: str | Path | None = None,
|
|
25
|
+
debug_enabled: bool = False,
|
|
26
|
+
resolved_values: dict[str, ResolvedValue] | None = None,
|
|
27
|
+
) -> BuildContext:
|
|
28
|
+
"""构造基础构建上下文。"""
|
|
29
|
+
normalized_project_dir = normalize_path(project_dir)
|
|
30
|
+
normalized_sdk_dir = normalize_path(sdk_dir)
|
|
31
|
+
_validate_project_dir(normalized_project_dir)
|
|
32
|
+
_validate_sdk_dir(normalized_sdk_dir)
|
|
33
|
+
normalized_toolchain_path = normalize_path(toolchain_path) if toolchain_path is not None else None
|
|
34
|
+
resolved_target_path = resolve_target_path(
|
|
35
|
+
target=target,
|
|
36
|
+
project_dir=normalized_project_dir,
|
|
37
|
+
sdk_dir=normalized_sdk_dir,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return BuildContext(
|
|
41
|
+
project_dir=normalized_project_dir,
|
|
42
|
+
sdk_dir=normalized_sdk_dir,
|
|
43
|
+
target=target,
|
|
44
|
+
target_path=resolved_target_path,
|
|
45
|
+
platform_name=resolve_platform_name(target),
|
|
46
|
+
board=board,
|
|
47
|
+
toolchain_path=normalized_toolchain_path,
|
|
48
|
+
debug_enabled=debug_enabled,
|
|
49
|
+
resolved_values=resolved_values or {},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_context_from_options(
|
|
54
|
+
*,
|
|
55
|
+
cwd: str | Path | None = None,
|
|
56
|
+
project_dir: str | Path | None = None,
|
|
57
|
+
sdk_dir: str | Path | None = None,
|
|
58
|
+
target: str | None = None,
|
|
59
|
+
board: str | None = None,
|
|
60
|
+
toolchain_path: str | Path | None = None,
|
|
61
|
+
home: str | Path | None = None,
|
|
62
|
+
) -> BuildContext:
|
|
63
|
+
"""按最小优先级规则构造命令运行上下文。"""
|
|
64
|
+
resolved_cwd = normalize_path(cwd) if cwd is not None else Path.cwd()
|
|
65
|
+
resolved_project_dir = normalize_path(project_dir) if project_dir is not None else resolved_cwd
|
|
66
|
+
|
|
67
|
+
local_config = _load_config_if_exists(resolved_project_dir / _LOCAL_CONFIG_FILENAME)
|
|
68
|
+
global_config = _load_config_if_exists(build_global_config_path(home))
|
|
69
|
+
global_base_dir = normalize_path(home) if home is not None else Path.home()
|
|
70
|
+
|
|
71
|
+
resolved_sdk = _resolve_value(
|
|
72
|
+
sdk_dir,
|
|
73
|
+
local_config,
|
|
74
|
+
global_config,
|
|
75
|
+
"sdk",
|
|
76
|
+
local_base_dir=resolved_project_dir,
|
|
77
|
+
global_base_dir=global_base_dir,
|
|
78
|
+
normalize_as_path=True,
|
|
79
|
+
)
|
|
80
|
+
resolved_target = _resolve_value(target, local_config, global_config, "target", default=_DEFAULT_TARGET)
|
|
81
|
+
resolved_debug = _resolve_value(
|
|
82
|
+
None,
|
|
83
|
+
local_config,
|
|
84
|
+
global_config,
|
|
85
|
+
"debug",
|
|
86
|
+
default=0,
|
|
87
|
+
allow_int=True,
|
|
88
|
+
)
|
|
89
|
+
resolved_platform = resolve_platform_name(resolved_target.value)
|
|
90
|
+
resolved_toolchain = _resolve_toolchain_path(
|
|
91
|
+
cli_value=toolchain_path,
|
|
92
|
+
local_config=local_config,
|
|
93
|
+
global_config=global_config,
|
|
94
|
+
platform_name=resolved_platform,
|
|
95
|
+
local_base_dir=resolved_project_dir,
|
|
96
|
+
global_base_dir=global_base_dir,
|
|
97
|
+
)
|
|
98
|
+
resolved_board = _resolve_value(board, local_config, global_config, "board", default="")
|
|
99
|
+
|
|
100
|
+
return build_context(
|
|
101
|
+
project_dir=resolved_project_dir,
|
|
102
|
+
sdk_dir=resolved_sdk.value,
|
|
103
|
+
target=resolved_target.value,
|
|
104
|
+
board=str(resolved_board.value),
|
|
105
|
+
toolchain_path=resolved_toolchain.value,
|
|
106
|
+
debug_enabled=bool(resolved_debug.value),
|
|
107
|
+
resolved_values={
|
|
108
|
+
"sdk": resolved_sdk,
|
|
109
|
+
"target": resolved_target,
|
|
110
|
+
"board": resolved_board,
|
|
111
|
+
"toolchain_path": resolved_toolchain,
|
|
112
|
+
"debug": resolved_debug,
|
|
113
|
+
"project": ResolvedValue(
|
|
114
|
+
value=resolved_project_dir,
|
|
115
|
+
source=ConfigSource.CLI if project_dir is not None else ConfigSource.DERIVED,
|
|
116
|
+
),
|
|
117
|
+
"target_path": ResolvedValue(
|
|
118
|
+
value=resolve_target_path(
|
|
119
|
+
target=resolved_target.value,
|
|
120
|
+
project_dir=resolved_project_dir,
|
|
121
|
+
sdk_dir=normalize_path(resolved_sdk.value),
|
|
122
|
+
),
|
|
123
|
+
source=ConfigSource.DERIVED,
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _load_config_if_exists(path: Path) -> dict[str, Any]:
|
|
130
|
+
"""存在时加载配置文件。"""
|
|
131
|
+
if not path.is_file():
|
|
132
|
+
return {}
|
|
133
|
+
return ConfigStore(path).load()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _normalize_config_path_value(value: str | Path, base_dir: Path | None) -> str:
|
|
137
|
+
"""将配置中的路径值规范化。"""
|
|
138
|
+
normalized_value = normalize_path(value)
|
|
139
|
+
if base_dir is not None and not normalized_value.is_absolute():
|
|
140
|
+
return os.fspath((base_dir / normalized_value).resolve())
|
|
141
|
+
return os.fspath(normalized_value)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _resolve_value(
|
|
145
|
+
cli_value: str | Path | None,
|
|
146
|
+
local_config: dict[str, Any],
|
|
147
|
+
global_config: dict[str, Any],
|
|
148
|
+
key: str,
|
|
149
|
+
*,
|
|
150
|
+
default: str | int | None = None,
|
|
151
|
+
local_base_dir: Path | None = None,
|
|
152
|
+
global_base_dir: Path | None = None,
|
|
153
|
+
normalize_as_path: bool = False,
|
|
154
|
+
allow_int: bool = False,
|
|
155
|
+
) -> ResolvedValue:
|
|
156
|
+
"""按优先级解析配置值。"""
|
|
157
|
+
if cli_value is not None:
|
|
158
|
+
value = _normalize_config_path_value(cli_value, None) if normalize_as_path else os.fspath(cli_value)
|
|
159
|
+
return ResolvedValue(value=value, source=ConfigSource.CLI)
|
|
160
|
+
|
|
161
|
+
local_value = local_config.get(key)
|
|
162
|
+
if isinstance(local_value, (str, Path)):
|
|
163
|
+
value = _normalize_config_path_value(local_value, local_base_dir) if normalize_as_path else os.fspath(local_value)
|
|
164
|
+
return ResolvedValue(value=value, source=ConfigSource.LOCAL, source_path=str(local_base_dir / _LOCAL_CONFIG_FILENAME) if local_base_dir is not None else None)
|
|
165
|
+
if allow_int and isinstance(local_value, int):
|
|
166
|
+
return ResolvedValue(value=local_value, source=ConfigSource.LOCAL, source_path=str(local_base_dir / _LOCAL_CONFIG_FILENAME) if local_base_dir is not None else None)
|
|
167
|
+
|
|
168
|
+
global_value = global_config.get(key)
|
|
169
|
+
if isinstance(global_value, (str, Path)):
|
|
170
|
+
value = _normalize_config_path_value(global_value, global_base_dir) if normalize_as_path else os.fspath(global_value)
|
|
171
|
+
return ResolvedValue(value=value, source=ConfigSource.GLOBAL, source_path=str(build_global_config_path(global_base_dir)))
|
|
172
|
+
if allow_int and isinstance(global_value, int):
|
|
173
|
+
return ResolvedValue(value=global_value, source=ConfigSource.GLOBAL, source_path=str(build_global_config_path(global_base_dir)))
|
|
174
|
+
|
|
175
|
+
if default is not None:
|
|
176
|
+
return ResolvedValue(value=default, source=ConfigSource.DEFAULT)
|
|
177
|
+
|
|
178
|
+
raise ConfigError(f"Missing required context value: {key}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _resolve_toolchain_path(
|
|
182
|
+
*,
|
|
183
|
+
cli_value: str | Path | None,
|
|
184
|
+
local_config: dict[str, Any],
|
|
185
|
+
global_config: dict[str, Any],
|
|
186
|
+
platform_name: str,
|
|
187
|
+
local_base_dir: Path,
|
|
188
|
+
global_base_dir: Path,
|
|
189
|
+
) -> ResolvedValue:
|
|
190
|
+
"""按优先级解析 toolchain 路径。"""
|
|
191
|
+
if cli_value is not None:
|
|
192
|
+
return ResolvedValue(value=_normalize_config_path_value(cli_value, None), source=ConfigSource.CLI)
|
|
193
|
+
|
|
194
|
+
local_toolchains = local_config.get("toolchains")
|
|
195
|
+
if isinstance(local_toolchains, dict):
|
|
196
|
+
local_value = local_toolchains.get(platform_name)
|
|
197
|
+
if isinstance(local_value, (str, Path)):
|
|
198
|
+
return ResolvedValue(
|
|
199
|
+
value=_normalize_config_path_value(local_value, local_base_dir),
|
|
200
|
+
source=ConfigSource.LOCAL,
|
|
201
|
+
source_path=str(local_base_dir / _LOCAL_CONFIG_FILENAME),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
global_toolchains = global_config.get("toolchains")
|
|
205
|
+
if isinstance(global_toolchains, dict):
|
|
206
|
+
global_value = global_toolchains.get(platform_name)
|
|
207
|
+
if isinstance(global_value, (str, Path)):
|
|
208
|
+
return ResolvedValue(
|
|
209
|
+
value=_normalize_config_path_value(global_value, global_base_dir),
|
|
210
|
+
source=ConfigSource.GLOBAL,
|
|
211
|
+
source_path=str(build_global_config_path(global_base_dir)),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return ResolvedValue(value=None, source=ConfigSource.DEFAULT)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _validate_project_dir(project_dir: Path) -> None:
|
|
218
|
+
"""校验工程目录结构。"""
|
|
219
|
+
if not has_xmake_project(project_dir):
|
|
220
|
+
raise ProjectError(
|
|
221
|
+
f"Project directory does not contain xmake.lua: {project_dir}"
|
|
222
|
+
)
|
|
223
|
+
if not has_xt_main_target(project_dir):
|
|
224
|
+
raise ProjectError(
|
|
225
|
+
"Project xmake.lua does not contain 'xt_main': "
|
|
226
|
+
f"{project_dir / 'xmake.lua'}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _validate_sdk_dir(sdk_dir: Path) -> None:
|
|
232
|
+
"""校验 sdk 目录结构。"""
|
|
233
|
+
sdk_marker = sdk_dir / _SDK_MARKER_RELATIVE_PATH
|
|
234
|
+
if sdk_marker.is_file():
|
|
235
|
+
return
|
|
236
|
+
raise ConfigError(
|
|
237
|
+
f"Invalid sdk directory: {sdk_dir}. Missing required file: .xt-sdk-version"
|
|
238
|
+
)
|