]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/resolve.rs
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
[cargo.git] / src / cargo / ops / resolve.rs
CommitLineData
e26ef017
EH
1//! High-level APIs for executing the resolver.
2//!
3//! This module provides functions for running the resolver given a workspace.
4//! There are roughly 3 main functions:
5//!
6//! - `resolve_ws`: A simple, high-level function with no options.
7//! - `resolve_ws_with_opts`: A medium-level function with options like
8//! user-provided features. This is the most appropriate function to use in
9//! most cases.
10//! - `resolve_with_previous`: A low-level function for running the resolver,
11//! providing the most power and flexibility.
12
7caa1612 13use crate::core::compiler::{CompileKind, RustcTargetData};
fe484973 14use crate::core::registry::{LockedPatchDependency, PackageRegistry};
501499c5 15use crate::core::resolver::features::{
85854b18 16 CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures,
501499c5 17};
c4b53c95 18use crate::core::resolver::{self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion};
76f0d68d 19use crate::core::summary::Summary;
63a9c7aa 20use crate::core::Feature;
c4b53c95
AC
21use crate::core::{
22 GitReference, PackageId, PackageIdSpec, PackageSet, Source, SourceId, Workspace,
23};
04ddd4d0
DW
24use crate::ops;
25use crate::sources::PathSource;
ebca5190 26use crate::util::errors::CargoResult;
afa3aced 27use crate::util::{profile, CanonicalUrl};
ebca5190 28use anyhow::Context as _;
db80baad
EH
29use log::{debug, trace};
30use std::collections::HashSet;
b6be4038 31
12392f6d 32/// Result for `resolve_ws_with_opts`.
442e2fdc 33pub struct WorkspaceResolve<'cfg> {
12392f6d 34 /// Packages to be downloaded.
442e2fdc 35 pub pkg_set: PackageSet<'cfg>,
12392f6d
EH
36 /// The resolve for the entire workspace.
37 ///
38 /// This may be `None` for things like `cargo install` and `-Zavoid-dev-deps`.
39 /// This does not include `paths` overrides.
40 pub workspace_resolve: Option<Resolve>,
41 /// The narrowed resolve, with the specific features enabled, and only the
42 /// given package specs requested.
43 pub targeted_resolve: Resolve,
7caa1612
EH
44 /// The features activated per package.
45 pub resolved_features: ResolvedFeatures,
12392f6d
EH
46}
47
e35ecbe7
EH
48const UNUSED_PATCH_WARNING: &str = "\
49Check that the patched package version and available features are compatible
50with the dependency requirements. If the patch has a different version from
51what is locked in the Cargo.lock file, run `cargo update` to use the new
52version. This may also occur with an optional dependency that is not enabled.";
53
f7c91ba6
AR
54/// Resolves all dependencies for the workspace using the previous
55/// lock file as a guide if present.
b6be4038 56///
e26ef017
EH
57/// This function will also write the result of resolution as a new lock file
58/// (unless it is an ephemeral workspace such as `cargo install` or `cargo
59/// package`).
60///
61/// This is a simple interface used by commands like `clean`, `fetch`, and
62/// `package`, which don't specify any options or features.
bdd3b21c
AK
63pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolve)> {
64 let mut registry = PackageRegistry::new(ws.config())?;
35ff555b 65 let resolve = resolve_with_registry(ws, &mut registry)?;
468f243e 66 let packages = get_resolved_packages(&resolve, registry)?;
bdd3b21c 67 Ok((packages, resolve))
b6be4038
AC
68}
69
c7211de3
AK
70/// Resolves dependencies for some packages of the workspace,
71/// taking into account `paths` overrides and activated features.
e26ef017
EH
72///
73/// This function will also write the result of resolution as a new lock file
74/// (unless `Workspace::require_optional_deps` is false, such as `cargo
75/// install` or `-Z avoid-dev-deps`), or it is an ephemeral workspace (`cargo
76/// install` or `cargo package`).
77///
78/// `specs` may be empty, which indicates it should resolve all workspace
79/// members. In this case, `opts.all_features` must be `true`.
442e2fdc
EH
80pub fn resolve_ws_with_opts<'cfg>(
81 ws: &Workspace<'cfg>,
5e11afc1 82 target_data: &RustcTargetData<'cfg>,
3fd28143 83 requested_targets: &[CompileKind],
85854b18 84 cli_features: &CliFeatures,
1e682848 85 specs: &[PackageIdSpec],
e0d64f94 86 has_dev_units: HasDevUnits,
d267fac2 87 force_all_targets: ForceAllTargets,
442e2fdc 88) -> CargoResult<WorkspaceResolve<'cfg>> {
f00e8901 89 let mut registry = PackageRegistry::new(ws.config())?;
eebddd37 90 let mut add_patches = true;
3d893793
EH
91 let resolve = if ws.ignore_lock() {
92 None
93 } else if ws.require_optional_deps() {
db71d878
JT
94 // First, resolve the root_package's *listed* dependencies, as well as
95 // downloading and updating all remotes and such.
35ff555b 96 let resolve = resolve_with_registry(ws, &mut registry)?;
e26ef017 97 // No need to add patches again, `resolve_with_registry` has done it.
eebddd37 98 add_patches = false;
db71d878
JT
99
100 // Second, resolve with precisely what we're doing. Filter out
101 // transitive dependencies if necessary, specify features, handle
102 // overrides, etc.
f7c91ba6 103 let _p = profile::start("resolving with overrides...");
f00e8901 104
db71d878 105 add_overrides(&mut registry, ws)?;
f00e8901 106
83dc9406 107 for &(ref replace_spec, ref dep) in ws.root_replace() {
1e682848
AC
108 if !resolve
109 .iter()
110 .any(|r| replace_spec.matches(r) && !dep.matches_id(r))
111 {
112 ws.config()
113 .shell()
114 .warn(format!("package replacement is not used: {}", replace_spec))?
83dc9406
AK
115 }
116 }
117
db71d878
JT
118 Some(resolve)
119 } else {
8b475c10 120 ops::load_pkg_lockfile(ws)?
db71d878 121 };
f00e8901 122
e26ef017 123 let resolved_with_overrides = resolve_with_previous(
1e682848
AC
124 &mut registry,
125 ws,
85854b18
EH
126 cli_features,
127 has_dev_units,
1e682848
AC
128 resolve.as_ref(),
129 None,
130 specs,
131 add_patches,
1e682848 132 )?;
f00e8901 133
12392f6d 134 let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?;
f00e8901 135
944f5049 136 let member_ids = ws
85854b18 137 .members_with_features(specs, cli_features)?
944f5049
EH
138 .into_iter()
139 .map(|(p, _fts)| p.package_id())
140 .collect::<Vec<_>>();
141 pkg_set.download_accessible(
142 &resolved_with_overrides,
143 &member_ids,
144 has_dev_units,
3fd28143 145 requested_targets,
686ccfa4 146 target_data,
3d5a9083 147 force_all_targets,
944f5049
EH
148 )?;
149
501499c5 150 let feature_opts = FeatureOpts::new(ws, has_dev_units, force_all_targets)?;
7caa1612
EH
151 let resolved_features = FeatureResolver::resolve(
152 ws,
153 target_data,
154 &resolved_with_overrides,
944f5049 155 &pkg_set,
85854b18 156 cli_features,
7caa1612 157 specs,
3fd28143 158 requested_targets,
501499c5 159 feature_opts,
7caa1612
EH
160 )?;
161
12392f6d
EH
162 Ok(WorkspaceResolve {
163 pkg_set,
164 workspace_resolve: resolve,
165 targeted_resolve: resolved_with_overrides,
7caa1612 166 resolved_features,
12392f6d 167 })
f00e8901
AK
168}
169
51d23560
AC
170fn resolve_with_registry<'cfg>(
171 ws: &Workspace<'cfg>,
172 registry: &mut PackageRegistry<'cfg>,
1e682848 173) -> CargoResult<Resolve> {
bdd3b21c 174 let prev = ops::load_pkg_lockfile(ws)?;
fdbe9f8e 175 let mut resolve = resolve_with_previous(
1e682848
AC
176 registry,
177 ws,
85854b18
EH
178 &CliFeatures::new_all(true),
179 HasDevUnits::Yes,
1e682848
AC
180 prev.as_ref(),
181 None,
182 &[],
183 true,
1e682848 184 )?;
bdd3b21c 185
944f5049 186 if !ws.is_ephemeral() && ws.require_optional_deps() {
fdbe9f8e 187 ops::write_pkg_lockfile(ws, &mut resolve)?;
bdd3b21c
AK
188 }
189 Ok(resolve)
190}
191
f7c91ba6 192/// Resolves all dependencies for a package using an optional previous instance.
b6be4038
AC
193/// of resolve to guide the resolution process.
194///
816373d9 195/// This also takes an optional hash set, `to_avoid`, which is a list of package
f7c91ba6 196/// IDs that should be avoided when consulting the previous instance of resolve
816373d9
AC
197/// (often used in pairings with updates).
198///
f7c91ba6
AR
199/// The previous resolve normally comes from a lock file. This function does not
200/// read or write lock files from the filesystem.
e26ef017
EH
201///
202/// `specs` may be empty, which indicates it should resolve all workspace
203/// members. In this case, `opts.all_features` must be `true`.
204///
205/// If `register_patches` is true, then entries from the `[patch]` table in
206/// the manifest will be added to the given `PackageRegistry`.
dae87a26 207pub fn resolve_with_previous<'cfg>(
51d23560
AC
208 registry: &mut PackageRegistry<'cfg>,
209 ws: &Workspace<'cfg>,
85854b18
EH
210 cli_features: &CliFeatures,
211 has_dev_units: HasDevUnits,
dae87a26
E
212 previous: Option<&Resolve>,
213 to_avoid: Option<&HashSet<PackageId>>,
1e682848
AC
214 specs: &[PackageIdSpec],
215 register_patches: bool,
1e682848 216) -> CargoResult<Resolve> {
5217280e
AC
217 // We only want one Cargo at a time resolving a crate graph since this can
218 // involve a lot of frobbing of the global caches.
219 let _lock = ws.config().acquire_package_cache_lock()?;
220
ce14ff2f 221 // Here we place an artificial limitation that all non-registry sources
f7c91ba6 222 // cannot be locked at more than one revision. This means that if a Git
ce14ff2f
AC
223 // repository provides more than one package, they must all be updated in
224 // step when any of them are updated.
225 //
f7c91ba6
AR
226 // TODO: this seems like a hokey reason to single out the registry as being
227 // different.
f05ef874
E
228 let to_avoid_sources: HashSet<SourceId> = to_avoid
229 .map(|set| {
230 set.iter()
1e682848 231 .map(|p| p.source_id())
f05ef874
E
232 .filter(|s| !s.is_registry())
233 .collect()
234 })
235 .unwrap_or_default();
ce14ff2f 236
afa3aced 237 let pre_patch_keep = |p: &PackageId| {
e5a11190
E
238 !to_avoid_sources.contains(&p.source_id())
239 && match to_avoid {
240 Some(set) => !set.contains(p),
241 None => true,
242 }
98836bb0 243 };
61a3c68b 244
afa3aced
EH
245 // This is a set of PackageIds of `[patch]` entries that should not be
246 // locked.
247 let mut avoid_patch_ids = HashSet::new();
eebddd37 248 if register_patches {
6995e1dd 249 for (url, patches) in ws.root_patch()?.iter() {
eebddd37
AC
250 let previous = match previous {
251 Some(r) => r,
252 None => {
b78cb373 253 let patches: Vec<_> = patches.iter().map(|p| (p, None)).collect();
afa3aced
EH
254 let unlock_ids = registry.patch(url, &patches)?;
255 // Since nothing is locked, this shouldn't possibly return anything.
256 assert!(unlock_ids.is_empty());
1e682848 257 continue;
61a3c68b 258 }
eebddd37 259 };
fe484973
AC
260
261 // This is a list of pairs where the first element of the pair is
262 // the raw `Dependency` which matches what's listed in `Cargo.toml`.
263 // The second element is, if present, the "locked" version of
264 // the `Dependency` as well as the `PackageId` that it previously
265 // resolved to. This second element is calculated by looking at the
266 // previous resolve graph, which is primarily what's done here to
267 // build the `registrations` list.
268 let mut registrations = Vec::new();
269 for dep in patches {
270 let candidates = || {
271 previous
272 .iter()
273 .chain(previous.unused_patches().iter().cloned())
274 .filter(&pre_patch_keep)
275 };
276
277 let lock = match candidates().find(|id| dep.matches_id(*id)) {
278 // If we found an exactly matching candidate in our list of
279 // candidates, then that's the one to use.
280 Some(package_id) => {
281 let mut locked_dep = dep.clone();
282 locked_dep.lock_to(package_id);
283 Some(LockedPatchDependency {
284 dependency: locked_dep,
285 package_id,
286 alt_package_id: None,
287 })
288 }
289 None => {
290 // If the candidate does not have a matching source id
291 // then we may still have a lock candidate. If we're
292 // loading a v2-encoded resolve graph and `dep` is a
293 // git dep with `branch = 'master'`, then this should
294 // also match candidates without `branch = 'master'`
295 // (which is now treated separately in Cargo).
296 //
297 // In this scenario we try to convert candidates located
298 // in the resolve graph to explicitly having the
299 // `master` branch (if they otherwise point to
300 // `DefaultBranch`). If this works and our `dep`
301 // matches that then this is something we'll lock to.
302 match candidates().find(|&id| {
303 match master_branch_git_source(id, previous) {
304 Some(id) => dep.matches_id(id),
305 None => false,
306 }
307 }) {
308 Some(id_using_default) => {
309 let id_using_master = id_using_default.with_source_id(
310 dep.source_id().with_precise(
311 id_using_default
312 .source_id()
313 .precise()
314 .map(|s| s.to_string()),
315 ),
316 );
317
318 let mut locked_dep = dep.clone();
319 locked_dep.lock_to(id_using_master);
320 Some(LockedPatchDependency {
321 dependency: locked_dep,
322 package_id: id_using_master,
323 // Note that this is where the magic
324 // happens, where the resolve graph
325 // probably has locks pointing to
326 // DefaultBranch sources, and by including
327 // this here those will get transparently
328 // rewritten to Branch("master") which we
329 // have a lock entry for.
330 alt_package_id: Some(id_using_default),
331 })
332 }
333
334 // No locked candidate was found
335 None => None,
1e682848 336 }
eebddd37 337 }
fe484973
AC
338 };
339
340 registrations.push((dep, lock));
341 }
342
afa3aced 343 let canonical = CanonicalUrl::new(url)?;
fe484973 344 for (orig_patch, unlock_id) in registry.patch(url, &registrations)? {
afa3aced
EH
345 // Avoid the locked patch ID.
346 avoid_patch_ids.insert(unlock_id);
347 // Also avoid the thing it is patching.
348 avoid_patch_ids.extend(previous.iter().filter(|id| {
349 orig_patch.matches_ignoring_source(*id)
350 && *id.source_id().canonical_url() == canonical
351 }));
352 }
eebddd37 353 }
afa3aced
EH
354 }
355 debug!("avoid_patch_ids={:?}", avoid_patch_ids);
eebddd37 356
afa3aced
EH
357 let keep = |p: &PackageId| pre_patch_keep(p) && !avoid_patch_ids.contains(p);
358
3b7cb69e 359 let dev_deps = ws.require_optional_deps() || has_dev_units == HasDevUnits::Yes;
afa3aced
EH
360 // In the case where a previous instance of resolve is available, we
361 // want to lock as many packages as possible to the previous version
362 // without disturbing the graph structure.
afa3aced
EH
363 if let Some(r) = previous {
364 trace!("previous: {:?}", r);
3b7cb69e 365 register_previous_locks(ws, registry, r, &keep, dev_deps);
afa3aced 366 }
f05ef874
E
367 // Everything in the previous lock file we want to keep is prioritized
368 // in dependency selection if it comes up, aka we want to have
369 // conservative updates.
370 let try_to_use = previous
371 .map(|r| {
372 r.iter()
373 .filter(keep)
374 .inspect(|id| {
375 debug!("attempting to prefer {}", id);
376 })
377 .collect()
378 })
379 .unwrap_or_default();
afa3aced
EH
380
381 if register_patches {
eebddd37 382 registry.lock_patches();
61a3c68b
AC
383 }
384
58ddb28a 385 for member in ws.members() {
e5a11190 386 registry.add_sources(Some(member.package_id().source_id()))?;
8d0b31b9 387 }
58ddb28a 388
76f0d68d 389 let summaries: Vec<(Summary, ResolveOpts)> = ws
85854b18 390 .members_with_features(specs, cli_features)?
76f0d68d
EH
391 .into_iter()
392 .map(|(member, features)| {
d369f97c 393 let summary = registry.lock(member.summary().clone());
76f0d68d
EH
394 (
395 summary,
396 ResolveOpts {
85854b18
EH
397 dev_deps,
398 features: RequestedFeatures::CliFeatures(features),
76f0d68d
EH
399 },
400 )
401 })
402 .collect();
816373d9 403
58ddb28a
AC
404 let root_replace = ws.root_replace();
405
406 let replace = match previous {
1e682848
AC
407 Some(r) => root_replace
408 .iter()
409 .map(|&(ref spec, ref dep)| {
dae87a26 410 for (&key, &val) in r.replacements().iter() {
61a3c68b 411 if spec.matches(key) && dep.matches_id(val) && keep(&val) {
60aadc58
AC
412 let mut dep = dep.clone();
413 dep.lock_to(val);
1e682848 414 return (spec.clone(), dep);
54d738b0 415 }
816373d9 416 }
54d738b0 417 (spec.clone(), dep.clone())
1e682848
AC
418 })
419 .collect::<Vec<_>>(),
58ddb28a 420 None => root_replace.to_vec(),
b6be4038
AC
421 };
422
51d23560
AC
423 ws.preload(registry);
424 let mut resolved = resolver::resolve(
425 &summaries,
426 &replace,
427 registry,
428 &try_to_use,
429 Some(ws.config()),
36c69a18
EH
430 ws.unstable_features()
431 .require(Feature::public_dependency())
432 .is_ok(),
51d23560 433 )?;
e5454122 434 resolved.register_used_patches(&registry.patches());
7d112803 435 if register_patches {
e35ecbe7
EH
436 // It would be good if this warning was more targeted and helpful
437 // (such as showing close candidates that failed to match). However,
438 // that's not terribly easy to do, so just show a general help
439 // message.
440 let warnings: Vec<String> = resolved
441 .unused_patches()
442 .iter()
443 .map(|pkgid| format!("Patch `{}` was not used in the crate graph.", pkgid))
444 .collect();
445 if !warnings.is_empty() {
446 ws.config().shell().warn(format!(
447 "{}\n{}",
448 warnings.join("\n"),
449 UNUSED_PATCH_WARNING
450 ))?;
451 }
452 }
54d738b0 453 if let Some(previous) = previous {
82655b46 454 resolved.merge_from(previous)?;
b6be4038 455 }
23591fe5 456 Ok(resolved)
b6be4038 457}
f00e8901
AK
458
459/// Read the `paths` configuration variable to discover all path overrides that
460/// have been configured.
a418364d
E
461pub fn add_overrides<'a>(
462 registry: &mut PackageRegistry<'a>,
463 ws: &Workspace<'a>,
464) -> CargoResult<()> {
2e5796d4
EH
465 let config = ws.config();
466 let paths = match config.get_list("paths")? {
f00e8901 467 Some(list) => list,
1e682848 468 None => return Ok(()),
f00e8901
AK
469 };
470
2e5796d4 471 let paths = paths.val.iter().map(|(s, def)| {
f00e8901
AK
472 // The path listed next to the string is the config file in which the
473 // key was located, so we want to pop off the `.cargo/config` component
474 // to get the directory containing the `.cargo` folder.
2e5796d4 475 (def.root(config).join(s), def)
f00e8901
AK
476 });
477
478 for (path, definition) in paths {
479 let id = SourceId::for_path(&path)?;
e5a11190 480 let mut source = PathSource::new_recursive(&path, id, ws.config());
ebca5190 481 source.update().with_context(|| {
1e682848
AC
482 format!(
483 "failed to update path override `{}` \
484 (defined in `{}`)",
485 path.display(),
2e5796d4 486 definition
1e682848 487 )
f00e8901 488 })?;
b02023ee 489 registry.add_override(Box::new(source));
f00e8901
AK
490 }
491 Ok(())
492}
bdd3b21c 493
442e2fdc 494pub fn get_resolved_packages<'cfg>(
a418364d 495 resolve: &Resolve,
442e2fdc
EH
496 registry: PackageRegistry<'cfg>,
497) -> CargoResult<PackageSet<'cfg>> {
dae87a26 498 let ids: Vec<PackageId> = resolve.iter().collect();
bdd3b21c
AK
499 registry.get(&ids)
500}
51d23560
AC
501
502/// In this function we're responsible for informing the `registry` of all
503/// locked dependencies from the previous lock file we had, `resolve`.
504///
505/// This gets particularly tricky for a couple of reasons. The first is that we
506/// want all updates to be conservative, so we actually want to take the
507/// `resolve` into account (and avoid unnecessary registry updates and such).
508/// the second, however, is that we want to be resilient to updates of
509/// manifests. For example if a dependency is added or a version is changed we
510/// want to make sure that we properly re-resolve (conservatively) instead of
511/// providing an opaque error.
512///
f7c91ba6
AR
513/// The logic here is somewhat subtle, but there should be more comments below to
514/// clarify things.
51d23560
AC
515///
516/// Note that this function, at the time of this writing, is basically the
f7c91ba6 517/// entire fix for issue #4127.
dae87a26 518fn register_previous_locks(
b8b7faee
AC
519 ws: &Workspace<'_>,
520 registry: &mut PackageRegistry<'_>,
dae87a26 521 resolve: &Resolve,
b8b7faee 522 keep: &dyn Fn(&PackageId) -> bool,
3b7cb69e 523 dev_deps: bool,
51d23560 524) {
e5a11190 525 let path_pkg = |id: SourceId| {
0deaae9e
AC
526 if !id.is_path() {
527 return None;
528 }
529 if let Ok(path) = id.url().to_file_path() {
530 if let Ok(pkg) = ws.load(&path.join("Cargo.toml")) {
531 return Some(pkg);
532 }
533 }
534 None
535 };
536
51d23560 537 // Ok so we've been passed in a `keep` function which basically says "if I
f7c91ba6
AR
538 // return `true` then this package wasn't listed for an update on the command
539 // line". That is, if we run `cargo update -p foo` then `keep(bar)` will return
540 // `true`, whereas `keep(foo)` will return `false` (roughly speaking).
51d23560
AC
541 //
542 // This isn't actually quite what we want, however. Instead we want to
543 // further refine this `keep` function with *all transitive dependencies* of
f7c91ba6 544 // the packages we're not keeping. For example, consider a case like this:
51d23560 545 //
f7c91ba6
AR
546 // * There's a crate `log`.
547 // * There's a crate `serde` which depends on `log`.
51d23560
AC
548 //
549 // Let's say we then run `cargo update -p serde`. This may *also* want to
550 // update the `log` dependency as our newer version of `serde` may have a
551 // new minimum version required for `log`. Now this isn't always guaranteed
552 // to work. What'll happen here is we *won't* lock the `log` dependency nor
553 // the `log` crate itself, but we will inform the registry "please prefer
554 // this version of `log`". That way if our newer version of serde works with
555 // the older version of `log`, we conservatively won't update `log`. If,
556 // however, nothing else in the dependency graph depends on `log` and the
557 // newer version of `serde` requires a new version of `log` it'll get pulled
558 // in (as we didn't accidentally lock it to an old version).
0deaae9e 559 //
f7c91ba6 560 // Additionally, here we process all path dependencies listed in the previous
0deaae9e
AC
561 // resolve. They can not only have their dependencies change but also
562 // the versions of the package change as well. If this ends up happening
f7c91ba6 563 // then we want to make sure we don't lock a package ID node that doesn't
0deaae9e
AC
564 // actually exist. Note that we don't do transitive visits of all the
565 // package's dependencies here as that'll be covered below to poison those
566 // if they changed.
51d23560 567 let mut avoid_locking = HashSet::new();
2bbce850 568 registry.add_to_yanked_whitelist(resolve.iter().filter(keep));
51d23560
AC
569 for node in resolve.iter() {
570 if !keep(&node) {
571 add_deps(resolve, node, &mut avoid_locking);
0deaae9e
AC
572 } else if let Some(pkg) = path_pkg(node.source_id()) {
573 if pkg.package_id() != node {
574 avoid_locking.insert(node);
575 }
51d23560
AC
576 }
577 }
578
f7c91ba6 579 // Ok, but the above loop isn't the entire story! Updates to the dependency
51d23560
AC
580 // graph can come from two locations, the `cargo update` command or
581 // manifests themselves. For example a manifest on the filesystem may
582 // have been updated to have an updated version requirement on `serde`. In
583 // this case both `keep(serde)` and `keep(log)` return `true` (the `keep`
584 // that's an argument to this function). We, however, don't want to keep
585 // either of those! Otherwise we'll get obscure resolve errors about locked
586 // versions.
587 //
588 // To solve this problem we iterate over all packages with path sources
589 // (aka ones with manifests that are changing) and take a look at all of
590 // their dependencies. If any dependency does not match something in the
591 // previous lock file, then we're guaranteed that the main resolver will
592 // update the source of this dependency no matter what. Knowing this we
593 // poison all packages from the same source, forcing them all to get
594 // updated.
595 //
596 // This may seem like a heavy hammer, and it is! It means that if you change
597 // anything from crates.io then all of crates.io becomes unlocked. Note,
598 // however, that we still want conservative updates. This currently happens
599 // because the first candidate the resolver picks is the previously locked
600 // version, and only if that fails to activate to we move on and try
601 // a different version. (giving the guise of conservative updates)
602 //
603 // For example let's say we had `serde = "0.1"` written in our lock file.
604 // When we later edit this to `serde = "0.1.3"` we don't want to lock serde
605 // at its old version, 0.1.1. Instead we want to allow it to update to
606 // `0.1.3` and update its own dependencies (like above). To do this *all
607 // crates from crates.io* are not locked (aka added to `avoid_locking`).
608 // For dependencies like `log` their previous version in the lock file will
609 // come up first before newer version, if newer version are available.
610 let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
611 let mut visited = HashSet::new();
612 while let Some(member) = path_deps.pop() {
dae87a26 613 if !visited.insert(member.package_id()) {
51d23560
AC
614 continue;
615 }
d274fba0 616 let is_ws_member = ws.is_member(&member);
51d23560 617 for dep in member.dependencies() {
0790db98
AC
618 // If this dependency didn't match anything special then we may want
619 // to poison the source as it may have been added. If this path
f7c91ba6 620 // dependencies is **not** a workspace member, however, and it's an
0790db98
AC
621 // optional/non-transitive dependency then it won't be necessarily
622 // be in our lock file. If this shows up then we avoid poisoning
623 // this source as otherwise we'd repeatedly update the registry.
624 //
625 // TODO: this breaks adding an optional dependency in a
f7c91ba6
AR
626 // non-workspace member and then simultaneously editing the
627 // dependency on that crate to enable the feature. For now,
628 // this bug is better than the always-updating registry though.
d274fba0 629 if !is_ws_member && (dep.is_optional() || !dep.is_transitive()) {
5c9d34be 630 continue;
0790db98
AC
631 }
632
3b7cb69e
EH
633 // If dev-dependencies aren't being resolved, skip them.
634 if !dep.is_transitive() && !dev_deps {
635 continue;
636 }
637
f7c91ba6
AR
638 // If this is a path dependency, then try to push it onto our
639 // worklist.
c7a11056
AC
640 if let Some(pkg) = path_pkg(dep.source_id()) {
641 path_deps.push(pkg);
642 continue;
643 }
644
645 // If we match *anything* in the dependency graph then we consider
f7c91ba6 646 // ourselves all ok, and assume that we'll resolve to that.
c7a11056
AC
647 if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
648 continue;
649 }
650
f7c91ba6 651 // Ok if nothing matches, then we poison the source of these
0790db98 652 // dependencies and the previous lock file.
5c241e10
DO
653 debug!(
654 "poisoning {} because {} looks like it changed {}",
655 dep.source_id(),
656 member.package_id(),
5295cadd 657 dep.package_name()
5c241e10
DO
658 );
659 for id in resolve
660 .iter()
661 .filter(|id| id.source_id() == dep.source_id())
662 {
51d23560
AC
663 add_deps(resolve, id, &mut avoid_locking);
664 }
665 }
666 }
667
668 // Alright now that we've got our new, fresh, shiny, and refined `keep`
f7c91ba6 669 // function let's put it to action. Take a look at the previous lock file,
51d23560
AC
670 // filter everything by this callback, and then shove everything else into
671 // the registry as a locked dependency.
dae87a26 672 let keep = |id: &PackageId| keep(id) && !avoid_locking.contains(id);
51d23560 673
61e59278 674 registry.clear_lock();
51d23560 675 for node in resolve.iter().filter(keep) {
0bd1d34c
AC
676 let deps = resolve
677 .deps_not_replaced(node)
678 .map(|p| p.0)
679 .filter(keep)
c4b53c95
AC
680 .collect::<Vec<_>>();
681
682 // In the v2 lockfile format and prior the `branch=master` dependency
683 // directive was serialized the same way as the no-branch-listed
684 // directive. Nowadays in Cargo, however, these two directives are
685 // considered distinct and are no longer represented the same way. To
686 // maintain compatibility with older lock files we register locked nodes
687 // for *both* the master branch and the default branch.
688 //
689 // Note that this is only applicable for loading older resolves now at
690 // this point. All new lock files are encoded as v3-or-later, so this is
691 // just compat for loading an old lock file successfully.
fe484973
AC
692 if let Some(node) = master_branch_git_source(node, resolve) {
693 registry.register_lock(node, deps.clone());
c4b53c95
AC
694 }
695
dae87a26 696 registry.register_lock(node, deps);
51d23560
AC
697 }
698
f7c91ba6 699 /// Recursively add `node` and all its transitive dependencies to `set`.
dae87a26 700 fn add_deps(resolve: &Resolve, node: PackageId, set: &mut HashSet<PackageId>) {
51d23560
AC
701 if !set.insert(node) {
702 return;
703 }
704 debug!("ignoring any lock pointing directly at {}", node);
0bd1d34c 705 for (dep, _) in resolve.deps_not_replaced(node) {
51d23560
AC
706 add_deps(resolve, dep, set);
707 }
708 }
709}
fe484973
AC
710
711fn master_branch_git_source(id: PackageId, resolve: &Resolve) -> Option<PackageId> {
712 if resolve.version() <= ResolveVersion::V2 {
713 let source = id.source_id();
714 if let Some(GitReference::DefaultBranch) = source.git_reference() {
715 let new_source =
716 SourceId::for_git(source.url(), GitReference::Branch("master".to_string()))
717 .unwrap()
718 .with_precise(source.precise().map(|s| s.to_string()));
719 return Some(id.with_source_id(new_source));
720 }
721 }
722 None
723}