Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ cd my-app
tan validate
tan build
tan size
tan run --flash
tan run --flash --confirm
```

What those commands do:
Expand All @@ -190,7 +190,15 @@ What those commands do:
4. `validate` checks `board.yaml` and related metadata.
5. `build` plans, materialises, and builds every core slice.
6. `size` reports firmware use against the SoM memory budget.
7. `run --flash` builds and then runs or programs the selected target.
7. `run --flash` builds and then runs or programs the selected target. On a
hardware target (a native_sim/host target always just runs), `--flash`
alone only *previews* the write: every slice comes back `planned`, nothing
reaches the device, and the run exits non-zero naming the remedy. Add
`--confirm` (as above) to actually arm the write, or set
`ALP_FLASH_FORCE=1` in the environment, or `flash_args.confirm: true` in
the manifest -- the same three-way gate `tan flash --confirm` already has.
This is deliberate, not a bug: a fresh checkout must not silently
reprogram an attached module.

Run `tan doctor` if setup or toolchain discovery fails; its `zephyrSdk`
check names the exact `west sdk install` command above too, so it stays
Expand Down Expand Up @@ -224,8 +232,8 @@ move.
| Create a project | `tan init --name my-app` |
| Check a project | `tan validate` |
| Build firmware | `tan build` |
| Build and run or flash | `tan run --flash` |
| Flash an existing build | `tan flash` |
| Build and run or flash | `tan run --flash --confirm` (`--confirm` arms the write on a hardware target; see the quickstart) |
| Flash an existing build | `tan flash --confirm` |
| Inspect firmware size | `tan size` |
| Create an image | `tan image` |
| Remove build output | `tan clean` |
Expand Down
14 changes: 14 additions & 0 deletions changelog.d/799.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
- **`tan size`/`image`/`clean`/`run`/`validate` now print the `sdk.discovery-
divergent` two-checkout warning on the default text channel, not only under
`--format json`.** `Envelope.__init__` appends that warning at the one seam
every command's envelope passes through (`_with_sdk_divergence`), but each of
these five built the `Envelope` only inside `if json_mode:`, after their
text-mode lines had already been assembled from the pre-envelope `issues`
list — so a workspace resolving two different alp-sdk checkouts got the
collision warning from `tan build`/`tan pinmux`/`tan doctor` and silence from
the other five, the exact channel a human reads. `pinmux_cmd.py` was already
correct (builds the envelope once, unconditionally, and renders text from
`envelope.issues`); this applies the same fix to the other five, diffing
each command's own pre-envelope issue list against `envelope.issues` so only
what the seam actually added is rendered, never a duplicate of a warning the
command already prints by hand.
12 changes: 12 additions & 0 deletions changelog.d/809.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- **`tan run --flash` can now arm the flash confirm gate directly, with a new
`--confirm` option threaded into the flash engine as `confirm_flag`.**
Without it, a hardware target (an E1M-AEN801 Flow D target among them) always
came back with every slice `planned`, wrote nothing, and exited 1 with
`flash.nothing-flashed` — whose own remedy names `--confirm` on the command
line, a flag `tan run` rejected outright. The gate itself stays gate-by-
default on purpose (a fresh checkout must not silently reprogram an attached
module); this only gives `run` the same explicit opt-in `tan flash --confirm`
already had, so the remedy the tool prints is one `run` itself can act on.
The README quickstart, walkthrough step, and Common-commands table now show
and explain `--confirm` instead of ending on a command that exits non-zero on
the flagship target.
29 changes: 19 additions & 10 deletions python/tan/commands/clean_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1091,20 +1091,29 @@ def clean(
text=["clean: internal failure"],
)

# Built ONCE, for both formats: `Envelope.__init__` appends the tan-cli#407
# `sdk.discovery-divergent` warning at the shared seam (`_with_sdk_
# divergence`), and `outcome.text` was assembled strictly before any
# `Envelope` existed -- so a seam-appended issue reached `--format json`
# and was silent on the default text channel (tan-cli#799). Diffed
# against `outcome.issues` (by value: `Issue` is a frozen dataclass) so
# only what the seam ADDED is rendered.
envelope = Envelope(
"clean",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
if json_mode:
emit(
Envelope(
"clean",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
)
emit(envelope)
else:
seam_extra = [issue for issue in envelope.issues if issue not in outcome.issues]
# stdout is the envelope channel and carries nothing else, in either
# mode; stderr carries no contract of its own.
for issue in seam_extra:
print(f"{issue.severity}: {issue.message}", file=sys.stderr)
for line in outcome.text:
print(line, file=sys.stderr)
raise typer.Exit(int(outcome.exit_code))
30 changes: 20 additions & 10 deletions python/tan/commands/image_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,19 +674,29 @@ def image(
disclosure.sdk,
)

# Built ONCE, for both formats: `Envelope.__init__` appends the tan-cli#407
# `sdk.discovery-divergent` warning at the shared seam (`_with_sdk_
# divergence`), and `outcome.text` was assembled strictly before any
# `Envelope` existed -- so a seam-appended issue reached `--format json`
# and was silent on the default text channel (tan-cli#799). Diffed
# against `outcome.issues` (by value: `Issue` is a frozen dataclass) so
# only what the seam ADDED is rendered, never a duplicate of a warning
# `outcome.text` already carries via `_sdk_warning_lines` above.
envelope = Envelope(
"image",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
if json_mode:
emit(
Envelope(
"image",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
)
emit(envelope)
else:
seam_extra = [issue for issue in envelope.issues if issue not in outcome.issues]
stream = typer.get_text_stream("stderr")
for issue in seam_extra:
stream.write(f"{issue.severity}: {issue.message}\n")
for line in outcome.text:
stream.write(f"{line}\n")
raise typer.Exit(int(outcome.exit_code))
Expand Down
47 changes: 45 additions & 2 deletions python/tan/commands/run_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ def _run(
flash: bool,
core: str | None,
json_mode: bool,
# Defaulted, not required: several existing unit tests call `_run`
# directly with the pre-tan-cli#809 kwarg set, exercising paths this flag
# never reaches (`BUILD_ONLY`/`MANIFEST_STALE`/`EXECUTE_NATIVE`) or
# stubbing `flash_cmd._run` themselves. `False` preserves the exact
# pre-#809 behaviour for every one of them.
confirm: bool = False,
) -> tuple[ExitCode, dict[str, Any] | None, list[Issue], list[str]]:
"""Everything between the resolved paths and the envelope. Returns
`(exit_code, data, issues, text_lines)`."""
Expand Down Expand Up @@ -317,6 +323,13 @@ def _run(
skip_missing_tools=False,
capture=json_mode,
cwd=build_root,
# tan-cli#809: `run` had no way to arm the flash confirm gate, so a
# hardware target (e.g. E1M-AEN801's Flow D) always landed every
# slice as `planned` and exited 1 with `flash.nothing-flashed` --
# whose own remedy names `--confirm`, a flag `run` rejected. The gate
# itself stays deliberate (see flash_plan.py); this only gives `run`
# the same opt-in `tan flash --confirm` already has.
confirm_flag=confirm,
)
return flash_exit, flash_data, flash_issues, flash_text

Expand All @@ -327,7 +340,10 @@ def run(
"--flash",
help="Program the board after building (hardware targets only). Required "
"opt-in: without it, `run` on a hardware project builds and reports but "
"never flashes. Ignored for a native_sim/host target, which always runs "
"never flashes. Arming the write also needs --confirm (or "
"ALP_FLASH_FORCE=1, or flash_args.confirm: true in the manifest); "
"without it every slice comes back `planned` and the run exits "
"non-zero. Ignored for a native_sim/host target, which always runs "
"the produced binary and never flashes.",
),
core: str = typer.Option(
Expand All @@ -337,6 +353,15 @@ def run(
help="With --flash, flash only the slice with this core_id (forwarded "
"verbatim to the native flash path's --core).",
),
confirm: bool = typer.Option(
False,
"--confirm",
help="With --flash, arm the confirm gate and actually write the device. "
"Without it (and without ALP_FLASH_FORCE=1 or flash_args.confirm: true "
"in the manifest) a hardware target is only previewed -- every slice "
"comes back `planned`, nothing reaches the device, and the run exits "
"non-zero (tan-cli#809). Ignored without --flash.",
),
project: str = typer.Option(
None, "--project", metavar="PATH", help="Project root (defaults to '.')."
),
Expand Down Expand Up @@ -418,6 +443,7 @@ def run(
board_yaml=board_yaml,
flash=flash,
core=core,
confirm=confirm,
json_mode=json_mode,
)
except Exception as err: # noqa: BLE001 -- see build_cmd.build's identical guard
Expand Down Expand Up @@ -452,8 +478,25 @@ def run(
f"{issue.severity}: {issue.message}" for issue in issues[:warning_count]
] + text_lines

# Built ONCE, for both formats: `Envelope.__init__` appends the
# tan-cli#407 `sdk.discovery-divergent` warning at the shared seam
# (`_with_sdk_divergence`), beyond the pin/foreign pair this function
# already prepends by hand above (tan-cli#464). Text mode used to render
# `text_lines`, built before any `Envelope` existed, so a seam-appended
# divergence issue reached `--format json` and was silent on the default
# channel (tan-cli#799) -- diffed against the pre-envelope `issues` list
# (by value: `Issue` is a frozen dataclass) so only what the seam ADDED
# is rendered, never a duplicate of the pin/foreign pair already in
# `text_lines`.
envelope = Envelope("run", project_obj, data, issues, exit_code, sdk=sdk)
seam_extra = [issue for issue in envelope.issues if issue not in issues]
if seam_extra:
text_lines = [
f"{issue.severity}: {issue.message}" for issue in seam_extra
] + text_lines

if json_mode:
emit(Envelope("run", project_obj, data, issues, exit_code, sdk=sdk))
emit(envelope)
else:
for line in text_lines:
print(line, file=sys.stderr)
Expand Down
31 changes: 21 additions & 10 deletions python/tan/commands/size_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,21 +802,32 @@ def size(
disclosure.sdk,
)

# Built ONCE, for both formats: `Envelope.__init__` appends the tan-cli#407
# `sdk.discovery-divergent` warning at the shared seam (`_with_sdk_
# divergence`), and `outcome.text` was assembled strictly before any
# `Envelope` existed -- so a seam-appended issue reached `--format json`
# and was silent on the default text channel (tan-cli#799). Diffed
# against `outcome.issues` (by value: `Issue` is a frozen dataclass) so
# only what the seam ADDED is rendered, never a duplicate of a warning
# `outcome.text` already carries (the pair `_run`/`_error_outcome` render
# via `_sdk_warning_lines` above).
envelope = Envelope(
"size",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
if json_mode:
emit(
Envelope(
"size",
outcome.project,
outcome.data,
outcome.issues,
outcome.exit_code,
sdk=outcome.sdk,
)
)
emit(envelope)
else:
seam_extra = [issue for issue in envelope.issues if issue not in outcome.issues]
# stdout is the envelope channel and carries nothing else, in either
# mode; stderr carries no contract of its own.
stream = typer.get_text_stream("stderr")
for issue in seam_extra:
stream.write(f"{issue.severity}: {issue.message}\n")
for line in outcome.text:
stream.write(f"{line}\n")
raise typer.Exit(int(outcome.exit_code))
Expand Down
Loading
Loading