workspace hot-patching, dynamic tls fixups (#5291)
* Refactor cross-tls-crate-dylib configuration and update test output - Commented out the crate-type configuration in Cargo.toml for cross-tls-crate-dylib. - Modified the initial value of the thread-local variable FOO from 2.0 to 1.0 in the main.rs test file. - Updated the output message in main.rs to be more descriptive. - Added a new field `tls_fixups` to the JumpTable struct in subsecond-types, which will facilitate future runtime TLS sharing. * ws hotpatch! * remove old file * undo core change * svh * Enhance rlib path resolution by incorporating -C extra-filename handling * remove obj diffing * small cleanups * obj cache as files * simplify * simplify a bit * dont bother round tripping cache * extract to helper * cleanup `build` function * dont round-trip modified_crates * make sure to wipe between full * bit of a cleanup * use explicit naming * compact extra method * fix temp cleanup * fix warnign * cleanup * fmt, clippy
This commit is contained in:
1 parent
9b0d380765
commit
33159f366a
12 files changed
+1229
-348
No files matched your search
@@ -22,9 +22,9 @@ use notify::{
|
||||
Config, EventKind, RecursiveMode, Watcher as NotifyWatcher,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
net::{IpAddr, TcpListener},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -511,9 +511,13 @@ impl AppServer {
|
||||
// A full rebuild is required when the user modifies static initializers which we haven't wired up yet.
|
||||
if needs_full_rebuild && self.automatic_rebuilds {
|
||||
if self.use_hotpatch_engine {
|
||||
self.client.patch_rebuild(files.to_vec(), BuildId::PRIMARY);
|
||||
let changed_crates = self.order_changed_crates(files);
|
||||
|
||||
self.client
|
||||
.patch_rebuild(files.to_vec(), changed_crates.clone(), BuildId::PRIMARY);
|
||||
|
||||
if let Some(server) = self.server.as_mut() {
|
||||
server.patch_rebuild(files.to_vec(), BuildId::SECONDARY);
|
||||
server.patch_rebuild(files.to_vec(), changed_crates, BuildId::SECONDARY);
|
||||
}
|
||||
self.clear_hot_reload_changes();
|
||||
self.clear_cached_rsx();
|
||||
@@ -1163,6 +1167,152 @@ impl AppServer {
|
||||
krates
|
||||
}
|
||||
|
||||
/// Compute the ordered compilation chain from a changed workspace crate to the tip crate.
|
||||
///
|
||||
/// Returns crate names (underscore-normalized) in compilation order: the changed crate first,
|
||||
/// then each intermediate workspace crate that depends on it, ending with the tip crate.
|
||||
///
|
||||
/// Uses BFS from the tip crate through its workspace dependencies to find the path.
|
||||
/// If the changed crate IS the tip crate, returns just `[tip]`.
|
||||
pub(crate) fn workspace_dep_chain(&self, changed_crate: &str) -> Vec<String> {
|
||||
let tip_name = self.client.build.main_target.replace('-', "_");
|
||||
|
||||
// If the changed crate is the tip, no chain needed
|
||||
if changed_crate == tip_name {
|
||||
return vec![tip_name];
|
||||
}
|
||||
|
||||
// Build a map of workspace crate names to their krates NodeIds
|
||||
let mut name_to_node: HashMap<String, NodeId> = HashMap::new();
|
||||
for member in self.workspace.krates.workspace_members() {
|
||||
if let krates::Node::Krate { id, krate, .. } = member {
|
||||
let normalized = krate.name.replace('-', "_");
|
||||
name_to_node.insert(normalized, self.workspace.krates.nid_for_kid(id).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// BFS/DFS from tip through workspace deps to find path to changed crate.
|
||||
// We walk the dependency edges (tip → its deps → their deps → ...) looking for changed_crate.
|
||||
let Some(&tip_node) = name_to_node.get(&tip_name) else {
|
||||
return vec![changed_crate.to_string()];
|
||||
};
|
||||
|
||||
// parent[node] = the workspace crate that depends on it (closer to tip)
|
||||
let mut parent: HashMap<NodeId, Option<NodeId>> = HashMap::new();
|
||||
parent.insert(tip_node, None);
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(tip_node);
|
||||
|
||||
let mut target_node = None;
|
||||
|
||||
while let Some(current) = queue.pop_front() {
|
||||
for (dep, _edge) in self.workspace.krates.get_deps(current) {
|
||||
let (dep_name, dep_nid) = match dep {
|
||||
krates::Node::Krate { id, krate, .. } => {
|
||||
let normalized = krate.name.replace('-', "_");
|
||||
let nid = self.workspace.krates.nid_for_kid(id).unwrap();
|
||||
(normalized, nid)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Only traverse workspace members
|
||||
if !name_to_node.contains_key(&dep_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if parent.contains_key(&dep_nid) {
|
||||
continue; // already visited
|
||||
}
|
||||
|
||||
parent.insert(dep_nid, Some(current));
|
||||
|
||||
if dep_name == changed_crate {
|
||||
target_node = Some(dep_nid);
|
||||
break;
|
||||
}
|
||||
|
||||
queue.push_back(dep_nid);
|
||||
}
|
||||
|
||||
if target_node.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct the path from changed_crate → ... → tip
|
||||
let Some(target) = target_node else {
|
||||
// Changed crate not found in workspace dep graph — just compile it alone
|
||||
return vec![changed_crate.to_string()];
|
||||
};
|
||||
|
||||
let mut chain = vec![];
|
||||
let mut node = target;
|
||||
loop {
|
||||
// Find the crate name for this node
|
||||
let krate = &self.workspace.krates[node];
|
||||
chain.push(krate.name.replace('-', "_"));
|
||||
|
||||
match parent.get(&node) {
|
||||
Some(Some(parent_node)) => node = *parent_node,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
chain
|
||||
}
|
||||
|
||||
/// Order a set of changed workspace crates so that deeper dependencies compile first.
|
||||
///
|
||||
/// Uses `workspace_dep_chain` to determine the depth of each crate in the dependency graph,
|
||||
/// then sorts so that leaves (deepest deps) compile before crates closer to the tip.
|
||||
fn order_changed_crates(&self, files: &[PathBuf]) -> Vec<String> {
|
||||
// Determine which workspace crates changed based on the file paths.
|
||||
// Order them so deeper deps compile first (leaves before dependents).
|
||||
let changed_set: HashSet<String> = files
|
||||
.iter()
|
||||
.filter_map(|f| self.file_to_workspace_crate(f))
|
||||
.collect();
|
||||
|
||||
let mut crates_with_depth: Vec<_> = changed_set
|
||||
.iter()
|
||||
.map(|c| (c.clone(), self.workspace_dep_chain(c).len()))
|
||||
.collect();
|
||||
|
||||
// Longer chain = deeper in dep tree = should compile first
|
||||
crates_with_depth.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
crates_with_depth.into_iter().map(|(c, _)| c).collect()
|
||||
}
|
||||
|
||||
/// Map a changed file path to the workspace crate it belongs to.
|
||||
///
|
||||
/// Returns the crate name in rustc convention (hyphens → underscores), matching the
|
||||
/// `--crate-name` arg used by rustc and the keys in `workspace_rustc_args`.
|
||||
///
|
||||
/// Finds the workspace member whose crate directory is the longest prefix of the file path.
|
||||
fn file_to_workspace_crate(&self, file: &Path) -> Option<String> {
|
||||
let mut best_match: Option<(String, usize)> = None;
|
||||
|
||||
for member in self.workspace.krates.workspace_members() {
|
||||
if let krates::Node::Krate { krate, .. } = member {
|
||||
let Some(crate_dir) = krate.manifest_path.parent() else {
|
||||
continue;
|
||||
};
|
||||
if let Ok(relative) = file.strip_prefix(crate_dir.as_std_path()) {
|
||||
let depth = relative.components().count();
|
||||
let is_better = best_match
|
||||
.as_ref()
|
||||
.map_or(true, |(_, best_depth)| depth < *best_depth);
|
||||
if is_better {
|
||||
best_match = Some((krate.name.replace('-', "_"), depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_match.map(|(name, _)| name)
|
||||
}
|
||||
|
||||
/// Check if this is a fullstack build. This means that there is an additional build with the `server` platform.
|
||||
pub(crate) fn is_fullstack(&self) -> bool {
|
||||
self.fullstack
|
||||
|
||||
Reference in new issue
Block a user