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
13 changes: 13 additions & 0 deletions dotbot/cli/_fw_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ def write_build_manifest(
for f in files:
entries[f.name] = {
"sha256": sha256(f),
"repo": str(Path(repo).resolve()),
"git_sha": sha,
"dirty": dirty,
"build_config": config,
Expand All @@ -453,6 +454,18 @@ def write_build_manifest(
return manifest


def recorded_repo(out_dir: Path) -> Path | None:
"""The source folder a built set's manifest names, or None."""
try:
manifest = json.loads((out_dir / MANIFEST_NAME).read_text())
except (OSError, ValueError):
return None
if not isinstance(manifest, dict) or manifest.get("kind") != "build":
return None
repo = manifest.get("repo")
return Path(repo) if repo else None


def collect(files: Iterable[Path], out_dir: Path) -> list[Path]:
"""Copy `files` flat into `out_dir` under their own names.

Expand Down
28 changes: 27 additions & 1 deletion dotbot/cli/fw.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ def _source_repo(source: str, path: Path | None, explicit: bool) -> Path:
default=False,
help="Print where each artifact would be collected, without building.",
)
@click.option(
"--force",
is_flag=True,
default=False,
help=(
"Build into an --as set that was built from another source folder, "
"which then names this one. Its other images stay as they were built."
),
)
@_verbose_option
@click.pass_context
def build(
Expand All @@ -236,6 +245,7 @@ def build(
set_name,
rebuild,
print_path,
force,
verbose,
):
"""Build firmware from your source folders into the cache.
Expand Down Expand Up @@ -329,6 +339,19 @@ def build(
planned.append((source, src_repo, cfg, target, files, df_apps))

root = artifacts_dir()
for source, src_repo, *_ in planned:
out = fs.set_dir(source, set_name, root)
recorded = fs.recorded_repo(out)
if force or recorded is None or recorded == src_repo.resolve():
continue
raise click.ClickException(
f"The '{set_name}' {source} set was built from {recorded}, not "
f"{src_repo.resolve()}: images from two source folders in one set "
"can disagree, e.g. on the swarmit NSC table.\n"
f" - build from the set's folder: --path {recorded}\n"
" - or build into another set: --as <name>\n"
" - or mix them anyway: --force"
)
if print_path:
for source, _, _, _, files, _ in planned:
out = fs.set_dir(source, set_name, root)
Expand All @@ -340,8 +363,11 @@ def build(
t0 = time.perf_counter()
for source, src_repo, cfg, board, _, selection in planned:
mode = "rebuild" if rebuild or source != APP_SOURCE else "incremental"
origin = "--path" if path is not None else "configured source"
click.echo(
f"Building {_LABELS[source]} for {board} ({cfg}, {mode})...", err=True
f"Building {_LABELS[source]} for {board} ({cfg}, {mode}) from "
f"{src_repo} ({origin})...",
err=True,
)
if source == "swarmit":
files = fs.build_swarmit(
Expand Down
26 changes: 26 additions & 0 deletions dotbot/tests/test_fw_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,3 +958,29 @@ def test_list_survives_a_malformed_manifest(isolated, manifest):
assert result.exit_code == 0, result.output
assert "swarmit-odd" in result.output
assert " netcore-nrf5340-net.hex" in result.output


# --- the set's source folder ----------------------------------------------


def test_build_into_a_set_from_another_folder_is_refused(
isolated, swarmit_repo, fake_build
):
other = isolated / "wt-swarmit"
shutil.copytree(swarmit_repo, other)
assert build("swarmit-sandbox", "--as", "bench").exit_code == 0
result = build("swarmit-sandbox", "--path", str(other), "--as", "bench")
assert result.exit_code != 0
assert str(swarmit_repo.resolve()) in result.output
assert str(other.resolve()) in result.output
assert "--force" in result.output
forced = build("swarmit-sandbox", "--path", str(other), "--as", "bench", "--force")
assert forced.exit_code == 0, forced.output
assert f"from {other}" in forced.output


def test_build_says_which_source_folder_it_builds_from(
isolated, firmware_repo, fake_make
):
result = build("dotbot")
assert f"from {firmware_repo} (configured source)" in result.output
Loading