use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::{FeatureResolver, ResolvedFeatures};
use crate::core::resolver::{self, HasDevUnits, Resolve, ResolveOpts};
use crate::core::summary::Summary;
use crate::core::Feature;
use crate::core::{PackageId, PackageIdSpec, PackageSet, Source, SourceId, Workspace};
use crate::ops;
use crate::sources::PathSource;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::profile;
use log::{debug, trace};
use std::collections::HashSet;
pub struct WorkspaceResolve<'cfg> {
pub pkg_set: PackageSet<'cfg>,
pub workspace_resolve: Option<Resolve>,
pub targeted_resolve: Resolve,
pub resolved_features: ResolvedFeatures,
}
const UNUSED_PATCH_WARNING: &str = "\
Check that the patched package version and available features are compatible
with the dependency requirements. If the patch has a different version from
what is locked in the Cargo.lock file, run `cargo update` to use the new
version. This may also occur with an optional dependency that is not enabled.";
pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolve)> {
let mut registry = PackageRegistry::new(ws.config())?;
let resolve = resolve_with_registry(ws, &mut registry)?;
let packages = get_resolved_packages(&resolve, registry)?;
Ok((packages, resolve))
}
pub fn resolve_ws_with_opts<'cfg>(
ws: &Workspace<'cfg>,
target_data: &RustcTargetData,
requested_target: CompileKind,
opts: &ResolveOpts,
specs: &[PackageIdSpec],
has_dev_units: HasDevUnits,
) -> CargoResult<WorkspaceResolve<'cfg>> {
let mut registry = PackageRegistry::new(ws.config())?;
let mut add_patches = true;
let resolve = if ws.ignore_lock() {
None
} else if ws.require_optional_deps() {
let resolve = resolve_with_registry(ws, &mut registry)?;
add_patches = false;
let _p = profile::start("resolving with overrides...");
add_overrides(&mut registry, ws)?;
for &(ref replace_spec, ref dep) in ws.root_replace() {
if !resolve
.iter()
.any(|r| replace_spec.matches(r) && !dep.matches_id(r))
{
ws.config()
.shell()
.warn(format!("package replacement is not used: {}", replace_spec))?
}
}
Some(resolve)
} else {
ops::load_pkg_lockfile(ws)?
};
let resolved_with_overrides = resolve_with_previous(
&mut registry,
ws,
opts,
resolve.as_ref(),
None,
specs,
add_patches,
)?;
let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?;
let member_ids = ws
.members_with_features(&specs, &opts.features)?
.into_iter()
.map(|(p, _fts)| p.package_id())
.collect::<Vec<_>>();
pkg_set.download_accessible(
&resolved_with_overrides,
&member_ids,
has_dev_units,
requested_target,
&target_data,
)?;
let resolved_features = FeatureResolver::resolve(
ws,
target_data,
&resolved_with_overrides,
&pkg_set,
&opts.features,
specs,
requested_target,
has_dev_units,
)?;
Ok(WorkspaceResolve {
pkg_set,
workspace_resolve: resolve,
targeted_resolve: resolved_with_overrides,
resolved_features,
})
}
fn resolve_with_registry<'cfg>(
ws: &Workspace<'cfg>,
registry: &mut PackageRegistry<'cfg>,
) -> CargoResult<Resolve> {
let prev = ops::load_pkg_lockfile(ws)?;
let resolve = resolve_with_previous(
registry,
ws,
&ResolveOpts::everything(),
prev.as_ref(),
None,
&[],
true,
)?;
if !ws.is_ephemeral() && ws.require_optional_deps() {
ops::write_pkg_lockfile(ws, &resolve)?;
}
Ok(resolve)
}
pub fn resolve_with_previous<'cfg>(
registry: &mut PackageRegistry<'cfg>,
ws: &Workspace<'cfg>,
opts: &ResolveOpts,
previous: Option<&Resolve>,
to_avoid: Option<&HashSet<PackageId>>,
specs: &[PackageIdSpec],
register_patches: bool,
) -> CargoResult<Resolve> {
let _lock = ws.config().acquire_package_cache_lock()?;
let mut to_avoid_sources: HashSet<SourceId> = HashSet::new();
if let Some(to_avoid) = to_avoid {
to_avoid_sources.extend(
to_avoid
.iter()
.map(|p| p.source_id())
.filter(|s| !s.is_registry()),
);
}
let keep = |p: &PackageId| {
!to_avoid_sources.contains(&p.source_id())
&& match to_avoid {
Some(set) => !set.contains(p),
None => true,
}
};
let mut try_to_use = HashSet::new();
if let Some(r) = previous {
trace!("previous: {:?}", r);
register_previous_locks(ws, registry, r, &keep);
try_to_use.extend(r.iter().filter(keep).inspect(|id| {
debug!("attempting to prefer {}", id);
}));
}
if register_patches {
for (url, patches) in ws.root_patch() {
let previous = match previous {
Some(r) => r,
None => {
registry.patch(url, patches)?;
continue;
}
};
let patches = patches
.iter()
.map(|dep| {
let unused = previous.unused_patches().iter().cloned();
let candidates = previous.iter().chain(unused);
match candidates.filter(keep).find(|&id| dep.matches_id(id)) {
Some(id) => {
let mut dep = dep.clone();
dep.lock_to(id);
dep
}
None => dep.clone(),
}
})
.collect::<Vec<_>>();
registry.patch(url, &patches)?;
}
registry.lock_patches();
}
for member in ws.members() {
registry.add_sources(Some(member.package_id().source_id()))?;
}
let summaries: Vec<(Summary, ResolveOpts)> = ws
.members_with_features(specs, &opts.features)?
.into_iter()
.map(|(member, features)| {
let summary = registry.lock(member.summary().clone());
(
summary,
ResolveOpts {
dev_deps: opts.dev_deps,
features,
},
)
})
.collect();
let root_replace = ws.root_replace();
let replace = match previous {
Some(r) => root_replace
.iter()
.map(|&(ref spec, ref dep)| {
for (&key, &val) in r.replacements().iter() {
if spec.matches(key) && dep.matches_id(val) && keep(&val) {
let mut dep = dep.clone();
dep.lock_to(val);
return (spec.clone(), dep);
}
}
(spec.clone(), dep.clone())
})
.collect::<Vec<_>>(),
None => root_replace.to_vec(),
};
ws.preload(registry);
let mut resolved = resolver::resolve(
&summaries,
&replace,
registry,
&try_to_use,
Some(ws.config()),
ws.features().require(Feature::public_dependency()).is_ok(),
)?;
resolved.register_used_patches(®istry.patches());
if register_patches {
let warnings: Vec<String> = resolved
.unused_patches()
.iter()
.map(|pkgid| format!("Patch `{}` was not used in the crate graph.", pkgid))
.collect();
if !warnings.is_empty() {
ws.config().shell().warn(format!(
"{}\n{}",
warnings.join("\n"),
UNUSED_PATCH_WARNING
))?;
}
}
if let Some(previous) = previous {
resolved.merge_from(previous)?;
}
Ok(resolved)
}
pub fn add_overrides<'a>(
registry: &mut PackageRegistry<'a>,
ws: &Workspace<'a>,
) -> CargoResult<()> {
let config = ws.config();
let paths = match config.get_list("paths")? {
Some(list) => list,
None => return Ok(()),
};
let paths = paths.val.iter().map(|(s, def)| {
(def.root(config).join(s), def)
});
for (path, definition) in paths {
let id = SourceId::for_path(&path)?;
let mut source = PathSource::new_recursive(&path, id, ws.config());
source.update().chain_err(|| {
format!(
"failed to update path override `{}` \
(defined in `{}`)",
path.display(),
definition
)
})?;
registry.add_override(Box::new(source));
}
Ok(())
}
pub fn get_resolved_packages<'cfg>(
resolve: &Resolve,
registry: PackageRegistry<'cfg>,
) -> CargoResult<PackageSet<'cfg>> {
let ids: Vec<PackageId> = resolve.iter().collect();
registry.get(&ids)
}
fn register_previous_locks(
ws: &Workspace<'_>,
registry: &mut PackageRegistry<'_>,
resolve: &Resolve,
keep: &dyn Fn(&PackageId) -> bool,
) {
let path_pkg = |id: SourceId| {
if !id.is_path() {
return None;
}
if let Ok(path) = id.url().to_file_path() {
if let Ok(pkg) = ws.load(&path.join("Cargo.toml")) {
return Some(pkg);
}
}
None
};
let mut avoid_locking = HashSet::new();
registry.add_to_yanked_whitelist(resolve.iter().filter(keep));
for node in resolve.iter() {
if !keep(&node) {
add_deps(resolve, node, &mut avoid_locking);
} else if let Some(pkg) = path_pkg(node.source_id()) {
if pkg.package_id() != node {
avoid_locking.insert(node);
}
}
}
let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
let mut visited = HashSet::new();
while let Some(member) = path_deps.pop() {
if !visited.insert(member.package_id()) {
continue;
}
let is_ws_member = ws.is_member(&member);
for dep in member.dependencies() {
if !is_ws_member && (dep.is_optional() || !dep.is_transitive()) {
continue;
}
if let Some(pkg) = path_pkg(dep.source_id()) {
path_deps.push(pkg);
continue;
}
if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
continue;
}
debug!(
"poisoning {} because {} looks like it changed {}",
dep.source_id(),
member.package_id(),
dep.package_name()
);
for id in resolve
.iter()
.filter(|id| id.source_id() == dep.source_id())
{
add_deps(resolve, id, &mut avoid_locking);
}
}
}
let keep = |id: &PackageId| keep(id) && !avoid_locking.contains(id);
for node in resolve.iter().filter(keep) {
let deps = resolve
.deps_not_replaced(node)
.map(|p| p.0)
.filter(keep)
.collect();
registry.register_lock(node, deps);
}
fn add_deps(resolve: &Resolve, node: PackageId, set: &mut HashSet<PackageId>) {
if !set.insert(node) {
return;
}
debug!("ignoring any lock pointing directly at {}", node);
for (dep, _) in resolve.deps_not_replaced(node) {
add_deps(resolve, dep, set);
}
}
}