Keep no-op rebuilds quiet

A -n dry-run probe answers 'no work to do' without the real ninja spawn,
and running with cwd instead of -C drops the 'Entering directory' banner
on real builds too.
This commit is contained in:
J. Nick Koston
2026-08-22 14:14:37 -05:00
parent 0e1c7cbb7e
commit 1d88a5a92b
2 changed files with 51 additions and 6 deletions
+22 -5
View File
@@ -89,16 +89,33 @@ def run_compile(config: ConfigType, verbose: bool) -> int:
if ninja_changed or not (build_dir / "compile_commands.json").is_file():
_write_compile_commands(paths.ninja, build_dir, env)
cmd = [str(paths.ninja), "-C", str(build_dir)]
cmd = [str(paths.ninja)]
if verbose:
cmd.append("-v")
if jobs := config[CONF_ESPHOME].get(CONF_COMPILE_PROCESS_LIMIT):
cmd += ["-j", str(jobs)]
_LOGGER.debug("Running: %s", " ".join(cmd))
rc = subprocess.run(cmd, env=env, check=False, close_fds=False).returncode
if rc != 0:
return rc
# A dry-run probe keeps a no-op rebuild quiet: ninja would only print
# "no work to do". cwd instead of -C also drops the "Entering
# directory" banner on real builds.
probe = subprocess.run(
[str(paths.ninja), "-n"],
cwd=build_dir,
env=env,
capture_output=True,
text=True,
check=False,
close_fds=False,
)
if probe.returncode == 0 and "no work to do" in probe.stdout:
_LOGGER.debug("ninja: nothing to rebuild")
else:
_LOGGER.debug("Running: %s", " ".join(cmd))
rc = subprocess.run(
cmd, cwd=build_dir, env=env, check=False, close_fds=False
).returncode
if rc != 0:
return rc
_print_size_summary(build_dir, paths)
try:
+29 -1
View File
@@ -92,13 +92,41 @@ def test_run_compile_success(tmp_path: Path) -> None:
{CONF_ESPHOME: {CONF_COMPILE_PROCESS_LIMIT: 4}}, verbose=False
)
assert rc == 0
cmd = mock_run.call_args[0][0]
# The -n probe runs first, then the real build (cwd, no -C banner)
ninja_calls = [c for c in mock_run.call_args_list if "ninja" in str(c[0][0][0])]
assert ninja_calls[0][0][0][-1] == "-n"
cmd = ninja_calls[1][0][0]
assert cmd[-2:] == ["-j", "4"]
assert "-C" not in cmd
assert ninja_calls[1][1]["cwd"] is not None
mock_compdb.assert_called_once()
mock_size.assert_called_once()
mock_idedata.assert_called_once()
def test_run_compile_noop_skips_the_build_spawn(tmp_path: Path) -> None:
"""A no-op rebuild stays quiet: the -n probe answers "no work to do"
and the real ninja spawn (and its banner) never happens."""
with (
patch.object(framework, "check_and_install", return_value=_paths(tmp_path)),
patch.object(framework, "get_build_env", return_value={}),
patch("esphome.build_gen.arduino8266.write_project"),
patch.object(
toolchain.subprocess,
"run",
return_value=MagicMock(returncode=0, stdout="ninja: no work to do.\n"),
) as mock_run,
patch.object(toolchain, "_write_compile_commands"),
patch.object(toolchain, "_print_size_summary"),
patch.object(toolchain, "get_idedata"),
):
rc = toolchain.run_compile({CONF_ESPHOME: {}}, verbose=False)
assert rc == 0
ninja_calls = [c for c in mock_run.call_args_list if "ninja" in str(c[0][0][0])]
assert len(ninja_calls) == 1
assert ninja_calls[0][0][0][-1] == "-n"
def test_run_compile_warns_when_idedata_fails(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None: