ixt-cli 0.8.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.
- ixt/__init__.py +8 -0
- ixt/__main__.py +8 -0
- ixt/backends/__init__.py +1 -0
- ixt/backends/binary.py +935 -0
- ixt/backends/binary_resolver.py +307 -0
- ixt/backends/node.py +490 -0
- ixt/backends/python.py +234 -0
- ixt/cli/__init__.py +31 -0
- ixt/cli/argparse_completion.py +557 -0
- ixt/cli/cmd_apply.py +404 -0
- ixt/cli/cmd_cache.py +86 -0
- ixt/cli/cmd_config.py +295 -0
- ixt/cli/cmd_info.py +116 -0
- ixt/cli/cmd_install.py +508 -0
- ixt/cli/cmd_misc.py +261 -0
- ixt/cli/cmd_registry.py +35 -0
- ixt/cli/cmd_upgrade.py +336 -0
- ixt/cli/commands.py +70 -0
- ixt/cli/parser.py +555 -0
- ixt/cli/render.py +85 -0
- ixt/config/__init__.py +5 -0
- ixt/config/asset_index.py +305 -0
- ixt/config/asset_pattern_cache.py +87 -0
- ixt/config/env_policy.py +340 -0
- ixt/config/flags.py +29 -0
- ixt/config/fs_policy.py +17 -0
- ixt/config/heuristics.py +465 -0
- ixt/config/models.py +176 -0
- ixt/config/registry.py +145 -0
- ixt/config/settings.py +173 -0
- ixt/config/setup_toml.py +179 -0
- ixt/config/toml.py +416 -0
- ixt/core/__init__.py +16 -0
- ixt/core/apply.py +564 -0
- ixt/core/apply_actions.py +106 -0
- ixt/core/backend.py +187 -0
- ixt/core/bootstrap.py +410 -0
- ixt/core/cache.py +332 -0
- ixt/core/discover.py +150 -0
- ixt/core/doctor.py +591 -0
- ixt/core/export.py +419 -0
- ixt/core/expose.py +350 -0
- ixt/core/extract.py +261 -0
- ixt/core/hooks.py +182 -0
- ixt/core/identity.py +148 -0
- ixt/core/inject.py +143 -0
- ixt/core/install.py +509 -0
- ixt/core/install_local.py +229 -0
- ixt/core/locks.py +54 -0
- ixt/core/pathlink.py +86 -0
- ixt/core/resolution_stats.py +191 -0
- ixt/core/resolve.py +150 -0
- ixt/core/resolve_cache.py +185 -0
- ixt/core/runtimes.py +192 -0
- ixt/core/save.py +237 -0
- ixt/core/setup_completions.py +11 -0
- ixt/core/setup_path.py +368 -0
- ixt/core/upgrade.py +596 -0
- ixt/data/__init__.py +10 -0
- ixt/data/asset_index.json +574 -0
- ixt/data/heuristics.toml +98 -0
- ixt/data/registry.toml +71 -0
- ixt/libs/__init__.py +3 -0
- ixt/libs/constants.py +4 -0
- ixt/libs/fmt.py +108 -0
- ixt/libs/logger.py +109 -0
- ixt/libs/output.py +25 -0
- ixt/libs/req_spec.py +115 -0
- ixt/libs/semver.py +149 -0
- ixt/libs/shell.py +126 -0
- ixt/libs/style.py +238 -0
- ixt/net/__init__.py +1 -0
- ixt/net/github_api.py +158 -0
- ixt/net/gitlab_api.py +149 -0
- ixt/net/http.py +194 -0
- ixt/net/npm.py +24 -0
- ixt/net/pypi.py +26 -0
- ixt/net/source.py +163 -0
- ixt/platform/__init__.py +131 -0
- ixt/platform/win.py +68 -0
- ixt_cli-0.8.0.dist-info/METADATA +294 -0
- ixt_cli-0.8.0.dist-info/RECORD +84 -0
- ixt_cli-0.8.0.dist-info/WHEEL +4 -0
- ixt_cli-0.8.0.dist-info/entry_points.txt +2 -0
ixt/core/export.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""Generate ixt.toml from installed tools, preserving user install intent.
|
|
2
|
+
|
|
3
|
+
Unlike the removed ``freeze`` command (which always force-pinned ``==X.Y.Z``),
|
|
4
|
+
``export`` rebuilds each tool entry from ``ixt.json``, keeping the version
|
|
5
|
+
constraint as the user originally wrote it at install time (range, pin, or no
|
|
6
|
+
constraint). Extra fields (``asset_pattern`` for binary, ``node_shim`` and
|
|
7
|
+
``runtime`` for node) are also preserved.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from ixt.config.asset_index import canonical_github_spec, platform_key
|
|
18
|
+
from ixt.config.models import ToolRecord
|
|
19
|
+
from ixt.config.settings import Settings, get_settings
|
|
20
|
+
from ixt.config.toml import IxtConfig, ToolSpec, serialize_config
|
|
21
|
+
from ixt.core.backend import BackendType
|
|
22
|
+
from ixt.core.install import ToolNotInstalledError
|
|
23
|
+
from ixt.core.save import tool_entry_key, tool_install_spec
|
|
24
|
+
from ixt.net.source import parse_spec
|
|
25
|
+
from ixt.platform import OS, Arch
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ToolNotInstalledError",
|
|
29
|
+
"export_asset_index",
|
|
30
|
+
"export_asset_index_json",
|
|
31
|
+
"export_config",
|
|
32
|
+
"export_toml",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def export_toml(
|
|
37
|
+
names: list[str] | None = None,
|
|
38
|
+
*,
|
|
39
|
+
settings: Settings | None = None,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Return a TOML string for the requested tools (all if *names* is None)."""
|
|
42
|
+
config = export_config(names, settings=settings)
|
|
43
|
+
return serialize_config(config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def export_config(
|
|
47
|
+
names: list[str] | None = None,
|
|
48
|
+
*,
|
|
49
|
+
settings: Settings | None = None,
|
|
50
|
+
) -> IxtConfig:
|
|
51
|
+
"""Build an IxtConfig from installed tools' metadata.
|
|
52
|
+
|
|
53
|
+
When *names* is given, only those tools are exported. Unknown names raise
|
|
54
|
+
ToolNotInstalledError. When *names* is None or empty, every installed tool
|
|
55
|
+
is exported.
|
|
56
|
+
"""
|
|
57
|
+
settings = settings or get_settings()
|
|
58
|
+
|
|
59
|
+
records_by_name: dict[str, ToolRecord] = {}
|
|
60
|
+
for meta_file in settings.iter_installed_metadata():
|
|
61
|
+
record = ToolRecord.load_json(meta_file)
|
|
62
|
+
records_by_name[record.name] = record
|
|
63
|
+
|
|
64
|
+
if names:
|
|
65
|
+
selected: dict[str, ToolRecord] = {}
|
|
66
|
+
for name in names:
|
|
67
|
+
from ixt.core.install import resolve_tool_arg
|
|
68
|
+
|
|
69
|
+
tool_id = resolve_tool_arg(name, settings=settings)
|
|
70
|
+
if tool_id not in records_by_name:
|
|
71
|
+
raise ToolNotInstalledError(name)
|
|
72
|
+
selected[tool_id] = records_by_name[tool_id]
|
|
73
|
+
else:
|
|
74
|
+
selected = records_by_name
|
|
75
|
+
|
|
76
|
+
tools: dict[str, ToolSpec] = {}
|
|
77
|
+
for record in selected.values():
|
|
78
|
+
# Local installs (--from <path>) are not reproducible cross-machine.
|
|
79
|
+
if record.source == "local":
|
|
80
|
+
continue
|
|
81
|
+
spec = _record_to_spec(record, settings)
|
|
82
|
+
tools[spec.name] = spec
|
|
83
|
+
|
|
84
|
+
return IxtConfig(tools=tools)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def export_asset_index(
|
|
88
|
+
*,
|
|
89
|
+
settings: Settings | None = None,
|
|
90
|
+
from_registry: Path | None = None,
|
|
91
|
+
all_platforms: bool = False,
|
|
92
|
+
warn: Callable[[str], None] | None = None,
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
"""Return portable binary-resolution metadata from the local cache.
|
|
95
|
+
|
|
96
|
+
This exports metadata, not installed environments and not the user's
|
|
97
|
+
``ixt.toml`` intent. It intentionally omits local cache paths.
|
|
98
|
+
"""
|
|
99
|
+
settings = settings or get_settings()
|
|
100
|
+
if from_registry is not None:
|
|
101
|
+
return _asset_index_from_registry(
|
|
102
|
+
from_registry,
|
|
103
|
+
all_platforms=all_platforms,
|
|
104
|
+
warn=warn,
|
|
105
|
+
)
|
|
106
|
+
if all_platforms:
|
|
107
|
+
_warn(
|
|
108
|
+
warn,
|
|
109
|
+
"--all-platforms is ignored without --from-registry; "
|
|
110
|
+
"cache-based asset-index export only knows the current platform",
|
|
111
|
+
)
|
|
112
|
+
return _asset_index_from_cache(settings)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def export_asset_index_json(
|
|
116
|
+
*,
|
|
117
|
+
settings: Settings | None = None,
|
|
118
|
+
from_registry: Path | None = None,
|
|
119
|
+
all_platforms: bool = False,
|
|
120
|
+
warn: Callable[[str], None] | None = None,
|
|
121
|
+
) -> str:
|
|
122
|
+
"""Return ``asset_index.json`` content for the local metadata cache."""
|
|
123
|
+
return (
|
|
124
|
+
json.dumps(
|
|
125
|
+
export_asset_index(
|
|
126
|
+
settings=settings,
|
|
127
|
+
from_registry=from_registry,
|
|
128
|
+
all_platforms=all_platforms,
|
|
129
|
+
warn=warn,
|
|
130
|
+
),
|
|
131
|
+
indent=2,
|
|
132
|
+
sort_keys=True,
|
|
133
|
+
)
|
|
134
|
+
+ "\n"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# -- Internal helpers --------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _record_to_spec(record: ToolRecord, settings: Settings | None = None) -> ToolSpec:
|
|
142
|
+
"""Convert a ToolRecord into a ToolSpec faithful to user install intent."""
|
|
143
|
+
settings = settings or get_settings()
|
|
144
|
+
name = _config_key_for_record(record)
|
|
145
|
+
version = _version_constraint(record)
|
|
146
|
+
asset_pattern = _read_asset_pattern(record) if record.backend == "binary" else None
|
|
147
|
+
runtime = _read_node_runtime(record) if record.backend == "node" else None
|
|
148
|
+
install = None
|
|
149
|
+
slot = _slot_for_record(record, name, settings)
|
|
150
|
+
if slot is not None:
|
|
151
|
+
name = slot
|
|
152
|
+
install = tool_install_spec(record.spec, BackendType(record.backend))
|
|
153
|
+
|
|
154
|
+
return ToolSpec(
|
|
155
|
+
name=name,
|
|
156
|
+
install=install,
|
|
157
|
+
version=version,
|
|
158
|
+
expose=list(record.expose_rules),
|
|
159
|
+
inject=list(record.injected),
|
|
160
|
+
node_shim=record.node_shim,
|
|
161
|
+
runtime=runtime,
|
|
162
|
+
asset_pattern=asset_pattern,
|
|
163
|
+
env_base=record.env_base,
|
|
164
|
+
env_allow=list(record.env_allow),
|
|
165
|
+
env_deny=dict(record.env_deny),
|
|
166
|
+
fs_base=record.fs_base,
|
|
167
|
+
fs_ro=list(record.fs_ro),
|
|
168
|
+
fs_rw=list(record.fs_rw),
|
|
169
|
+
fs_scratch=list(record.fs_scratch),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _config_key_for_record(record: ToolRecord) -> str:
|
|
174
|
+
"""Return the canonical install-spec key to write in ``ixt.toml``."""
|
|
175
|
+
return tool_entry_key(record.name, record.spec, BackendType(record.backend))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _slot_for_record(record: ToolRecord, install_spec: str, settings: Settings) -> str | None:
|
|
179
|
+
from ixt.core.identity import slot_from_id
|
|
180
|
+
from ixt.core.install import plan_install
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
base_id = plan_install(install_spec, settings=settings).tool_name
|
|
184
|
+
except Exception:
|
|
185
|
+
return None
|
|
186
|
+
return slot_from_id(record.name, base_id)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _version_constraint(record: ToolRecord) -> str | None:
|
|
190
|
+
"""Extract the version constraint the user wrote at install time.
|
|
191
|
+
|
|
192
|
+
Returns None when the user didn't pin anything. The shape depends on the
|
|
193
|
+
backend: PEP 508 for python/node (``==0.5.0``, ``>=1.0``), plain-tag-with-``==``
|
|
194
|
+
for binary (``==14.1.0``).
|
|
195
|
+
"""
|
|
196
|
+
spec = record.spec
|
|
197
|
+
if record.backend == "python":
|
|
198
|
+
from ixt.libs.req_spec import parse_requirement
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
return parse_requirement(spec).version_constraint
|
|
202
|
+
except ValueError:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
if record.backend == "node":
|
|
206
|
+
from ixt.backends.node import parse_npm_spec
|
|
207
|
+
|
|
208
|
+
_name, version = parse_npm_spec(spec)
|
|
209
|
+
if not version:
|
|
210
|
+
return None
|
|
211
|
+
# npm version can already carry an operator (``^5.0``, ``~1.2``). Keep
|
|
212
|
+
# ixt.toml's PEP-508-ish convention: bare semver → prefix ``==``.
|
|
213
|
+
if version[0] in "^~><=*":
|
|
214
|
+
return version
|
|
215
|
+
return f"=={version}"
|
|
216
|
+
|
|
217
|
+
if record.backend == "binary":
|
|
218
|
+
from ixt.net.source import parse_spec as parse_repo_spec
|
|
219
|
+
|
|
220
|
+
repo = parse_repo_spec(spec)
|
|
221
|
+
if repo is None or not repo.version:
|
|
222
|
+
return None
|
|
223
|
+
return f"=={repo.version}"
|
|
224
|
+
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _read_asset_pattern(record: ToolRecord) -> str | None:
|
|
229
|
+
"""Return the ``asset_pattern`` to export, if any.
|
|
230
|
+
|
|
231
|
+
Only forced patterns (``--asset-pattern`` CLI or ``ixt.setup.toml`` author)
|
|
232
|
+
are exported — patterns derived from the resolved asset name bake in
|
|
233
|
+
OS/arch and would break cross-machine ``apply``.
|
|
234
|
+
"""
|
|
235
|
+
if not record.asset_pattern_forced:
|
|
236
|
+
return None
|
|
237
|
+
return record.asset_pattern if record.asset_pattern else None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _read_node_runtime(record: ToolRecord) -> str | None:
|
|
241
|
+
"""Return the non-default node runtime to export, if any."""
|
|
242
|
+
from ixt.backends.node import read_metadata
|
|
243
|
+
|
|
244
|
+
data = read_metadata(Path(record.env_dir))
|
|
245
|
+
if data and data.get("runtime") == "node":
|
|
246
|
+
return "node"
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _load_asset_patterns(settings: Settings) -> dict[str, str]:
|
|
251
|
+
path = settings.metadata_dir / "asset_patterns.json"
|
|
252
|
+
try:
|
|
253
|
+
with path.open(encoding="utf-8") as f:
|
|
254
|
+
data = json.load(f)
|
|
255
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
256
|
+
return {}
|
|
257
|
+
if not isinstance(data, dict):
|
|
258
|
+
return {}
|
|
259
|
+
return {k: v for k, v in data.items() if isinstance(k, str) and isinstance(v, str)}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _load_downloads(settings: Settings) -> list[dict[str, Any]]:
|
|
263
|
+
from ixt.core.cache import downloads_index_path
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
with downloads_index_path(settings=settings).open(encoding="utf-8") as f:
|
|
267
|
+
data = json.load(f)
|
|
268
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
269
|
+
return []
|
|
270
|
+
if not isinstance(data, dict):
|
|
271
|
+
return []
|
|
272
|
+
entries = data.get("entries", [])
|
|
273
|
+
if not isinstance(entries, list):
|
|
274
|
+
return []
|
|
275
|
+
return [
|
|
276
|
+
entry
|
|
277
|
+
for entry in entries
|
|
278
|
+
if isinstance(entry, dict) and entry.get("kind") == "binary-asset"
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _asset_index_from_cache(settings: Settings) -> dict[str, Any]:
|
|
283
|
+
result: dict[str, dict[str, Any]] = {}
|
|
284
|
+
key = platform_key()
|
|
285
|
+
patterns = _load_asset_patterns(settings)
|
|
286
|
+
for owner_repo, pattern in sorted(patterns.items()):
|
|
287
|
+
owner, sep, repo = owner_repo.partition("/")
|
|
288
|
+
if not sep or not owner or not repo:
|
|
289
|
+
continue
|
|
290
|
+
entry = _asset_index_entry(result, owner=owner, repo=repo)
|
|
291
|
+
entry.setdefault("patterns", {})[key] = pattern
|
|
292
|
+
|
|
293
|
+
downloads = sorted(
|
|
294
|
+
_load_downloads(settings),
|
|
295
|
+
key=lambda item: _str(item.get("downloaded_at")),
|
|
296
|
+
)
|
|
297
|
+
for download in downloads:
|
|
298
|
+
if download.get("platform") != "github" or download.get("host") != "github.com":
|
|
299
|
+
continue
|
|
300
|
+
owner = _str(download.get("owner"))
|
|
301
|
+
repo = _str(download.get("repo"))
|
|
302
|
+
tag = _str(download.get("tag"))
|
|
303
|
+
asset = _str(download.get("asset"))
|
|
304
|
+
if not owner or not repo or not tag or not asset:
|
|
305
|
+
continue
|
|
306
|
+
entry = _asset_index_entry(result, owner=owner, repo=repo)
|
|
307
|
+
entry.setdefault("assets", {})[key] = {
|
|
308
|
+
"tag": tag,
|
|
309
|
+
"asset": asset,
|
|
310
|
+
"url": _str(download.get("url")),
|
|
311
|
+
}
|
|
312
|
+
return _prune_empty_asset_index(result)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _asset_index_from_registry(
|
|
316
|
+
path: Path,
|
|
317
|
+
*,
|
|
318
|
+
all_platforms: bool,
|
|
319
|
+
warn: Callable[[str], None] | None,
|
|
320
|
+
) -> dict[str, Any]:
|
|
321
|
+
from ixt.backends.binary import derive_asset_pattern, make_source
|
|
322
|
+
from ixt.config.heuristics import select_asset
|
|
323
|
+
from ixt.config.toml import _load_toml
|
|
324
|
+
from ixt.core.backend import strip_protocol
|
|
325
|
+
|
|
326
|
+
data = _load_toml(path)
|
|
327
|
+
tools = data.get("tools", {})
|
|
328
|
+
if not isinstance(tools, dict):
|
|
329
|
+
return {}
|
|
330
|
+
|
|
331
|
+
result: dict[str, dict[str, Any]] = {}
|
|
332
|
+
platforms = _export_platforms(all_platforms=all_platforms)
|
|
333
|
+
for name, raw_spec in sorted(tools.items()):
|
|
334
|
+
if not isinstance(name, str) or not isinstance(raw_spec, str):
|
|
335
|
+
continue
|
|
336
|
+
try:
|
|
337
|
+
_forced, clean_spec = strip_protocol(raw_spec)
|
|
338
|
+
repo_spec = parse_spec(clean_spec)
|
|
339
|
+
if repo_spec is None or repo_spec.platform != "github":
|
|
340
|
+
continue
|
|
341
|
+
canonical = canonical_github_spec(repo_spec)
|
|
342
|
+
if canonical is None:
|
|
343
|
+
continue
|
|
344
|
+
release = make_source(repo_spec).get_latest_release(repo_spec.owner, repo_spec.repo)
|
|
345
|
+
entry = result.setdefault(canonical, {"names": [], "patterns": {}})
|
|
346
|
+
if name not in entry["names"]:
|
|
347
|
+
entry["names"].append(name)
|
|
348
|
+
for os_value, arch_value in platforms:
|
|
349
|
+
key = platform_key(os_override=os_value, arch_override=arch_value)
|
|
350
|
+
asset = select_asset(
|
|
351
|
+
release.assets,
|
|
352
|
+
repo_spec.repo,
|
|
353
|
+
release.tag.removeprefix("v"),
|
|
354
|
+
os_override=os_value,
|
|
355
|
+
arch_override=arch_value,
|
|
356
|
+
)
|
|
357
|
+
if asset is None:
|
|
358
|
+
_warn(warn, f"{name}: no asset for {key} in {release.tag}")
|
|
359
|
+
continue
|
|
360
|
+
pattern = derive_asset_pattern(asset.name, release.tag)
|
|
361
|
+
if pattern is None:
|
|
362
|
+
_warn(warn, f"{name}: cannot derive pattern for {asset.name}")
|
|
363
|
+
continue
|
|
364
|
+
entry["patterns"][key] = pattern
|
|
365
|
+
except Exception as exc:
|
|
366
|
+
_warn(warn, f"{name}: {type(exc).__name__}: {exc}")
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
return _prune_empty_asset_index(result)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _asset_index_entry(
|
|
373
|
+
result: dict[str, dict[str, Any]],
|
|
374
|
+
*,
|
|
375
|
+
owner: str,
|
|
376
|
+
repo: str,
|
|
377
|
+
) -> dict[str, Any]:
|
|
378
|
+
canonical = f"@gh:{owner}/{repo}"
|
|
379
|
+
return result.setdefault(canonical, {})
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _prune_empty_asset_index(data: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
|
383
|
+
result: dict[str, Any] = {}
|
|
384
|
+
for canonical, entry in sorted(data.items()):
|
|
385
|
+
cleaned: dict[str, Any] = {}
|
|
386
|
+
names = entry.get("names")
|
|
387
|
+
patterns = entry.get("patterns")
|
|
388
|
+
assets = entry.get("assets")
|
|
389
|
+
if isinstance(names, list) and names:
|
|
390
|
+
cleaned["names"] = sorted({name for name in names if isinstance(name, str)})
|
|
391
|
+
if isinstance(patterns, dict) and patterns:
|
|
392
|
+
cleaned["patterns"] = dict(sorted(patterns.items()))
|
|
393
|
+
if isinstance(assets, dict) and assets:
|
|
394
|
+
cleaned["assets"] = dict(sorted(assets.items()))
|
|
395
|
+
if "patterns" in cleaned or "assets" in cleaned:
|
|
396
|
+
result[canonical] = cleaned
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _export_platforms(*, all_platforms: bool) -> list[tuple[OS, Arch]]:
|
|
401
|
+
if not all_platforms:
|
|
402
|
+
from ixt.platform import get_arch, get_os
|
|
403
|
+
|
|
404
|
+
return [(get_os(), get_arch())]
|
|
405
|
+
return [
|
|
406
|
+
(OS.LINUX, Arch.X86_64),
|
|
407
|
+
(OS.LINUX, Arch.ARM64),
|
|
408
|
+
(OS.MACOS, Arch.X86_64),
|
|
409
|
+
(OS.MACOS, Arch.ARM64),
|
|
410
|
+
]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _warn(warn: Callable[[str], None] | None, message: str) -> None:
|
|
414
|
+
if warn is not None:
|
|
415
|
+
warn(message)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _str(value: object) -> str:
|
|
419
|
+
return value if isinstance(value, str) else ""
|