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

Make required dependency as future an error, remove RcList #6860

Merged
merged 9 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
89 changes: 37 additions & 52 deletions src/cargo/core/resolver/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ use crate::util::CargoResult;
use crate::util::Graph;

use super::errors::ActivateResult;
use super::types::{
ConflictMap, ConflictReason, DepInfo, GraphNode, Method, RcList, RegistryQueryer,
};
use super::types::{ConflictMap, ConflictReason, DepInfo, Method, RegistryQueryer};

pub use super::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
pub use super::encode::{Metadata, WorkspaceResolve};
Expand All @@ -37,20 +35,9 @@ pub struct Context {
pub public_dependency:
Option<im_rc::HashMap<PackageId, im_rc::HashMap<InternedString, (PackageId, bool)>>>,

// This is somewhat redundant with the `resolve_graph` that stores the same data,
// but for querying in the opposite order.
/// a way to look up for a package in activations what packages required it
/// and all of the exact deps that it fulfilled.
pub parents: Graph<PackageId, Rc<Vec<Dependency>>>,

// These are two cheaply-cloneable lists (O(1) clone) which are effectively
// hash maps but are built up as "construction lists". We'll iterate these
// at the very end and actually construct the map that we're making.
pub resolve_graph: RcList<GraphNode>,
pub resolve_replacements: RcList<(PackageId, PackageId)>,

// These warnings are printed after resolution.
pub warnings: RcList<String>,
}

/// When backtracking it can be useful to know how far back to go.
Expand Down Expand Up @@ -98,7 +85,6 @@ impl PackageId {
impl Context {
pub fn new(check_public_visible_dependencies: bool) -> Context {
Context {
resolve_graph: RcList::new(),
resolve_features: im_rc::HashMap::new(),
links: im_rc::HashMap::new(),
public_dependency: if check_public_visible_dependencies {
Expand All @@ -107,9 +93,7 @@ impl Context {
None
},
parents: Graph::new(),
resolve_replacements: RcList::new(),
activations: im_rc::HashMap::new(),
warnings: RcList::new(),
}
}

Expand All @@ -128,7 +112,6 @@ impl Context {
);
}
im_rc::hashmap::Entry::Vacant(v) => {
self.resolve_graph.push(GraphNode::Add(id));
if let Some(link) = summary.links() {
ensure!(
self.links.insert(link, id).is_none(),
Expand Down Expand Up @@ -174,7 +157,7 @@ impl Context {
// First, figure out our set of dependencies based on the requested set
// of features. This also calculates what features we're going to enable
// for our own dependencies.
let deps = self.resolve_features(parent, candidate, method)?;
let deps = self.resolve_features(parent, candidate, method, None)?;

// Next, transform all dependencies into a list of possible candidates
// which can satisfy that dependency.
Expand Down Expand Up @@ -229,11 +212,12 @@ impl Context {
}

/// Returns all dependencies and the features we want from them.
fn resolve_features<'b>(
pub fn resolve_features<'b>(
&mut self,
parent: Option<&Summary>,
s: &'b Summary,
method: &'b Method<'_>,
config: Option<&crate::util::config::Config>,
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
) -> ActivateResult<Vec<(Dependency, Vec<InternedString>)>> {
let dev_deps = match *method {
Method::Everything => true,
Expand Down Expand Up @@ -261,20 +245,23 @@ impl Context {
// name.
let base = reqs.deps.get(&dep.name_in_toml()).unwrap_or(&default_dep);
used_features.insert(dep.name_in_toml());
let always_required = !dep.is_optional()
&& !s
.dependencies()
.iter()
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
if always_required && base.0 {
self.warnings.push(format!(
"Package `{}` does not have feature `{}`. It has a required dependency \
with that name, but only optional dependencies can be used as features. \
This is currently a warning to ease the transition, but it will become an \
error in the future.",
s.package_id(),
dep.name_in_toml()
));
if let Some(config) = config {
let mut shell = config.shell();
let always_required = !dep.is_optional()
&& !s
.dependencies()
.iter()
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
if always_required && base.0 {
shell.warn(&format!(
"Package `{}` does not have feature `{}`. It has a required dependency \
with that name, but only optional dependencies can be used as features. \
This is currently a warning to ease the transition, but it will become an \
error in the future.",
s.package_id(),
dep.name_in_toml()
))?
}
}
let mut base = base.1.clone();
base.extend(dep.features().iter());
Expand Down Expand Up @@ -331,28 +318,26 @@ impl Context {
Ok(ret)
}

pub fn resolve_replacements(&self) -> HashMap<PackageId, PackageId> {
let mut replacements = HashMap::new();
let mut cur = &self.resolve_replacements;
while let Some(ref node) = cur.head {
let (k, v) = node.0;
replacements.insert(k, v);
cur = &node.1;
}
replacements
pub fn resolve_replacements(
&self,
registry: &RegistryQueryer<'_>,
) -> HashMap<PackageId, PackageId> {
self.activations
.values()
.filter_map(|(s, _)| registry.used_replacement_for(s.package_id()))
.collect()
}

pub fn graph(&self) -> Graph<PackageId, Vec<Dependency>> {
let mut graph: Graph<PackageId, Vec<Dependency>> = Graph::new();
let mut cur = &self.resolve_graph;
while let Some(ref node) = cur.head {
match node.0 {
GraphNode::Add(ref p) => graph.add(p.clone()),
GraphNode::Link(ref a, ref b, ref dep) => {
graph.link(a.clone(), b.clone()).push(dep.clone());
}
let mut graph = Graph::new();
self.activations
.values()
.for_each(|(r, _)| graph.add(r.package_id()));
for i in self.parents.iter() {
graph.add(*i);
for (o, e) in self.parents.edges(i) {
*graph.link(*o, *i) = e.to_vec();
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
}
cur = &node.1;
}
graph
}
Expand Down
1 change: 1 addition & 0 deletions src/cargo/core/resolver/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl fmt::Display for ResolveError {

pub type ActivateResult<T> = Result<T, ActivateError>;

#[derive(Debug)]
pub enum ActivateError {
Fatal(failure::Error),
Conflict(PackageId, ConflictReason),
Expand Down
67 changes: 52 additions & 15 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ use crate::util::errors::CargoResult;
use crate::util::profile;

use self::context::{Activations, Context};
use self::types::{Candidate, ConflictMap, ConflictReason, DepsFrame, GraphNode};
use self::types::{Candidate, ConflictMap, ConflictReason, DepsFrame};
use self::types::{RcVecIter, RegistryQueryer, RemainingDeps, ResolverProgress};

pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
Expand Down Expand Up @@ -143,7 +143,7 @@ pub fn resolve(
}
let resolve = Resolve::new(
cx.graph(),
cx.resolve_replacements(),
cx.resolve_replacements(&registry),
cx.resolve_features
.iter()
.map(|(k, v)| (*k, v.iter().map(|x| x.to_string()).collect()))
Expand All @@ -158,15 +158,8 @@ pub fn resolve(
trace!("resolved: {:?}", resolve);

// If we have a shell, emit warnings about required deps used as feature.
if let Some(config) = config {
if print_warnings {
let mut shell = config.shell();
let mut warnings = &cx.warnings;
while let Some(ref head) = warnings.head {
shell.warn(&head.0)?;
warnings = &head.1;
}
}
if print_warnings && config.is_some() {
emit_warnings(&cx, &resolve, summaries, config)
}

Ok(resolve)
Expand Down Expand Up @@ -613,8 +606,6 @@ fn activate(
let candidate_pid = candidate.summary.package_id();
if let Some((parent, dep)) = parent {
let parent_pid = parent.package_id();
cx.resolve_graph
.push(GraphNode::Link(parent_pid, candidate_pid, dep.clone()));
Rc::make_mut(
// add a edge from candidate to parent in the parents graph
cx.parents.link(candidate_pid, parent_pid),
Expand Down Expand Up @@ -675,8 +666,6 @@ fn activate(

let candidate = match candidate.replace {
Some(replace) => {
cx.resolve_replacements
.push((candidate_pid, replace.package_id()));
if cx.flag_activated(&replace, method)? && activated {
return Ok(None);
}
Expand Down Expand Up @@ -1098,3 +1087,51 @@ fn check_duplicate_pkgs_in_lockfile(resolve: &Resolve) -> CargoResult<()> {
}
Ok(())
}

/// re-run all used resolve_features so it can print warnings
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
fn emit_warnings(
cx: &Context,
resolve: &Resolve,
summaries: &[(Summary, Method<'_>)],
config: Option<&Config>,
) {
let mut new_cx = cx.clone();
new_cx.resolve_features = im_rc::HashMap::new();
let mut features_from_dep = HashMap::new();
for (summery, method) in summaries {
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
for (dep, features) in new_cx
.resolve_features(None, summery, &method, config)
.expect("can not resolve_features for a required summery")
{
features_from_dep.insert((summery.package_id(), dep), features);
}
}
for summery in resolve.sort().iter().rev().map(|id| {
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
cx.activations
.get(&id.as_activations_key())
.expect("id in dependency graph but not in activations")
.0
.clone()
}) {
for (parent, deps) in cx.parents.edges(&summery.package_id()) {
for dep in deps.iter() {
let features = features_from_dep
.remove(&(*parent, dep.clone()))
.expect("fulfilled a dep that was not needed");
let method = Method::Required {
dev_deps: false,
features: &features,
all_features: false,
uses_default_features: dep.uses_default_features(),
};
for (dep, features) in new_cx
.resolve_features(None, &summery, &method, config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to perhaps avoid re-calling resolve_features here? I found this somewhat complicated to follow, and figured it might be simpler if we simply walk over the graph after resolution, check the list of activated features for each package, and then present a warning if the feature enables a required dependency.

We may produce a slightly worse error message since we don't have why a feature is activated, but that seems reasonable given the age of this warning perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original commit f0f8565, did that. It was a lot cleaner. But it did not work. resolve_features is only adds a canonicalized feature to cx.resolve_features. So I did not find a way to reconstruct the warnings from that. (The hard cases are "invalid9", "dep_feature_in_cmd_line") Suggestions are welcome.

I also don't think we can extend that strategy to only the features transitively enabled by some deps. But I would love to be proven wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could build up a side table or something like that during resolution to facilitate this strategy of emitting warnings? I suppose that's sort of like what happens today, but I think it'd be best to decouple the warning emission as much as we can from the exact state of the resolver today, because it may make future changes to resolve_features more complicated in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, this new code that redos the work of the resolver is going to be a pane to keep in sink.

I gave up on making it work, and the future opportunities of filtering dependencies, and just made this case a resolver bactrackable error.

.expect("can not resolve_features for a used dep")
{
features_from_dep.insert((summery.package_id(), dep), features);
}
}
}
}
assert_eq!(cx.resolve_features, new_cx.resolve_features);
}
61 changes: 12 additions & 49 deletions src/cargo/core/resolver/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ pub struct RegistryQueryer<'a> {
pub registry: &'a mut (dyn Registry + 'a),
replacements: &'a [(PackageIdSpec, Dependency)],
try_to_use: &'a HashSet<PackageId>,
cache: HashMap<Dependency, Rc<Vec<Candidate>>>,
// If set the list of dependency candidates will be sorted by minimal
// versions first. That allows `cargo update -Z minimal-versions` which will
// specify minimum dependency versions to be used.
minimal_versions: bool,
cache: HashMap<Dependency, Rc<Vec<Candidate>>>,
used_replacements: HashMap<PackageId, PackageId>,
}

impl<'a> RegistryQueryer<'a> {
Expand All @@ -112,12 +113,17 @@ impl<'a> RegistryQueryer<'a> {
RegistryQueryer {
registry,
replacements,
cache: HashMap::new(),
try_to_use,
minimal_versions,
cache: HashMap::new(),
used_replacements: HashMap::new(),
}
}

pub fn used_replacement_for(&self, p: PackageId) -> Option<(PackageId, PackageId)> {
self.used_replacements.get(&p).map(|&r| (p, r))
}

/// Queries the `registry` to return a list of candidates for `dep`.
///
/// This method is the location where overrides are taken into account. If
Expand Down Expand Up @@ -212,6 +218,10 @@ impl<'a> RegistryQueryer<'a> {
for dep in summary.dependencies() {
debug!("\t{} => {}", dep.package_name(), dep.version_req());
}
if let Some(r) = &replace {
self.used_replacements
.insert(summary.package_id(), r.package_id());
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
}

candidate.replace = replace;
}
Expand Down Expand Up @@ -481,50 +491,3 @@ where
}

impl<T: Clone> ExactSizeIterator for RcVecIter<T> {}

pub struct RcList<T> {
pub head: Option<Rc<(T, RcList<T>)>>,
}

impl<T> RcList<T> {
pub fn new() -> RcList<T> {
RcList { head: None }
}

pub fn push(&mut self, data: T) {
let node = Rc::new((
data,
RcList {
head: self.head.take(),
},
));
self.head = Some(node);
}
}

// Not derived to avoid `T: Clone`
impl<T> Clone for RcList<T> {
fn clone(&self) -> RcList<T> {
RcList {
head: self.head.clone(),
}
}
}

// Avoid stack overflows on drop by turning recursion into a loop
impl<T> Drop for RcList<T> {
fn drop(&mut self) {
let mut cur = self.head.take();
while let Some(head) = cur {
match Rc::try_unwrap(head) {
Ok((_data, mut next)) => cur = next.head.take(),
Err(_) => break,
}
}
}
}

pub enum GraphNode {
Add(PackageId),
Link(PackageId, PackageId, Dependency),
}
2 changes: 1 addition & 1 deletion tests/testsuite/support/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub fn resolve_with_config_raw(
&mut registry,
&HashSet::new(),
config,
false,
true,
true,
);

Expand Down