From a591bee846619a28b41c9f36563e07e75c5e8dcc Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 4 Aug 2026 18:10:52 +0300 Subject: [PATCH] cook: don't stage submodules; refresh staged source in place Two defects in the out-of-tree staging from 221a91cba4/a3d9240d62, both found by actually running it rather than by inspection. 1. Submodules were staged. `git ls-files --error-unmatch` succeeds on a gitlink, so relibc looked like an ordinary tracked tree and was copied wholesale, losing the nested-repo context cargo needs -- crt0, crti, crtn and ld_so all failed to build. Fork submodules are already covered by the stricter REFUSING-TO-BUILD dirty gate, so skipping them loses no protection. 2. `rm -rf` before the copy made the staged path vanish mid-build. Qt bakes its COOKBOOK_SOURCE into installed cmake files (QtSeparateDebugInfo.cmake try_compile()s ${QT_SOURCE_TREE}/config.tests/...), so once that path was the staged tree, rebuilding qtbase pulled it out from under consumers: CMake Error: The source ".../qtbase/.../source-staged/config.tests/ binary_for_strip/CMakeLists.txt" does not ... (qtsvg and qtshadertools). Now rsync -a --delete refreshes in place: same pristine result, but the directory never stops existing. Verified with 4000 probes across a full kirigami rebuild -- 0 observations of it missing. Staging is confirmed working: a 26-package run staged 10 trees, skipped relibc, and left 0 dirty tracked source files (previously 22-29). --- src/cook/cook_build.rs | 48 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs index 2157dc2198..f68d4f2670 100644 --- a/src/cook/cook_build.rs +++ b/src/cook/cook_build.rs @@ -987,7 +987,22 @@ pub fn build( // vendored tree look untracked, so staging silently never fired and // builds kept mutating the git-tracked source. let source_real = source_dir.canonicalize().unwrap_or_else(|_| source_dir.to_path_buf()); - let source_is_tracked = std::env::var_os("REDBEAR_IN_TREE_BUILD").is_none() + + // Never stage a SUBMODULE. `git ls-files --error-unmatch` succeeds + // on a submodule's gitlink, so relibc & co. looked like ordinary + // tracked trees and got copied wholesale -- which broke their cargo + // builds (crt0/crti/crtn/ld_so all failed) because the nested repo + // context did not survive the copy. + // + // Skipping them is principled, not a workaround: each fork + // submodule is already covered by the stricter REFUSING-TO-BUILD + // dirty gate, which forbids uncommitted edits outright. Vendored + // recipe `source/` trees have no such guarantee, and they are the + // ones recipes actually rewrite. + let source_is_submodule = source_real.join(".git").exists(); + + let source_is_tracked = !source_is_submodule + && std::env::var_os("REDBEAR_IN_TREE_BUILD").is_none() && Command::new("git") .arg("ls-files") .arg("--error-unmatch") @@ -1000,17 +1015,38 @@ pub fn build( let effective_source = if source_is_tracked { let staged = get_sub_target_dir(target_dir, "source-staged"); - let _ = fs::remove_dir_all(&staged); - let status = Command::new("cp") + + // Refresh IN PLACE; never remove the directory first. + // + // Qt bakes its COOKBOOK_SOURCE path into installed cmake files + // (QtSeparateDebugInfo.cmake try_compile()s against + // ${QT_SOURCE_TREE}/config.tests/...). Once that path is the + // staged tree, a `rm -rf` at the start of a rebuild makes it + // vanish underneath consumers that are configuring against it: + // CMake Error: The source ".../qtbase/.../source-staged/ + // config.tests/binary_for_strip/CMakeLists.txt" does not ... + // (qtsvg and qtshadertools, both mid-configure). + // + // rsync --delete gives the same pristine result -- files the + // last build's seds added are removed, modified ones are + // restored from the tracked tree -- while the directory itself + // continues to exist throughout. + let _ = fs::create_dir_all(&staged); + let mut src_slash = source_real.clone().into_os_string(); + src_slash.push("/"); + let mut dst_slash = staged.clone().into_os_string(); + dst_slash.push("/"); + let status = Command::new("rsync") .arg("-a") - .arg("--reflink=auto") - .arg(&source_real) + .arg("--delete") + .arg(src_slash) + .arg(dst_slash) .arg(&staged) .status() .map_err(|e| format!("failed to stage source out of tree: {e}"))?; if !status.success() { return Err(format!( - "failed to stage source out of tree: cp exited {status}" + "failed to stage source out of tree: rsync exited {status}" )); } log_to_pty!(