Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: try making cargo tree's Graph from a UnitGraph #11379

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c0a4e8b
initial failing test
alsuren Nov 15, 2022
7cab4ab
skeleton Graph::from_bcx()
alsuren Nov 15, 2022
532d075
fixme
alsuren Nov 15, 2022
b928ab5
partially working graph::from_bcx()
alsuren Nov 15, 2022
c4486e0
handle build dependencies by asking the resolver
alsuren Nov 15, 2022
8679e80
use CompileMode::Test to convince the resolver to give us dev-depende…
alsuren Nov 15, 2022
cf7dab6
also remove fixme about dev deps
alsuren Nov 18, 2022
1a11448
REVERTME: run tests with new Graph builder algorithm
alsuren Nov 19, 2022
0677377
initial support for features
alsuren Nov 19, 2022
df6cd75
REVERTME: enable trace debugging for tests
alsuren Nov 19, 2022
13c87f1
identify what the problem is with my test
alsuren Nov 19, 2022
099e351
make test include build.rs to enable build-dependencies
alsuren Nov 19, 2022
c8d609f
REVERTME: debug for common panic in package_for_id()
alsuren Nov 19, 2022
d24448c
filter out edge kinds that were not requested
alsuren Nov 19, 2022
6b671e9
optional: set CompileMode based on HasDevUnits
alsuren Nov 19, 2022
4a40877
fill in dep_name_map
alsuren Nov 19, 2022
161d0fb
copy TreeOptions::cli_features onto CompileOptions
alsuren Nov 19, 2022
9e48c3d
Revert "REVERTME: enable trace debugging for tests"
alsuren Nov 19, 2022
6ec86cb
only populate dep_name_map when needed
alsuren Nov 19, 2022
f605535
forward packages from TreeOptions to CompileOptions
alsuren Nov 19, 2022
40f795e
copy requested_kinds across, for --target
alsuren Nov 19, 2022
c25fa77
fudge tree::filters_target test so that it passes
alsuren Nov 19, 2022
f9e104f
add build.rs to tree::invert_with_build_dep test
alsuren Nov 19, 2022
63446d0
add comments for remaining test failures
alsuren Nov 19, 2022
e8ed11a
note why the rest of the tests are failing. All '-e features' and '--…
alsuren Nov 20, 2022
fde5f77
handle -e features - add default feature and disable all other link t…
alsuren Nov 20, 2022
fde631d
write up how features_namespaced::tree is failing
alsuren Nov 20, 2022
8befb2a
do feature-adding properly; don't preallocate ::Feature nodes
alsuren Nov 20, 2022
a3e0932
don't panic on --features bar?/feat where bar is not activated
alsuren Nov 20, 2022
bde2a08
hack in a Normal dep if no features asked for
alsuren Nov 20, 2022
3499509
annotate another --target=all test
alsuren Nov 20, 2022
95dcd44
note where tree::host_Dep_feature might be going wrong
alsuren Nov 20, 2022
f2c6540
update fixmes in tests
alsuren Nov 20, 2022
bb9a4e2
WIP on supporting --target=all via ForceAllTargets (doesn't work yet)
alsuren Nov 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/cargo-test-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ impl Project {
let mut execs = self.process(&cargo);
if let Some(ref mut p) = execs.process_builder {
p.env("CARGO", cargo);
p.env("CARGO_TREE_FROM_UNIT_GRAPH", "1");
p.arg_line(cmd);
}
execs
Expand Down
26 changes: 26 additions & 0 deletions crates/cargo-test-support/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ pub struct Package {
name: String,
vers: String,
deps: Vec<Dependency>,
needs_build_script: bool,
files: Vec<PackageFile>,
yanked: bool,
features: FeatureMap,
Expand Down Expand Up @@ -854,6 +855,7 @@ impl Package {
name: name.to_string(),
vers: vers.to_string(),
deps: Vec::new(),
needs_build_script: false,
files: Vec::new(),
yanked: false,
features: BTreeMap::new(),
Expand Down Expand Up @@ -890,6 +892,15 @@ impl Package {
self
}

/// Add a build.rs so that the UnitGraph will not ignore our [build-dependenies] section.
///
/// Also adds a src/lib.rs, because make_archive() will no longer do it for us.
/// FIXME: there is probably a better way to do this.
pub fn build_script(&mut self) -> &mut Package {
self.needs_build_script = true;
self
}

/// Adds a file to the package.
pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
self.file_with_mode(name, DEFAULT_MODE, contents)
Expand Down Expand Up @@ -979,6 +990,9 @@ impl Package {
/// foo = {version = "1.0"}
/// ```
pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
// FIXME: these will be removed from UnitGraph if there is no matching is_custom_build unit.
// Either implicitly add one here, or add a `.add_build_script()` and make sure it is called
// everywhere?.
self.add_dep(Dependency::new(name, vers).build())
}

Expand Down Expand Up @@ -1142,6 +1156,14 @@ impl Package {
}
}
}
if self.needs_build_script && !self.files.iter().any(|f| f.path == "build.rs") {
self.append(
&mut a,
"src/lib.rs",
DEFAULT_MODE,
&EntryData::Regular("fn main {}".into()),
)
}
}

fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
Expand All @@ -1164,6 +1186,10 @@ impl Package {
self.name, self.vers
));

if self.needs_build_script {
manifest.push_str("build = \"build.rs\"");
}

if let Some(version) = &self.rust_version {
manifest.push_str(&format!("rust-version = \"{}\"", version));
}
Expand Down
6 changes: 6 additions & 0 deletions src/cargo/core/compiler/unit_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,12 @@ impl<'a, 'cfg> State<'a, 'cfg> {
// dependencies, otherwise we want everything *other than* build
// dependencies.
if unit.target.is_custom_build() != dep.is_build() {
log::trace!(
"dropping dep of {:?}: {:?}: buildiness mismatch",
pkg_id.name(),
id.name()
);

return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/resolver/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub enum HasDevUnits {
}

/// Flag to indicate that target-specific filtering should be disabled.
#[derive(Copy, Clone, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ForceAllTargets {
Yes,
No,
Expand Down
8 changes: 6 additions & 2 deletions src/cargo/ops/cargo_compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::core::compiler::{BuildConfig, BuildContext, Compilation, Context};
use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTargetData, Unit};
use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::resolver::features::{self, CliFeatures, FeaturesFor};
use crate::core::resolver::features::{self, CliFeatures, FeaturesFor, ForceAllTargets};
use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::{FeatureValue, Package, PackageSet, Shell, Summary, Target};
use crate::core::{PackageId, SourceId, TargetKind, Workspace};
Expand Down Expand Up @@ -79,6 +79,8 @@ pub struct CompileOptions {
/// Filter to apply to the root package to select which targets will be
/// built.
pub filter: CompileFilter,
/// HACK: used by `cargo tree` to support `--target=all`.
pub force_all_targets: ForceAllTargets,
/// Extra arguments to be passed to rustdoc (single target only)
pub target_rustdoc_args: Option<Vec<String>>,
/// The specified target will be compiled with all the available arguments,
Expand All @@ -105,6 +107,7 @@ impl CompileOptions {
filter: CompileFilter::Default {
required_features_filterable: false,
},
force_all_targets: ForceAllTargets::No,
target_rustdoc_args: None,
target_rustc_args: None,
target_rustc_crate_types: None,
Expand Down Expand Up @@ -200,6 +203,7 @@ pub fn create_bcx<'a, 'cfg>(
ref spec,
ref cli_features,
ref filter,
force_all_targets,
ref target_rustdoc_args,
ref target_rustc_args,
ref target_rustc_crate_types,
Expand Down Expand Up @@ -255,7 +259,7 @@ pub fn create_bcx<'a, 'cfg>(
cli_features,
&resolve_specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,
force_all_targets,
)?;
let WorkspaceResolve {
mut pkg_set,
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/cargo_compile/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use anyhow::{bail, Context as _};
///
/// Generally, it represents the combination of all `-p` flag. When working within
/// a workspace, `--exclude` and `--workspace` flags also contribute to it.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Packages {
/// Pacakges selected by default. Ususally means no flag provided.
Default,
Expand Down
3 changes: 2 additions & 1 deletion src/cargo/ops/cargo_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::sync::Arc;
use std::task::Poll;

use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
use crate::core::resolver::CliFeatures;
use crate::core::resolver::{CliFeatures, ForceAllTargets};
use crate::core::{Feature, Shell, Verbosity, Workspace};
use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
use crate::sources::PathSource;
Expand Down Expand Up @@ -851,6 +851,7 @@ fn run_verify(
filter: ops::CompileFilter::Default {
required_features_filterable: true,
},
force_all_targets: ForceAllTargets::No,
target_rustdoc_args: None,
target_rustc_args: rustc_args,
target_rustc_crate_types: None,
Expand Down
182 changes: 178 additions & 4 deletions src/cargo/ops/tree/graph.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Code for building the graph used by `cargo tree`.

use itertools::Itertools;

use super::TreeOptions;
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::compiler::{BuildContext, CompileKind, RustcTargetData, Unit};
use crate::core::dependency::DepKind;
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::resolver::Resolve;
Expand All @@ -26,6 +28,20 @@ pub enum Node {
},
}

impl Node {
/// Make a Node representing the Node::Package of a Unit.
///
/// Note: There is a many:1 relationship between Unit and Package,
/// because some units are build.rs builds or build.rs invocations.
fn package_for_unit(unit: &Unit) -> Node {
Node::Package {
package_id: unit.pkg.package_id(),
features: unit.features.clone(),
kind: unit.kind,
}
}
}

/// The kind of edge, for separating dependencies into different sections.
#[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)]
pub enum EdgeKind {
Expand All @@ -49,7 +65,7 @@ impl Edges {
Edges(HashMap::new())
}

/// Adds an edge pointing to the given node.
/// Adds an edge pointing to the given node. This is idempotent.
fn add_edge(&mut self, kind: EdgeKind, index: usize) {
let indexes = self.0.entry(kind).or_default();
if !indexes.contains(&index) {
Expand Down Expand Up @@ -93,6 +109,15 @@ impl<'a> Graph<'a> {
}
}

/// Adds a new node to the graph if it doesn't exist, returning its index.
fn add_node_idempotently(&mut self, node: Node) -> usize {
if let Some(index) = self.index.get(&node) {
*index
} else {
self.add_node(node)
}
}

/// Adds a new node to the graph, returning its new index.
fn add_node(&mut self, node: Node) -> usize {
let from_index = self.nodes.len();
Expand Down Expand Up @@ -145,7 +170,9 @@ impl<'a> Graph<'a> {
}

pub fn package_for_id(&self, id: PackageId) -> &Package {
self.package_map[&id]
self.package_map
.get(&id)
.unwrap_or_else(|| panic!("could not find {id:#?} in {:#?}", self.package_map))
}

fn package_id_for_index(&self, index: usize) -> PackageId {
Expand Down Expand Up @@ -266,6 +293,149 @@ impl<'a> Graph<'a> {
}
}

/// Builds the graph by iterating over the UnitDeps of a BuildContext.
///
/// This is useful for finding bugs in the implementation of `build()`, below.
pub fn from_bcx<'a, 'cfg>(
bcx: BuildContext<'a, 'cfg>,
resolve: &Resolve,
// FIXME: it feels like it would be easy for specs and cli_features to get out-of-sync with
// what bcx has been configured with. Either make that structurally impossible or add an assert.
specs: &[PackageIdSpec],
cli_features: &CliFeatures,
package_map: HashMap<PackageId, &'a Package>,
opts: &TreeOptions,
) -> CargoResult<Graph<'a>> {
let mut graph = Graph::new(package_map);

// First pass: add all of the nodes for Packages
for unit in bcx.unit_graph.keys().sorted() {
let node = Node::package_for_unit(unit);
// There may be multiple units for the same package if build-scripts are involved.
graph.add_node_idempotently(node);

// FIXME: I quite like the idea of adding all of the nodes in the first pass, but adding
// the full set of features doesn't seem to be possible here. Maybe it's better to think of
// features as more like a kind of Edge, and do it in the second loop.
// if opts.graph_features {
// for name in unit.features.iter().copied() {
// let node = Node::Feature { node_index, name };
// graph.add_node_idempotently(node);
// }
// }
}

// second pass: add all of the edges (and `Node::Feature`s if that's what's asked for)
for (unit, deps) in bcx.unit_graph.iter() {
let node = Node::package_for_unit(unit);
let from_index = *graph.index.get(&node).unwrap();

for dep in deps {
if dep.unit.pkg.package_id() == unit.pkg.package_id() {
// Probably a build script that's part of the same package. Skip it.
continue;
}
let dep_node = Node::package_for_unit(&dep.unit);
let dep_index = *graph.index.get(&dep_node).unwrap();

// FIXME: This is really ugly. It's also quadratic in `deps.len()`, but `deps` is only
// the direct dependencies of `unit`, so the ugliness is more important.
// I think I want to `zip(sorted(deps), sorted(resolve.deps(unit)))` and then assert
// that the ids line up, with nothing left over.
let mut found = false;
let mut added = false;
for (_, dep_set) in resolve
.deps(unit.pkg.package_id())
.filter(|(dep_id, _dep_set)| dep_id == &dep.unit.pkg.package_id())
{
found = true;
assert!(
!dep_set.is_empty(),
"resolver should be able to tell us why {unit:?} depends on {dep:?}"
);

// FIXME: think of better names for dep and link
// (most code needs to have `dep` renamed to `link` when copy-pasting)
for link in dep_set {
if opts.graph_features {
if link.uses_default_features() {
add_feature(
&mut graph,
InternedString::new("default"),
Some(from_index),
dep_index,
EdgeKind::Dep(link.kind()),
);
added = true;
}
for feature in link.features() {
// FIXME: is add_feature() idempotent?
add_feature(
&mut graph,
*feature,
Some(from_index),
dep_index,
EdgeKind::Dep(link.kind()),
);
added = true;
}
// FIXME: do this in its own pass or something?
graph
.dep_name_map
.entry(from_index)
.or_default()
.entry(link.name_in_toml())
.or_default()
.insert((dep_index, link.is_optional()));
} else {
let kind = EdgeKind::Dep(link.kind());
if opts.edge_kinds.contains(&kind) {
// FIXME: if it's not possible to get from unit to dep with this kind
// of edge then maybe we shouldn't add it? Maybe this would help with
// the tree::host_dep_feature test? Not sure how to determine this though.
graph.edges[from_index].add_edge(kind, dep_index);
}
}
}
}

assert!(
found,
"resolver should have a record of {unit:?} depending on {dep:?}"
);
if opts.graph_features && !added {
// HACK: if dep was added with default-features = false and no other features then
// it won't be linked up yet. Fudge a direct link in there so that we can represent
// it on the graph.
graph.edges[from_index].add_edge(EdgeKind::Dep(DepKind::Normal), dep_index);
}
}
}

if opts.graph_features {
let mut members_with_features = bcx.ws.members_with_features(specs, cli_features)?;
members_with_features.sort_unstable_by_key(|e| e.0.package_id());
for (member, cli_features) in members_with_features {
// This package might be built for both host and target.
let member_indexes = graph.indexes_from_ids(&[member.package_id()]);
assert!(!member_indexes.is_empty());

// FIXME: if the package shows up in both host and `target`, it may be possible for the
// features to be different (this may not even be possible for workspace members in the
// current resolver - I've not checked).
//
// We might be better off querying the UnitGraph again or something?
let fmap = resolve.summary(member.package_id()).features();
for member_index in member_indexes.into_iter() {
add_cli_features(&mut graph, member_index, &cli_features, fmap);
}
}

add_internal_features(&mut graph, resolve)
}
Ok(graph)
}

/// Builds the graph.
pub fn build<'a>(
ws: &Workspace<'_>,
Expand Down Expand Up @@ -519,7 +689,11 @@ fn add_cli_features(
dep_feature,
weak,
} => {
let dep_connections = match graph.dep_name_map[&package_index].get(&dep_name) {
let dep_connections = match graph
.dep_name_map
.get(&package_index)
.and_then(|h| h.get(&dep_name))
{
// Clone to deal with immutable borrow of `graph`. :(
Some(dep_connections) => dep_connections.clone(),
None => {
Expand Down
Loading