A component can name the files its build reads elsewhere
Freshness compared the commits under a component's own cwd, which is right about where its files are and wrong about what its build reads. tdep-survey's two clients each source one shared scripts/android-sdk-env.sh: a fix committed there changed what every build does, moved no component's subtree head, rebuilt nothing, and left every card correctly reporting "current" while answering a narrower question than the reader was asking. alsoWatch names the rest. The paths join the component's own in the same subtree-head and dirty comparisons -- git takes several pathspecs, so it stays one call each -- and it is part of the acceptance gate like any other declared field. Declared rather than inferred: which files a build reads is not knowable from here, and both wrong guesses are expensive. Raised by the tdep-survey session, which traced why its dioxus client could never become stale on the serving host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f00c3b970f
commit
b67c70fa2f
7 files changed
+209
-18
No files matched your search
@@ -556,10 +556,11 @@ impl BuildState {
|
||||
let Some(built) = built else {
|
||||
return Freshness::Unknown;
|
||||
};
|
||||
if crate::git::subtree_dirty(&self.project_path, component.cwd()) != Some(false) {
|
||||
if crate::git::subtree_dirty(&self.project_path, &component.watched_paths()) != Some(false)
|
||||
{
|
||||
return Freshness::Unknown;
|
||||
}
|
||||
match crate::git::subtree_head(&self.project_path, component.cwd()) {
|
||||
match crate::git::subtree_head(&self.project_path, &component.watched_paths()) {
|
||||
Some(current) if current == built => Freshness::Current,
|
||||
Some(_) => Freshness::Behind,
|
||||
None => Freshness::Unknown,
|
||||
@@ -611,7 +612,7 @@ impl BuildState {
|
||||
// tell; neither is evidence of staleness, and announcing one would
|
||||
// make every project built before this existed demand a rebuild.
|
||||
if let Some(built) = built_from.get(component.name())
|
||||
&& crate::git::subtree_head(&self.project_path, component.cwd())
|
||||
&& crate::git::subtree_head(&self.project_path, &component.watched_paths())
|
||||
.is_some_and(|current| ¤t != built)
|
||||
{
|
||||
return true;
|
||||
@@ -850,7 +851,8 @@ impl BuildState {
|
||||
//
|
||||
// A checkout that cannot be read records nothing, which reads
|
||||
// as unknown rather than as current -- see `component_is_stale`.
|
||||
if let Some(sha) = crate::git::subtree_head(&self.project_path, component.cwd()) {
|
||||
if let Some(sha) = crate::git::subtree_head(&self.project_path, &component.watched_paths())
|
||||
{
|
||||
self.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -1324,6 +1326,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("true"),
|
||||
cwd: Some(PathBuf::from("server")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
service: None,
|
||||
built_from: None,
|
||||
},
|
||||
@@ -1332,6 +1335,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("true"),
|
||||
cwd: Some(PathBuf::from("app")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1388,6 +1392,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("touch built-marker"),
|
||||
cwd: None,
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1441,6 +1446,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("touch backend-built"),
|
||||
cwd: Some(PathBuf::from("server")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
service: None,
|
||||
built_from: None,
|
||||
},
|
||||
@@ -1449,6 +1455,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("touch app-built"),
|
||||
cwd: Some(PathBuf::from("app")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1522,6 +1529,7 @@ mod tests {
|
||||
}),
|
||||
cwd: Some(PathBuf::from(name)),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1592,6 +1600,7 @@ mod tests {
|
||||
}),
|
||||
cwd: Some(PathBuf::from(name)),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1661,6 +1670,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("touch a-built"),
|
||||
cwd: Some(PathBuf::from("a")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1670,6 +1680,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("touch b-built"),
|
||||
cwd: Some(PathBuf::from("b")),
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: None,
|
||||
@@ -1767,7 +1778,7 @@ mod tests {
|
||||
// Built now, from what is checked out now.
|
||||
for name in ["backend", "app"] {
|
||||
let cwd = PathBuf::from(if name == "backend" { "server" } else { "app" });
|
||||
let sha = crate::git::subtree_head(root, Some(&cwd)).expect("a commit");
|
||||
let sha = crate::git::subtree_head(root, std::slice::from_ref(&cwd)).expect("a commit");
|
||||
state
|
||||
.inner
|
||||
.lock()
|
||||
@@ -1789,6 +1800,65 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The other half of that scoping: a component's build reads more
|
||||
/// than its own directory, and what it names with `alsoWatch` counts
|
||||
/// as its own.
|
||||
///
|
||||
/// The case this is for had every piece defensible and the whole
|
||||
/// silent: two clients sourcing one shared script that locates their
|
||||
/// SDK and mints their signing key, a fix committed to that script,
|
||||
/// and no component's subtree head moved -- so nothing rebuilt, and
|
||||
/// every card correctly reported "current" while answering a
|
||||
/// narrower question than the reader was asking. The component that
|
||||
/// says nothing must still be left alone, which is the second
|
||||
/// assertion here.
|
||||
#[test]
|
||||
fn a_commit_to_a_watched_path_makes_only_the_components_that_watch_it_stale() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let root = dir.path();
|
||||
let mut components = two_component_checkout(root);
|
||||
let Component::Apk { also_watch, .. } = &mut components[1] else {
|
||||
panic!("the app is the second component");
|
||||
};
|
||||
*also_watch = vec![PathBuf::from("scripts")];
|
||||
let state = state_for(root, components);
|
||||
|
||||
std::fs::create_dir_all(root.join("scripts")).expect("mkdir");
|
||||
std::fs::write(root.join("scripts/env.sh"), "one").expect("write");
|
||||
run(root, &["git", "add", "-A"]);
|
||||
run(root, &["git", "commit", "-qm", "scripts"]);
|
||||
|
||||
for name in ["backend", "app"] {
|
||||
let component = state
|
||||
.components
|
||||
.iter()
|
||||
.find(|component| component.name() == name)
|
||||
.expect("component");
|
||||
let sha = crate::git::subtree_head(root, &component.watched_paths()).expect("a commit");
|
||||
state
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.built_from
|
||||
.insert(name.to_string(), sha);
|
||||
}
|
||||
assert!(!stale(&state, "backend"), "just built");
|
||||
assert!(!stale(&state, "app"), "just built");
|
||||
|
||||
// A change to neither component's own directory.
|
||||
std::fs::write(root.join("scripts/env.sh"), "two").expect("write");
|
||||
run(root, &["git", "commit", "-qam", "shared script"]);
|
||||
|
||||
assert!(
|
||||
stale(&state, "app"),
|
||||
"the app watches scripts/, so a commit there is a commit to it",
|
||||
);
|
||||
assert!(
|
||||
!stale(&state, "backend"),
|
||||
"the server said nothing about scripts/, so nothing about it changed",
|
||||
);
|
||||
}
|
||||
|
||||
/// Nothing recorded is "we have never built this", not "this is out of
|
||||
/// date" -- otherwise every project that existed before this feature
|
||||
/// would demand a rebuild the moment it arrived.
|
||||
@@ -1822,6 +1892,7 @@ mod tests {
|
||||
build: crate::config::Command::from_line("true"),
|
||||
cwd: None,
|
||||
stale_when: None,
|
||||
also_watch: Vec::new(),
|
||||
strip: false,
|
||||
package: None,
|
||||
built_from: Some("0000000000000000000000000000000000000000".to_string()),
|
||||
|
||||
Reference in new issue
Block a user