bakar 0.5.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 (58) hide show
  1. bakar/__init__.py +3 -0
  2. bakar/__main__.py +6 -0
  3. bakar/bsp_detect.py +189 -0
  4. bakar/bsp_model.py +260 -0
  5. bakar/cli.py +72 -0
  6. bakar/commands/__init__.py +9 -0
  7. bakar/commands/_app.py +63 -0
  8. bakar/commands/_helpers.py +361 -0
  9. bakar/commands/build.py +371 -0
  10. bakar/commands/clean.py +61 -0
  11. bakar/commands/clean_sstate.py +191 -0
  12. bakar/commands/diff.py +63 -0
  13. bakar/commands/doctor.py +137 -0
  14. bakar/commands/dump.py +87 -0
  15. bakar/commands/for_all.py +111 -0
  16. bakar/commands/gen_kas.py +87 -0
  17. bakar/commands/hashserv.py +136 -0
  18. bakar/commands/layers.py +63 -0
  19. bakar/commands/lock.py +98 -0
  20. bakar/commands/log.py +141 -0
  21. bakar/commands/override.py +117 -0
  22. bakar/commands/prefetch.py +79 -0
  23. bakar/commands/report.py +104 -0
  24. bakar/commands/settings.py +74 -0
  25. bakar/commands/shell.py +150 -0
  26. bakar/commands/stress_parse.py +174 -0
  27. bakar/commands/sync.py +142 -0
  28. bakar/commands/triage.py +100 -0
  29. bakar/config.py +357 -0
  30. bakar/diagnostics.py +1307 -0
  31. bakar/fork_race_signatures.py +58 -0
  32. bakar/hashserv.py +264 -0
  33. bakar/kas.py +406 -0
  34. bakar/layers.py +223 -0
  35. bakar/manifest_diff.py +84 -0
  36. bakar/observability.py +151 -0
  37. bakar/overlays/bakar-tuning-generic.yml +129 -0
  38. bakar/overlays/bakar-tuning-hashequiv.yml +27 -0
  39. bakar/overlays/bakar-tuning-nxp.yml +104 -0
  40. bakar/overlays/bakar-tuning-ti.yml +79 -0
  41. bakar/report.py +148 -0
  42. bakar/steps/__init__.py +1 -0
  43. bakar/steps/bitbake_override.py +544 -0
  44. bakar/steps/kas_build.py +944 -0
  45. bakar/steps/repo.py +76 -0
  46. bakar/steps/run_qemu.py +61 -0
  47. bakar/steps/setup_env.py +45 -0
  48. bakar/steps/stress_parse.py +376 -0
  49. bakar/steps/ti_layertool.py +136 -0
  50. bakar/steps/ti_setup_env.py +64 -0
  51. bakar/triage.py +257 -0
  52. bakar/user_config.py +339 -0
  53. bakar/vendor_config.py +63 -0
  54. bakar/workspace.py +327 -0
  55. bakar-0.5.0.dist-info/METADATA +52 -0
  56. bakar-0.5.0.dist-info/RECORD +58 -0
  57. bakar-0.5.0.dist-info/WHEEL +4 -0
  58. bakar-0.5.0.dist-info/entry_points.txt +3 -0
bakar/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """bakar: practical kas wrapper for Yocto BSP development."""
2
+
3
+ __version__ = "0.5.0"
bakar/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m bakar`."""
2
+
3
+ from bakar.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
bakar/bsp_detect.py ADDED
@@ -0,0 +1,189 @@
1
+ """Inspect a kas YAML and classify the BSP family.
2
+
3
+ Used by the BYO (Form A) path of ``bakar build``: when the user hands
4
+ bakar a kas YAML directly, we cannot rely on the manifest filename
5
+ regex in :func:`bakar.bsp_model.detect_bsp_family`. Instead the
6
+ classifier reads ``machine:`` and ``repos:`` from the YAML and applies
7
+ a small first-match-wins rule set:
8
+
9
+ * machine starts with ``imx`` -> ``nxp``
10
+ * machine starts with ``am`` / ``k3-`` / ``j7-`` -> ``ti``
11
+ * repos contains ``meta-imx`` / ``meta-freescale*`` / ``meta-nxp*`` -> ``nxp``
12
+ * repos contains ``meta-ti-bsp`` / ``meta-ti`` / ``meta-arago`` -> ``ti``
13
+ * parseable YAML with at least ``machine:`` or ``repos:`` -> ``generic``
14
+ * unparseable / empty -> ``unknown``
15
+
16
+ The ``generic`` classification is the BSP-agnostic fallback for kas
17
+ YAMLs that look like real builds but do not target an NXP/TI SoM
18
+ (e.g. qemuarm64 + poky + meta-arm). Callers layer the
19
+ ``bakar-tuning-generic.yml`` overlay - which carries only the
20
+ BSP-agnostic optimizations (ccache, MIRRORS, PREMIRRORS, FETCHCMD_wget,
21
+ PYTHONMALLOC) - onto these YAMLs.
22
+
23
+ The function never raises - I/O on the YAML is wrapped defensively so
24
+ ``bakar build my.yml`` can fail with a single typer.Exit(2) instead of
25
+ a Python traceback.
26
+
27
+ See also :func:`bakar.bsp_model.detect_bsp_family`, which classifies
28
+ by manifest *filename* (no I/O, pure regex). The two classifiers serve
29
+ different entry points: this module handles the BYO-yaml path
30
+ (``bakar build my.yml``); ``detect_bsp_family`` handles the manifest
31
+ path (``bakar build -f imx-*.xml``). Both encode NXP/TI family
32
+ markers; if either set of markers changes, update the other.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ from typing import TYPE_CHECKING, Any, Literal
39
+
40
+ import yaml
41
+
42
+ if TYPE_CHECKING:
43
+ from pathlib import Path
44
+
45
+ # Repo-name substrings that identify each BSP. Order matters within a
46
+ # family: the first matching substring wins. The lists are kept short
47
+ # and explicit so a future BSP addition does not collide silently.
48
+ _NXP_REPO_NAMES: tuple[str, ...] = (
49
+ "meta-imx",
50
+ "meta-freescale",
51
+ "meta-nxp",
52
+ "meta-variscite-bsp-imx",
53
+ "meta-variscite-sdk-imx",
54
+ )
55
+
56
+ _TI_REPO_NAMES: tuple[str, ...] = (
57
+ "meta-ti-bsp",
58
+ "meta-ti-extras",
59
+ "meta-ti",
60
+ "meta-tisdk",
61
+ "meta-arago",
62
+ "meta-variscite-bsp-ti",
63
+ "meta-variscite-sdk-ti",
64
+ )
65
+
66
+
67
+ def _machine_family(machine: str) -> Literal["nxp", "ti", "unknown"]:
68
+ if not machine:
69
+ return "unknown"
70
+ name = machine.lower()
71
+ if name.startswith("imx"):
72
+ return "nxp"
73
+ if name.startswith(("am", "k3-", "j7-", "j72", "j78", "j784")):
74
+ return "ti"
75
+ return "unknown"
76
+
77
+
78
+ def _repos_family(repos: dict[str, Any] | None) -> Literal["nxp", "ti", "unknown"]:
79
+ if not isinstance(repos, dict):
80
+ return "unknown"
81
+ names = set(repos.keys())
82
+ for hit in _NXP_REPO_NAMES:
83
+ if hit in names:
84
+ return "nxp"
85
+ for hit in _TI_REPO_NAMES:
86
+ if hit in names:
87
+ return "ti"
88
+ return "unknown"
89
+
90
+
91
+ def detect_bsp_from_yaml(yaml_path: Path) -> Literal["nxp", "ti", "generic", "unknown"]:
92
+ """Inspect a kas YAML and classify the BSP family.
93
+
94
+ Pure function over a parsed YAML dict. Returns ``"generic"`` for a
95
+ kas YAML that parses cleanly but lacks NXP/TI markers; callers
96
+ use that to layer the BSP-agnostic tuning overlay. Returns
97
+ ``"unknown"`` only for unparseable, empty, or shape-incomplete
98
+ YAMLs - those exit with a typer.Exit(2) and a hint.
99
+ """
100
+ if yaml_path is None or not yaml_path.is_file():
101
+ return "unknown"
102
+ try:
103
+ with yaml_path.open("r", encoding="utf-8") as fh:
104
+ data = yaml.safe_load(fh)
105
+ except OSError, yaml.YAMLError:
106
+ return "unknown"
107
+ if not isinstance(data, dict):
108
+ return "unknown"
109
+
110
+ machine = data.get("machine", "")
111
+ machine_hit = _machine_family(machine if isinstance(machine, str) else "")
112
+ if machine_hit != "unknown":
113
+ return machine_hit
114
+
115
+ repos_hit = _repos_family(data.get("repos"))
116
+ if repos_hit != "unknown":
117
+ return repos_hit
118
+
119
+ # No NXP/TI markers but the YAML has at least a machine string or a
120
+ # repos block - treat as a generic kas build. Reject only YAMLs
121
+ # that lack both anchors (typo or empty file).
122
+ has_machine = isinstance(machine, str) and bool(machine.strip())
123
+ has_repos = isinstance(data.get("repos"), dict) and bool(data["repos"])
124
+ if has_machine or has_repos:
125
+ return "generic"
126
+ return "unknown"
127
+
128
+
129
+ def is_bbsetup_workspace(path: Path) -> bool:
130
+ """Return True if ``path`` is an initialized ``bitbake-setup`` workspace.
131
+
132
+ A directory qualifies only when all of the following hold:
133
+
134
+ * ``<path>/config/config-upstream.json`` exists and parses as JSON
135
+ with top-level ``data`` and ``bitbake-config`` keys.
136
+ * ``<path>/build/init-build-env`` exists (the Yocto environment
137
+ setup script that ``bitbake-setup init`` writes).
138
+
139
+ Detection is structure-based: bitbake-setup workspaces have no
140
+ manifest filename to match against the regex dispatch used for
141
+ NXP/TI families. The function never raises - a missing file,
142
+ unreadable config, or malformed JSON all return False so callers
143
+ can fall through to the next detection path.
144
+ """
145
+ cfg = path / "config" / "config-upstream.json"
146
+ env = path / "build" / "init-build-env"
147
+ if not (cfg.exists() and env.exists()):
148
+ return False
149
+ try:
150
+ data = json.loads(cfg.read_text(encoding="utf-8"))
151
+ except json.JSONDecodeError, OSError:
152
+ return False
153
+ return isinstance(data, dict) and "data" in data and "bitbake-config" in data
154
+
155
+
156
+ def is_meta_avocado_yaml(yaml_path: Path) -> bool:
157
+ """Return True if the YAML lives inside a meta-avocado repository.
158
+
159
+ Walks the resolved path and checks whether any ancestor directory is
160
+ named ``meta-avocado``. This is how bakar detects that a generic kas
161
+ YAML belongs to the Avocado OS build system and needs the
162
+ ``init-build``-style build-directory setup before kas can run.
163
+ """
164
+ try:
165
+ return "meta-avocado" in yaml_path.resolve().parts
166
+ except Exception:
167
+ return False
168
+
169
+
170
+ def detect_kas_workspace(yaml_path: Path) -> Path:
171
+ """Return the effective workspace root for a generic kas YAML.
172
+
173
+ For meta-avocado YAMLs the YAML sits several levels deep inside the
174
+ ``meta-avocado`` repository (e.g. ``sources/meta-avocado/kas/machine/
175
+ qemux86-64.yml``). kas must run from a build directory that is a
176
+ *sibling* of ``meta-avocado/`` (e.g. ``sources/build-qemux86-64/``),
177
+ not from inside the repo. This function walks up from the YAML to
178
+ find the ``meta-avocado`` boundary and returns its parent
179
+ (e.g. ``sources/``) so :func:`bakar.config.BuildConfig.bsp_root`
180
+ can derive the correct build-directory path.
181
+
182
+ For every other generic kas YAML the workspace is simply the YAML's
183
+ parent directory (preserving the existing behavior).
184
+ """
185
+ resolved = yaml_path.resolve()
186
+ for parent in resolved.parents:
187
+ if parent.name == "meta-avocado":
188
+ return parent.parent
189
+ return resolved.parent
bakar/bsp_model.py ADDED
@@ -0,0 +1,260 @@
1
+ """Per-BSP model for the bakar CLI.
2
+
3
+ NXP i.MX and TI Sitara BSP families are supported.
4
+ Each family has its own toolchain (Google ``repo`` + ``var-setup-release.sh``
5
+ for NXP; ``varigit/oe-layersetup`` shell wrapper for TI), its own
6
+ manifest format (``imx-A.B.C-X.Y.Z.xml`` vs ``processor-sdk-...-config_var<N>.txt``),
7
+ its own static tuning overlay (``overlays/bakar-tuning-<bsp>.yml``),
8
+ and its own pre-flight checks.
9
+
10
+ This module exports:
11
+
12
+ * :func:`detect_bsp_family` - classify a manifest filename. Pure regex,
13
+ no I/O. The return value drives both the dispatcher in
14
+ :mod:`bakar.cli` and the ``check_host_tools`` decision. For the
15
+ complementary BYO-yaml classifier (content-based, not filename-based)
16
+ see :func:`bakar.bsp_detect.detect_bsp_from_yaml`.
17
+ * :func:`infer_bsp_branch` - synthesize the
18
+ ``meta-variscite-bsp-ti`` branch suffix from a TI config filename.
19
+ * :class:`BspModel` - dataclass + registry that hold every per-BSP
20
+ knob: defaults, kas template, sync/setup steps, doctor extras.
21
+ * :func:`get_model` - factory returning the dispatched model. Imports
22
+ are lazy so :mod:`bakar.config` (which imports
23
+ :func:`infer_bsp_branch` for TI branch fallback) can keep its
24
+ circular dep-free top-level import.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import re
30
+ from collections.abc import Callable
31
+ from dataclasses import dataclass, replace
32
+ from typing import TYPE_CHECKING, Any, Literal
33
+
34
+ from bakar.vendor_config import load_vendors
35
+
36
+ if TYPE_CHECKING:
37
+ from pathlib import Path
38
+
39
+ from bakar.kas import KasTemplate
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Manifest-shape detection (no I/O on the regex path)
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ # NXP manifest filename: imx-<kernel>-<bsp>.xml
48
+ # Examples: imx-6.6.52-2.2.2.xml, imx-6.12.49-2.2.0.xml
49
+ _NXP_MANIFEST_RE = re.compile(r"^imx-\d+\.\d+\.\d+-\d+\.\d+\.\d+\.xml$")
50
+
51
+ # TI Processor SDK config filename:
52
+ # processor-sdk-<poky>-<flavour>-<sdk>-config_<var>.txt
53
+ # where <poky> is the LTS code name (scarthgap, walnascar, ...),
54
+ # <flavour> is "chromium" / "non-chromium" / etc,
55
+ # <sdk> is a 4-part TI SDK version (11.00.09.04),
56
+ # <var> is "var01" / "var02" / ...
57
+ _TI_PROCESSOR_SDK_RE = re.compile(
58
+ r"^processor-sdk-"
59
+ r"(?P<poky>[A-Za-z]\w*)-"
60
+ r".*?-"
61
+ r"(?P<sdk>\d+\.\d+\.\d+\.\d+)-"
62
+ r"config_(?P<var>var\d+)\.txt$"
63
+ )
64
+
65
+ # TI legacy/Arago manifest filename: arago-<anything>.txt
66
+ # Kept as alternation for forward compatibility with older
67
+ # config naming conventions.
68
+ _TI_ARAGO_RE = re.compile(r"^arago-.*\.txt$")
69
+
70
+ # Layer name string used as a fallback heuristic when a config file is
71
+ # present but does not match either regex.
72
+ _TI_LAYER_PREFIX = "meta-variscite-bsp-ti"
73
+
74
+
75
+ def detect_bsp_family(
76
+ manifest_path: Path | None,
77
+ config_file: Path | None = None,
78
+ ) -> Literal["nxp", "ti", "unknown"]:
79
+ """Detect the BSP family from a manifest filename and optional config.
80
+
81
+ Only the filename (``path.name``) is inspected; the file need not
82
+ exist on disk. Pass ``None`` for either argument to skip that
83
+ check. The function never raises - I/O on ``config_file`` is
84
+ wrapped defensively.
85
+ """
86
+ if manifest_path is not None:
87
+ name = manifest_path.name
88
+ for vendor in load_vendors():
89
+ if re.match(vendor.manifest_regex, name):
90
+ return vendor.family # type: ignore[return-value]
91
+ if _NXP_MANIFEST_RE.match(name):
92
+ return "nxp"
93
+ if _TI_PROCESSOR_SDK_RE.match(name) or _TI_ARAGO_RE.match(name):
94
+ return "ti"
95
+
96
+ if config_file is not None:
97
+ try:
98
+ text = config_file.read_text(encoding="utf-8", errors="replace")
99
+ if _TI_LAYER_PREFIX in text:
100
+ return "ti"
101
+ except OSError:
102
+ pass
103
+
104
+ return "unknown"
105
+
106
+
107
+ def infer_bsp_branch(config_filename: str) -> str:
108
+ """Derive the ``meta-variscite-bsp-ti`` branch from a TI config name.
109
+
110
+ The canonical filename
111
+ ``processor-sdk-scarthgap-chromium-11.00.09.04-config_var01.txt``
112
+ parses to ``(poky=scarthgap, sdk=11.00.09.04, var=var01)`` and
113
+ yields ``scarthgap_11.00.09.04_var01`` - the actual branch name on
114
+ the layer repo.
115
+
116
+ Returns ``"<unknown>"`` for inputs that do not match the regex
117
+ (legacy ``arago-*.txt`` configs go through the legacy versioning
118
+ scheme, which is out of scope for this helper).
119
+ """
120
+ m = _TI_PROCESSOR_SDK_RE.match(config_filename)
121
+ if not m:
122
+ return "<unknown>"
123
+ return f"{m.group('poky')}_{m.group('sdk')}_{m.group('var')}"
124
+
125
+
126
+ # ---------------------------------------------------------------------------
127
+ # BspModel registry
128
+ # ---------------------------------------------------------------------------
129
+
130
+
131
+ # Type aliases. ``Callable[..., None]`` is intentionally permissive so
132
+ # both the NXP sync step (``init_and_sync(cfg, log, *, force_init=...)``)
133
+ # and the TI sync step (``populate(cfg, log, *, force_init=...)``) fit.
134
+ SyncStep = Callable[..., None]
135
+ SetupEnvStep = Callable[..., None]
136
+ DoctorCheck = Callable[..., Any]
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class BspModel:
141
+ """Per-BSP knobs consumed by the dispatch layer.
142
+
143
+ Every field that varies between NXP and TI lives here. The CLI's
144
+ ``_dispatch_bsp`` returns one of two singleton instances; downstream
145
+ code reads the fields instead of switching on ``cfg.bsp_family``.
146
+ """
147
+
148
+ family: Literal["nxp", "ti"]
149
+ workspace_subdir: str # "nxp" or "ti"
150
+ kas_yaml_filename: str # "kas-nxp.yml" or "kas-ti.yml"
151
+ tuning_overlay_filename: str # "bakar-tuning-nxp.yml" or "bakar-tuning-ti.yml"
152
+ manifest_kind: Literal["repo-xml", "oe-layertool-config"]
153
+ default_machine: str
154
+ default_distro: str
155
+ default_image: str
156
+ default_manifest: str
157
+ default_branch: str
158
+ required_host_tools: tuple[str, ...]
159
+ sync_step: SyncStep
160
+ setup_env_step: SetupEnvStep
161
+ kas_template: KasTemplate
162
+ doctor_extras: tuple[DoctorCheck, ...]
163
+
164
+
165
+ def get_model(family: Literal["nxp", "ti"]) -> BspModel:
166
+ """Return the BspModel singleton for ``family``.
167
+
168
+ Imports are intentionally lazy. ``bsp_model`` is imported from
169
+ ``config.py`` (``infer_bsp_branch``) and from ``cli.py``; deferring
170
+ the heavy imports until ``get_model`` is actually called keeps the
171
+ import graph clean and lets unit tests construct dummy models
172
+ without dragging in diagnostics or steps.
173
+ """
174
+ # Lazy: avoid pulling diagnostics/steps/kas into every consumer of
175
+ # detect_bsp_family or infer_bsp_branch.
176
+ from bakar import config as cfg_mod
177
+ from bakar.diagnostics import (
178
+ check_forks_linux_imx,
179
+ check_forks_ti_linux_kernel,
180
+ check_forks_ti_u_boot,
181
+ check_git_object_cache,
182
+ check_manifest_consistency,
183
+ check_ti_layertool_config_consistency,
184
+ check_ti_layertool_present,
185
+ )
186
+ from bakar.kas import NXP_KAS_TEMPLATE, TI_KAS_TEMPLATE
187
+ from bakar.steps import repo as step_repo
188
+ from bakar.steps import setup_env as step_setup
189
+ from bakar.steps import ti_layertool as step_ti_layertool
190
+ from bakar.steps import ti_setup_env as step_ti_setup
191
+
192
+ if family == "nxp":
193
+ model = BspModel(
194
+ family="nxp",
195
+ workspace_subdir="nxp",
196
+ kas_yaml_filename="kas-nxp.yml",
197
+ tuning_overlay_filename="bakar-tuning-nxp.yml",
198
+ manifest_kind="repo-xml",
199
+ default_machine=cfg_mod.DEFAULT_NXP_MACHINE,
200
+ default_distro=cfg_mod.DEFAULT_NXP_DISTRO,
201
+ default_image=cfg_mod.DEFAULT_NXP_IMAGE,
202
+ default_manifest=cfg_mod.DEFAULT_NXP_MANIFEST,
203
+ default_branch=cfg_mod.DEFAULT_NXP_REPO_BRANCH,
204
+ required_host_tools=("repo", "kas-container", "docker", "python3"),
205
+ sync_step=step_repo.init_and_sync,
206
+ setup_env_step=step_setup.run,
207
+ kas_template=NXP_KAS_TEMPLATE,
208
+ doctor_extras=(
209
+ check_forks_linux_imx,
210
+ check_manifest_consistency,
211
+ check_git_object_cache,
212
+ ),
213
+ )
214
+ elif family == "ti":
215
+ model = BspModel(
216
+ family="ti",
217
+ workspace_subdir="ti",
218
+ kas_yaml_filename="kas-ti.yml",
219
+ tuning_overlay_filename="bakar-tuning-ti.yml",
220
+ manifest_kind="oe-layertool-config",
221
+ default_machine=cfg_mod.DEFAULT_TI_MACHINE,
222
+ default_distro=cfg_mod.DEFAULT_TI_DISTRO,
223
+ default_image=cfg_mod.DEFAULT_TI_IMAGE,
224
+ default_manifest=cfg_mod.DEFAULT_TI_MANIFEST,
225
+ default_branch=cfg_mod.DEFAULT_TI_REPO_BRANCH,
226
+ required_host_tools=("git", "kas-container", "docker", "python3"),
227
+ sync_step=step_ti_layertool.populate,
228
+ setup_env_step=step_ti_setup.run,
229
+ kas_template=TI_KAS_TEMPLATE,
230
+ doctor_extras=(
231
+ check_ti_layertool_present,
232
+ check_ti_layertool_config_consistency,
233
+ check_forks_ti_linux_kernel,
234
+ check_forks_ti_u_boot,
235
+ ),
236
+ )
237
+ else:
238
+ raise ValueError(f"Unknown BSP family: {family!r}")
239
+
240
+ # Apply optional VendorEntry overrides for the first matching vendor.
241
+ for entry in load_vendors():
242
+ if entry.family == family:
243
+ overrides: dict[str, Any] = {}
244
+ if entry.default_machine is not None:
245
+ overrides["default_machine"] = entry.default_machine
246
+ if entry.default_distro is not None:
247
+ overrides["default_distro"] = entry.default_distro
248
+ if entry.default_image is not None:
249
+ overrides["default_image"] = entry.default_image
250
+ if entry.default_manifest is not None:
251
+ overrides["default_manifest"] = entry.default_manifest
252
+ if entry.default_branch is not None:
253
+ overrides["default_branch"] = entry.default_branch
254
+ if entry.tuning_overlay is not None:
255
+ overrides["tuning_overlay_filename"] = entry.tuning_overlay
256
+ if overrides:
257
+ model = replace(model, **overrides)
258
+ break
259
+
260
+ return model
bakar/cli.py ADDED
@@ -0,0 +1,72 @@
1
+ """bakar entry point - imports all command modules to register @app.command() handlers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import typer
8
+
9
+ # Typer >= 0.26 vendored Click as ``typer._click``; the exceptions raised
10
+ # inside Typer's parser are ``typer._click.exceptions.*``, not the external
11
+ # ``click.exceptions.*``. Older Typer still raises from the external module.
12
+ # Catch from both so the entry point works regardless of which Typer ships.
13
+ try:
14
+ from typer._click import exceptions as _click_exc # ty: ignore[unresolved-import]
15
+ except ImportError: # pragma: no cover - typer < 0.26 path
16
+ from click import exceptions as _click_exc
17
+
18
+ import bakar.commands.build # noqa: F401
19
+ import bakar.commands.clean # noqa: F401
20
+ import bakar.commands.clean_sstate # noqa: F401
21
+ import bakar.commands.diff # noqa: F401
22
+ import bakar.commands.doctor # noqa: F401
23
+ import bakar.commands.dump # noqa: F401
24
+ import bakar.commands.for_all # noqa: F401
25
+ import bakar.commands.gen_kas # noqa: F401
26
+ import bakar.commands.hashserv # noqa: F401
27
+ import bakar.commands.layers # noqa: F401
28
+ import bakar.commands.lock # noqa: F401
29
+ import bakar.commands.log # noqa: F401
30
+ import bakar.commands.override # noqa: F401
31
+ import bakar.commands.prefetch # noqa: F401
32
+ import bakar.commands.report # noqa: F401
33
+ import bakar.commands.settings # noqa: F401
34
+ import bakar.commands.shell # noqa: F401
35
+ import bakar.commands.stress_parse # noqa: F401
36
+ import bakar.commands.sync # noqa: F401
37
+ import bakar.commands.triage # noqa: F401
38
+ from bakar.commands import app # noqa: F401 - re-exported for pyproject.toml entry point
39
+ from bakar.commands._app import console
40
+
41
+ __all__ = ["app", "main"]
42
+
43
+
44
+ def main() -> int:
45
+ """Run the bakar CLI with plain (non-Rich-panel) error output."""
46
+ try:
47
+ # standalone_mode=False prevents Click from calling sys.exit AND prevents
48
+ # Typer's rich_utils from rendering UsageError/BadParameter inside a Panel.
49
+ return app(standalone_mode=False) or 0
50
+ except _click_exc.UsageError as exc:
51
+ # Captures NoSuchOption, MissingParameter, BadParameter, and bare UsageError.
52
+ ctx = exc.ctx
53
+ if ctx is not None:
54
+ console.print(ctx.get_usage())
55
+ console.print(f"Try '{ctx.command_path} --help' for help.")
56
+ console.print(f"Error: {exc.format_message()}")
57
+ return exc.exit_code if exc.exit_code is not None else 2
58
+ except _click_exc.ClickException as exc:
59
+ # Non-usage Click errors (e.g. FileError, BadOptionUsage, custom ClickException).
60
+ console.print(f"Error: {exc.format_message()}")
61
+ return exc.exit_code if exc.exit_code is not None else 1
62
+ except _click_exc.Abort:
63
+ # SIGINT during a prompt; Click convention is exit 1 with no traceback.
64
+ console.print("Aborted.")
65
+ return 1
66
+ except (_click_exc.Exit, typer.Exit) as exc:
67
+ # typer.Exit (used everywhere in our commands) -> the carried exit code.
68
+ return int(exc.exit_code) if getattr(exc, "exit_code", 0) is not None else 0
69
+
70
+
71
+ if __name__ == "__main__":
72
+ sys.exit(main())
@@ -0,0 +1,9 @@
1
+ """bakar command sub-package.
2
+
3
+ Importing this package registers all subcommands on ``app``. Use
4
+ ``from bakar.commands import app`` to get the fully-wired Typer app.
5
+ """
6
+
7
+ from bakar.commands._app import app, console
8
+
9
+ __all__ = ["app", "console"]
bakar/commands/_app.py ADDED
@@ -0,0 +1,63 @@
1
+ """Typer app, Rich console, and startup state for all bakar subcommands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Annotated
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from bakar import __version__
11
+ from bakar.user_config import load_user_config
12
+ from bakar.vendor_config import load_vendors
13
+
14
+ if TYPE_CHECKING:
15
+ from bakar.user_config import UserConfig
16
+
17
+ app = typer.Typer(
18
+ help="BSP orchestrator (NXP i.MX + TI Sitara built-in).",
19
+ no_args_is_help=True,
20
+ add_completion=False,
21
+ pretty_exceptions_enable=False,
22
+ )
23
+ console = Console(stderr=True)
24
+
25
+ _VENDORS: list | None = None
26
+ _USER_CONFIG: UserConfig | None = None
27
+
28
+
29
+ def _get_vendors() -> list:
30
+ global _VENDORS
31
+ if _VENDORS is None:
32
+ try:
33
+ _VENDORS = load_vendors()
34
+ except ValueError as exc:
35
+ console.print(f"[red]Invalid vendors config:[/] {exc}")
36
+ raise typer.Exit(code=2) from exc
37
+ return _VENDORS
38
+
39
+
40
+ def _load_user_config_safe() -> UserConfig:
41
+ try:
42
+ return load_user_config()
43
+ except ValueError as exc:
44
+ console.print(f"[red]Invalid bakar config:[/] {exc}")
45
+ raise typer.Exit(code=2) from exc
46
+
47
+
48
+ def _version(value: bool) -> None:
49
+ if value:
50
+ console.print(f"bakar {__version__}")
51
+ raise typer.Exit()
52
+
53
+
54
+ @app.callback()
55
+ def _main(
56
+ version: Annotated[
57
+ bool,
58
+ typer.Option("--version", callback=_version, is_eager=True, help="Show version"),
59
+ ] = False,
60
+ ) -> None:
61
+ global _USER_CONFIG
62
+ _USER_CONFIG = _load_user_config_safe()
63
+ _get_vendors()