1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use crate::project;
use crate::project::AbsolutePath;
use crate::project::ManifestFingerprint;
use crate::project::WorkspaceMetadata;
use crate::scan;
use crate::scan::CargoMetadataError;
use crate::tui::app::App;
use crate::tui::app::target_index::TargetDirMember;
impl App {
/// Merge a `cargo metadata` arrival back into the process-wide store and
/// advance the startup metadata phase. The startup path drives the
/// "Cargo metadata" row of the consolidated Startup panel; post-startup
/// per-workspace refreshes land with Step 1b (watcher-triggered refresh)
/// — until then only the startup path can arrive here.
pub(super) fn handle_cargo_metadata_msg(
&mut self,
workspace_root: AbsolutePath,
generation: u64,
fingerprint: &ManifestFingerprint,
result: Result<WorkspaceMetadata, CargoMetadataError>,
) {
let Some(is_current) = self
.scan
.metadata_store()
.lock()
.ok()
.map(|store| store.is_current_generation(&workspace_root, generation))
else {
tracing::warn!(
workspace_root = %workspace_root.as_path().display(),
generation,
"cargo_metadata_store_lock_poisoned"
);
return;
};
if !is_current {
tracing::debug!(
workspace_root = %workspace_root.as_path().display(),
generation,
"cargo_metadata_msg_stale_generation"
);
return;
}
match result {
Ok(workspace_metadata) => {
if !self.accept_cargo_metadata(
&workspace_root,
generation,
fingerprint,
workspace_metadata,
) {
return;
}
},
Err(err) => match err.user_facing_message() {
Some(message) => {
let label = project::home_relative_path(workspace_root.as_path());
self.show_timed_toast(
format!("cargo metadata failed ({label})"),
message.to_string(),
);
tracing::warn!(
workspace_root = %workspace_root.as_path().display(),
generation,
error = %message,
"cargo_metadata_failed"
);
},
None => {
// `WorkspaceMissing`: the workspace root vanished
// between dispatch and run (typically the user just
// deleted a worktree). Stale-refresh race, not a real
// failure — suppress the toast.
tracing::debug!(
workspace_root = %workspace_root.as_path().display(),
generation,
"cargo_metadata_workspace_missing"
);
},
},
}
// If the user had a confirm popup waiting on this workspace's
// re-fingerprint, clear the Verifying flag so the next render
// shows Ready and 'y' starts working again.
self.scan.clear_confirm_verifying_for(&workspace_root);
self.startup.metadata.seen.insert(workspace_root);
self.maybe_log_startup_phase_completions();
}
/// Merge a successful `cargo metadata` arrival. Returns `false` when the
/// arrival was dropped because the captured fingerprint no longer
/// matches what's on disk — caller should skip startup-phase bookkeeping
/// so a later dispatch can still tick it off.
pub(super) fn accept_cargo_metadata(
&mut self,
workspace_root: &AbsolutePath,
generation: u64,
fingerprint: &ManifestFingerprint,
workspace_metadata: WorkspaceMetadata,
) -> bool {
let current_fp = ManifestFingerprint::capture(workspace_root.as_path()).ok();
let fingerprint_drift = current_fp
.as_ref()
.is_some_and(|current| current != fingerprint);
if fingerprint_drift {
tracing::debug!(
workspace_root = %workspace_root.as_path().display(),
generation,
"cargo_metadata_msg_fingerprint_drift"
);
return false;
}
let target_directory = workspace_metadata.target_directory.clone();
let member_roots = workspace_member_roots(&workspace_metadata);
let needs_out_of_tree_walk = !target_directory
.as_path()
.starts_with(workspace_root.as_path());
// Stamp Cargo fields (types / examples / benches /
// publishable) from each PackageRecord onto the matching
// Package / Workspace / VendoredPackage in the project list.
// The workspace metadata is authoritative.
self.project_list
.apply_cargo_fields_from_workspace_metadata(&workspace_metadata);
if let Ok(mut store) = self.scan.metadata_store().lock() {
store.upsert(workspace_metadata);
}
if needs_out_of_tree_walk {
scan::spawn_out_of_tree_target_walk(
&self.net.http_client.handle,
self.background.background_sender(),
workspace_root.clone(),
target_directory.clone(),
);
}
// Refresh the target-dir index so build_clean_plan / siblings
// lookups see the fresh membership. Every package under this
// workspace shares `target_directory`; upsert each so a
// subsequent clean on any member resolves to the correct dir.
// (Members that were in *previous* metadata but not this one
// will linger until a full scan restart — minor staleness,
// acceptable for Step 6c.)
for project_root in member_roots {
self.scan
.target_dir_index
.upsert(TargetDirMember { project_root }, target_directory.clone());
}
tracing::info!(
workspace_root = %workspace_root.as_path().display(),
generation,
"cargo_metadata_applied"
);
true
}
}
/// Project root for each package covered by a [`WorkspaceMetadata`] —
/// derived from each package's `manifest_path.parent()`. Feeds the
/// `TargetDirIndex` membership update after a successful
/// `BackgroundMsg::CargoMetadata` arrival; every package under a given
/// workspace shares the metadata's `target_directory`.
fn workspace_member_roots(workspace_metadata: &WorkspaceMetadata) -> Vec<AbsolutePath> {
workspace_metadata
.packages
.values()
.filter_map(|pkg| {
pkg.manifest_path
.as_path()
.parent()
.map(|parent| AbsolutePath::from(parent.to_path_buf()))
})
.collect()
}