]>
Commit | Line | Data |
---|---|---|
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 | 13 | use crate::core::compiler::{CompileKind, RustcTargetData}; |
fe484973 | 14 | use crate::core::registry::{LockedPatchDependency, PackageRegistry}; |
501499c5 | 15 | use crate::core::resolver::features::{ |
85854b18 | 16 | CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures, |
501499c5 | 17 | }; |
c4b53c95 | 18 | use crate::core::resolver::{self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion}; |
76f0d68d | 19 | use crate::core::summary::Summary; |
63a9c7aa | 20 | use crate::core::Feature; |
c4b53c95 AC |
21 | use crate::core::{ |
22 | GitReference, PackageId, PackageIdSpec, PackageSet, Source, SourceId, Workspace, | |
23 | }; | |
04ddd4d0 DW |
24 | use crate::ops; |
25 | use crate::sources::PathSource; | |
ebca5190 | 26 | use crate::util::errors::CargoResult; |
afa3aced | 27 | use crate::util::{profile, CanonicalUrl}; |
ebca5190 | 28 | use anyhow::Context as _; |
db80baad EH |
29 | use log::{debug, trace}; |
30 | use std::collections::HashSet; | |
b6be4038 | 31 | |
12392f6d | 32 | /// Result for `resolve_ws_with_opts`. |
442e2fdc | 33 | pub 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 |
48 | const UNUSED_PATCH_WARNING: &str = "\ |
49 | Check that the patched package version and available features are compatible | |
50 | with the dependency requirements. If the patch has a different version from | |
51 | what is locked in the Cargo.lock file, run `cargo update` to use the new | |
52 | version. 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 |
63 | pub 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 |
80 | pub 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 |
170 | fn 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 | 207 | pub 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, ®istrations)? { |
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(®istry.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 |
461 | pub 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 | 494 | pub 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 | 518 | fn 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 | |
711 | fn 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 | } |