Skip to content

Commit 06d01d2

Browse files
committed
cargo-rail: fixing the features/configs in unify... I think we've done it!
1 parent 335df3e commit 06d01d2

File tree

4 files changed

+204
-43
lines changed

4 files changed

+204
-43
lines changed

src/cargo/manifest.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,23 @@ impl CargoTransform {
290290
// Write each unified dependency
291291
// Group by target (None for regular deps, Some(target) for platform-specific)
292292
for unified in unified_deps {
293+
if unified.name == "reqwest" {
294+
use std::io::Write;
295+
let _ = std::fs::OpenOptions::new()
296+
.create(true)
297+
.write(true)
298+
.truncate(true)
299+
.open("/tmp/rail-write-ws.log")
300+
.and_then(|mut f| {
301+
writeln!(
302+
f,
303+
"[write_workspace_dependencies] Processing reqwest with {} features: {:?}",
304+
unified.features.len(),
305+
unified.features
306+
)
307+
});
308+
}
309+
293310
// Determine which table to write to based on target
294311
let deps_table = if let Some(ref target) = unified.target {
295312
// Platform-specific dependency: write to [workspace.target.'<target>'.dependencies]
@@ -363,6 +380,21 @@ impl CargoTransform {
363380

364381
// Add features if any (inline arrays for <10 features)
365382
if !unified.features.is_empty() {
383+
if unified.name == "reqwest" {
384+
use std::io::Write;
385+
let _ = std::fs::OpenOptions::new()
386+
.create(true)
387+
.write(true)
388+
.truncate(true)
389+
.open("/tmp/rail-manifest.log")
390+
.and_then(|mut f| {
391+
writeln!(
392+
f,
393+
"[manifest write] reqwest features being written: {:?}",
394+
unified.features
395+
)
396+
});
397+
}
366398
let mut features_array = Array::new();
367399
for feature in &unified.features {
368400
features_array.push(feature.as_str());
@@ -389,6 +421,22 @@ impl CargoTransform {
389421
// Complex dependency with features - use regular table format
390422
let mut dep_table = table();
391423

424+
if unified.name == "reqwest" {
425+
use std::io::Write;
426+
let _ = std::fs::OpenOptions::new()
427+
.create(true)
428+
.write(true)
429+
.truncate(true)
430+
.open("/tmp/rail-manifest.log")
431+
.and_then(|mut f| {
432+
writeln!(
433+
f,
434+
"[manifest write REGULAR TABLE] reqwest features being written: {:?}",
435+
unified.features
436+
)
437+
});
438+
}
439+
392440
// INVISIBLE FEATURE: Support workspace member path dependencies
393441
if let Some(ref path) = unified.path {
394442
dep_table["path"] = toml_edit::value(path.to_string());

src/cargo/metadata.rs

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,123 @@ impl WorkspaceMetadata {
138138
/// For external packages that appear in multiple versions, returns features
139139
/// for the first resolved version found.
140140
pub fn get_resolved_features_for_package(&self, pkg_name: &str) -> Option<HashSet<String>> {
141+
if pkg_name == "reqwest" {
142+
println!("\n\n=== REQWEST DEBUG START ===");
143+
println!("[ENTRY] get_resolved_features_for_package called for reqwest");
144+
}
145+
141146
let resolve = self.resolve()?;
142147

148+
if pkg_name == "reqwest" {
149+
println!("[RESOLVE] Got resolve graph with {} nodes", resolve.nodes.len());
150+
}
151+
143152
// Find all resolved nodes for this package name
144153
for node in &resolve.nodes {
145-
if let Some(pkg) = self.find_package_by_id(&node.id) {
154+
// Debug log to see if we're iterating nodes for reqwest
155+
if pkg_name == "reqwest" && node.id.repr.contains("reqwest") {
156+
use std::io::Write;
157+
let _ = std::fs::OpenOptions::new()
158+
.create(true)
159+
.append(true)
160+
.open("/tmp/rail-get-resolved.log")
161+
.and_then(|mut f| writeln!(f, "[LOOP] Found node with id: {}", node.id.repr));
162+
}
163+
164+
let found_pkg = self.find_package_by_id(&node.id);
165+
if pkg_name == "reqwest" && node.id.repr.contains("reqwest") {
166+
use std::io::Write;
167+
let _ = std::fs::OpenOptions::new()
168+
.create(true)
169+
.append(true)
170+
.open("/tmp/rail-get-resolved.log")
171+
.and_then(|mut f| {
172+
writeln!(
173+
f,
174+
"[FIND] find_package_by_id for {} returned: {}",
175+
node.id.repr,
176+
found_pkg.is_some()
177+
)
178+
});
179+
}
180+
181+
if let Some(pkg) = found_pkg {
146182
// Skip workspace members - we only want external packages
147183
if self.get_package(&pkg.name).is_some() {
184+
if pkg_name == "reqwest" {
185+
use std::io::Write;
186+
let _ = std::fs::OpenOptions::new()
187+
.create(true)
188+
.append(true)
189+
.open("/tmp/rail-get-resolved.log")
190+
.and_then(|mut f| writeln!(f, "[SKIP] Skipping workspace member: {}", pkg.name));
191+
}
148192
continue;
149193
}
150194

151195
// Check if this is the package we're looking for
152196
if pkg.name == pkg_name {
153-
// Return the resolved features for this package
154-
return Some(node.features.iter().map(|f| f.to_string()).collect());
197+
// IMPORTANT: Filter node.features to only include features that actually exist
198+
// in the package's features table. Cargo's resolve graph includes activated
199+
// optional dependencies in node.features, but these aren't always user-facing
200+
// features (especially with the new "dep:" syntax in Cargo features).
201+
//
202+
// For example, reqwest 0.12 has `__rustls = ["dep:hyper-rustls", ...]`
203+
// Cargo will list "hyper-rustls" in node.features when that dep is activated,
204+
// but "hyper-rustls" is NOT in reqwest's features table, so it can't be
205+
// specified in a Cargo.toml features array.
206+
207+
if pkg_name == "reqwest" {
208+
use std::io::Write;
209+
let _ = std::fs::OpenOptions::new()
210+
.create(true)
211+
.append(true)
212+
.open("/tmp/rail-get-resolved.log")
213+
.and_then(|mut f| {
214+
writeln!(f, "\n[get_resolved_features_for_package] Processing reqwest")?;
215+
writeln!(f, " node.features (raw): {:?}", node.features)?;
216+
writeln!(
217+
f,
218+
" pkg.features.keys(): {:?}",
219+
pkg.features.keys().collect::<Vec<_>>()
220+
)
221+
});
222+
}
223+
224+
let valid_features: HashSet<String> = node
225+
.features
226+
.iter()
227+
.filter(|feature_name| {
228+
let is_valid = pkg.features.contains_key(feature_name.as_str());
229+
if pkg_name == "reqwest" {
230+
use std::io::Write;
231+
let _ = std::fs::OpenOptions::new()
232+
.create(true)
233+
.append(true)
234+
.open("/tmp/rail-get-resolved.log")
235+
.and_then(|mut file| {
236+
if !is_valid {
237+
writeln!(file, " [FILTER] Removing '{}' - not in features table", feature_name)
238+
} else {
239+
writeln!(file, " [KEEP] '{}' - in features table", feature_name)
240+
}
241+
});
242+
}
243+
is_valid
244+
})
245+
.map(|f| f.to_string())
246+
.collect();
247+
248+
if pkg_name == "reqwest" {
249+
use std::io::Write;
250+
let _ = std::fs::OpenOptions::new()
251+
.create(true)
252+
.append(true)
253+
.open("/tmp/rail-get-resolved.log")
254+
.and_then(|mut f| writeln!(f, " valid_features (after filter): {:?}\n", valid_features));
255+
}
256+
257+
return Some(valid_features);
155258
}
156259
}
157260
}

src/cargo/unify/collector.rs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::path_handling::is_workspace_member_path;
44
use super::types::{DependencyInstance, FeatureSource};
55
use crate::cargo::WorkspaceMetadata;
6-
use std::collections::{HashMap, HashSet};
6+
use std::collections::HashMap;
77

88
/// Collect all dependency instances from workspace members
99
///
@@ -17,14 +17,7 @@ pub fn collect_dependencies(metadata: &WorkspaceMetadata, use_all_features: bool
1717
let mut instances = Vec::new();
1818

1919
for pkg in metadata.list_crates() {
20-
// Check which dependencies in this member use workspace = true
21-
let workspace_inherited = get_member_workspace_deps(&pkg.manifest_path);
22-
2320
for dep in &pkg.dependencies {
24-
// Skip dependencies that are already using workspace inheritance in THIS member
25-
if workspace_inherited.contains(&dep.name) {
26-
continue;
27-
}
2821
// Get RESOLVED features if available, fallback to declared features
2922
// Resolved features include:
3023
// 1. Features declared in this member's Cargo.toml
@@ -92,37 +85,6 @@ pub fn group_by_name(
9285
grouped
9386
}
9487

95-
/// Get dependencies that use workspace = true in a member's Cargo.toml
96-
fn get_member_workspace_deps(manifest_path: &cargo_metadata::camino::Utf8Path) -> HashSet<String> {
97-
let mut workspace_deps = HashSet::new();
98-
99-
if let Ok(content) = std::fs::read_to_string(manifest_path.as_std_path())
100-
&& let Ok(doc) = content.parse::<toml_edit::DocumentMut>()
101-
{
102-
// Check all dependency sections
103-
for section in &["dependencies", "dev-dependencies", "build-dependencies"] {
104-
if let Some(deps) = doc.get(section).and_then(|d| d.as_table()) {
105-
for (key, value) in deps.iter() {
106-
// Check if this dep uses workspace = true
107-
let uses_workspace = if let Some(inline_table) = value.as_inline_table() {
108-
inline_table.get("workspace").and_then(|w| w.as_bool()) == Some(true)
109-
} else if let Some(table) = value.as_table() {
110-
table.get("workspace").and_then(|w| w.as_bool()) == Some(true)
111-
} else {
112-
false
113-
};
114-
115-
if uses_workspace {
116-
workspace_deps.insert(key.to_string());
117-
}
118-
}
119-
}
120-
}
121-
}
122-
123-
workspace_deps
124-
}
125-
12688
/// Determine WHY each feature is enabled for a dependency
12789
///
12890
/// This is the key to showing users Cargo.toml-level information about their dependencies.

src/cargo/unify/unifier.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,55 @@ pub fn unify_instances(
2020
for instance in instances {
2121
all_features.extend(instance.features.iter().cloned());
2222
}
23-
let mut features: Vec<_> = all_features.into_iter().collect();
23+
24+
if dep_name == "reqwest" {
25+
println!("\n==== REQWEST DEBUG ====");
26+
println!("all_features from instances: {:?}", all_features);
27+
}
28+
29+
// Get package info to determine which features are explicitly defined
30+
let pkg_info = metadata
31+
.metadata_json()
32+
.packages
33+
.iter()
34+
.find(|pkg| pkg.name == dep_name);
35+
36+
let (explicit_features, optional_deps): (HashSet<String>, HashSet<String>) = if let Some(pkg) = pkg_info {
37+
let explicit = pkg.features.keys().cloned().collect();
38+
let optional = pkg
39+
.dependencies
40+
.iter()
41+
.filter(|dep| dep.optional)
42+
.map(|dep| dep.name.clone())
43+
.collect();
44+
(explicit, optional)
45+
} else {
46+
(HashSet::new(), HashSet::new())
47+
};
48+
49+
if dep_name == "reqwest" {
50+
println!("explicit_features: {:?}", explicit_features);
51+
println!("optional_deps: {:?}", optional_deps);
52+
}
53+
54+
// Filter out:
55+
// 1. Internal/private features (starting with __)
56+
// 2. Names that are ONLY optional dependencies (not explicitly defined as features)
57+
// Example: "hyper-rustls" is an optional dep without a matching feature
58+
// Counter-example: "tokio" is both an optional dep AND an explicit feature (keep it)
59+
let mut features: Vec<_> = all_features
60+
.into_iter()
61+
.filter(|f| {
62+
if f.starts_with("__") {
63+
return false; // Filter out internal features
64+
}
65+
// If it's an optional dependency name, only keep it if it's also an explicit feature
66+
if optional_deps.contains(f) {
67+
return explicit_features.contains(f);
68+
}
69+
true // Keep all other features
70+
})
71+
.collect();
2472
features.sort();
2573

2674
// Feature provenance: collect ALL sources for each feature across instances

0 commit comments

Comments
 (0)