Skip to main content

guppy/graph/cargo/
cargo_api.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    Error, PackageId,
6    graph::{
7        DependencyDirection, PackageGraph, PackageIx, PackageLink, PackageLinkContext, PackageSet,
8        cargo::build::CargoSetBuildState,
9        feature::{FeatureGraph, FeatureSet},
10    },
11    platform::PlatformSpec,
12    sorted_set::SortedSet,
13};
14use petgraph::prelude::*;
15use serde::{Deserialize, Serialize};
16use std::{collections::HashSet, fmt};
17
18/// Options for queries which simulate what Cargo does.
19///
20/// This provides control over the resolution algorithm used by `guppy`'s simulation of Cargo.
21#[derive(Clone, Debug)]
22pub struct CargoOptions<'a> {
23    pub(crate) resolver: CargoResolverVersion,
24    pub(crate) include_dev: bool,
25    pub(crate) initials_platform: InitialsPlatform,
26    pub(crate) host_platform: PlatformSpec,
27    pub(crate) target_platform: PlatformSpec,
28    pub(crate) omitted_packages: HashSet<&'a PackageId>,
29}
30
31impl<'a> CargoOptions<'a> {
32    /// Creates a new `CargoOptions` with default settings.
33    ///
34    /// The default settings are similar to what a plain `cargo build` does:
35    ///
36    /// * use version 3 of the Cargo resolver
37    /// * exclude dev-dependencies
38    /// * do not build proc macros specified in the query on the target platform
39    /// * resolve dependencies assuming any possible host or target platform
40    /// * do not omit any packages.
41    pub fn new() -> Self {
42        Self {
43            resolver: CargoResolverVersion::V3,
44            include_dev: false,
45            initials_platform: InitialsPlatform::Standard,
46            host_platform: PlatformSpec::Any,
47            target_platform: PlatformSpec::Any,
48            omitted_packages: HashSet::new(),
49        }
50    }
51
52    /// Sets the Cargo feature resolver version.
53    ///
54    /// For more about feature resolution, see the documentation for `CargoResolverVersion`.
55    pub fn set_resolver(&mut self, resolver: CargoResolverVersion) -> &mut Self {
56        self.resolver = resolver;
57        self
58    }
59
60    /// If set to true, causes dev-dependencies of the initial set to be followed.
61    ///
62    /// This does not affect transitive dependencies -- for example, a build or dev-dependency's
63    /// further dev-dependencies are never followed.
64    ///
65    /// The default is false, which matches what a plain `cargo build` does.
66    pub fn set_include_dev(&mut self, include_dev: bool) -> &mut Self {
67        self.include_dev = include_dev;
68        self
69    }
70
71    /// Configures the way initials are treated on the target and the host.
72    ///
73    /// The default is a "standard" build and this does not usually need to be set, but some
74    /// advanced use cases may require it. For more about this option, see the documentation for
75    /// [`InitialsPlatform`](InitialsPlatform).
76    pub fn set_initials_platform(&mut self, initials_platform: InitialsPlatform) -> &mut Self {
77        self.initials_platform = initials_platform;
78        self
79    }
80
81    /// Sets both the target and host platforms to the provided spec.
82    pub fn set_platform(&mut self, platform_spec: impl Into<PlatformSpec>) -> &mut Self {
83        let platform_spec = platform_spec.into();
84        self.target_platform = platform_spec.clone();
85        self.host_platform = platform_spec;
86        self
87    }
88
89    /// Sets the target platform to the provided spec.
90    pub fn set_target_platform(&mut self, target_platform: impl Into<PlatformSpec>) -> &mut Self {
91        self.target_platform = target_platform.into();
92        self
93    }
94
95    /// Sets the host platform to the provided spec.
96    pub fn set_host_platform(&mut self, host_platform: impl Into<PlatformSpec>) -> &mut Self {
97        self.host_platform = host_platform.into();
98        self
99    }
100
101    /// Omits edges into the given packages.
102    ///
103    /// This may be useful in order to figure out what additional dependencies or features a
104    /// particular set of packages pulls in.
105    ///
106    /// This method is additive.
107    pub fn add_omitted_packages(
108        &mut self,
109        package_ids: impl IntoIterator<Item = &'a PackageId>,
110    ) -> &mut Self {
111        self.omitted_packages.extend(package_ids);
112        self
113    }
114
115    // Note that the lifetime of the returned value is `'g` rather than being
116    // tied to `self`.
117    pub(crate) fn resolve_package_ids<'g>(
118        &self,
119        graph: &'g PackageGraph,
120    ) -> Result<CargoOptions<'g>, Error> {
121        let omitted_packages = self
122            .omitted_packages
123            .iter()
124            .map(|package_id| graph.metadata(package_id).map(|metadata| metadata.id()))
125            .collect::<Result<HashSet<_>, _>>()?;
126
127        Ok(CargoOptions {
128            resolver: self.resolver,
129            include_dev: self.include_dev,
130            initials_platform: self.initials_platform,
131            host_platform: self.host_platform.clone(),
132            target_platform: self.target_platform.clone(),
133            omitted_packages,
134        })
135    }
136}
137
138impl Default for CargoOptions<'_> {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/// Represents whether a particular link within a package graph should be
145/// followed while building a [`CargoSet`].
146///
147/// This is similar to [`PackageLinkVisitor`], but is passed a
148/// [`CargoLinkContext`] with additional information about the Cargo build.
149///
150/// [`PackageLinkVisitor`]: crate::graph::PackageLinkVisitor
151pub trait CargoLinkVisitor<'g> {
152    /// Returns true if this link should be followed.
153    ///
154    /// Returning false does not prevent the `to` package from being included
155    /// if it's reachable through other means.
156    fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool;
157}
158
159impl<'g, T> CargoLinkVisitor<'g> for &mut T
160where
161    T: CargoLinkVisitor<'g>,
162{
163    fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
164        (**self).visit_link(cx, link)
165    }
166}
167
168impl<'g> CargoLinkVisitor<'g> for Box<dyn CargoLinkVisitor<'g> + '_> {
169    fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
170        (**self).visit_link(cx, link)
171    }
172}
173
174impl<'g> CargoLinkVisitor<'g> for &mut dyn CargoLinkVisitor<'g> {
175    fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
176        (**self).visit_link(cx, link)
177    }
178}
179
180/// Context passed to a [`CargoLinkVisitor`] for each link visited while
181/// building a [`CargoSet`].
182#[derive(Clone, Debug)]
183pub struct CargoLinkContext<'a, 'g> {
184    package_context: &'a PackageLinkContext<'g>,
185    build_platform: BuildPlatform,
186    platform_spec: &'a PlatformSpec,
187    build_dep_platform_spec: &'a PlatformSpec,
188    include_dev: bool,
189}
190
191impl<'a, 'g> CargoLinkContext<'a, 'g> {
192    pub(super) fn new(
193        package_context: &'a PackageLinkContext<'g>,
194        build_platform: BuildPlatform,
195        opts: &'a CargoOptions<'_>,
196    ) -> Self {
197        let (platform_spec, build_dep_platform_spec) = match build_platform {
198            BuildPlatform::Target => (&opts.target_platform, &opts.host_platform),
199            BuildPlatform::Host => (&opts.host_platform, &opts.host_platform),
200        };
201        Self {
202            package_context,
203            build_platform,
204            platform_spec,
205            build_dep_platform_spec,
206            include_dev: opts.include_dev,
207        }
208    }
209
210    /// Returns the context for the underlying package graph traversal.
211    pub fn package_context(&self) -> &PackageLinkContext<'g> {
212        self.package_context
213    }
214
215    /// Returns the platform this link is being evaluated for: the target pass
216    /// or the host pass.
217    pub fn build_platform(&self) -> BuildPlatform {
218        self.build_platform
219    }
220
221    /// Returns the platform spec that normal and dev dependencies are
222    /// evaluated against in this pass.
223    pub fn platform_spec(&self) -> &PlatformSpec {
224        self.platform_spec
225    }
226
227    /// Returns the platform spec that build dependencies are evaluated against
228    /// in this pass (always the host platform).
229    pub fn build_dep_platform_spec(&self) -> &PlatformSpec {
230        self.build_dep_platform_spec
231    }
232
233    /// Returns true if dev-dependencies of this link may be followed:
234    /// [`CargoOptions::set_include_dev`] is set and the `from` package is an
235    /// initial.
236    pub fn considers_dev_deps(&self, link: &PackageLink<'g>) -> bool {
237        self.include_dev && self.package_context.starts_from_initial(link)
238    }
239
240    /// Returns true if build-dependencies of this link may be followed: the
241    /// `from` package has a build script.
242    pub fn considers_build_deps(&self, link: &PackageLink<'g>) -> bool {
243        link.from().has_build_script()
244    }
245}
246
247/// The version of Cargo's feature resolver to use.
248#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
249#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
250#[serde(rename_all = "kebab-case")]
251#[non_exhaustive]
252pub enum CargoResolverVersion {
253    /// The "classic" feature resolver in Rust.
254    ///
255    /// This feature resolver unifies features across inactive platforms, and also unifies features
256    /// across normal, build and dev dependencies for initials. This may produce results that are
257    /// surprising at times.
258    #[serde(rename = "1", alias = "v1")]
259    V1,
260
261    /// The "classic" feature resolver in Rust, as used by commands like `cargo install`.
262    ///
263    /// This resolver is the same as `V1`, except it doesn't unify features across dev dependencies
264    /// for initials. However, if `CargoOptions::set_include_dev` is set to true, it behaves
265    /// identically to the V1 resolver.
266    ///
267    /// For more, see
268    /// [avoid-dev-deps](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#avoid-dev-deps)
269    /// in the Cargo reference.
270    #[serde(rename = "install", alias = "v1-install")]
271    V1Install,
272
273    /// [Version 2 of the feature resolver](https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2),
274    /// available since Rust 1.51. This feature resolver does not unify features:
275    ///
276    /// * across host (build) and target (regular) dependencies
277    /// * with dev-dependencies for initials, if tests aren't currently being built
278    /// * with [platform-specific dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies) that are currently inactive
279    ///
280    /// Version 2 of the feature resolver can be enabled by specifying `resolver
281    /// = "2"` in the workspace's `Cargo.toml`. It is also [the default resolver
282    /// version](https://doc.rust-lang.org/beta/edition-guide/rust-2021/default-cargo-resolver.html)
283    /// for [the Rust 2021
284    /// edition](https://doc.rust-lang.org/edition-guide/rust-2021/index.html).
285    #[serde(rename = "2", alias = "v2")]
286    V2,
287
288    /// [Version 3 of the dependency
289    /// resolver](https://doc.rust-lang.org/beta/cargo/reference/resolver.html#resolver-versions),
290    /// available since Rust 1.84.
291    ///
292    /// Version 3 of the resolver enables [MSRV-aware dependency
293    /// resolution](https://doc.rust-lang.org/beta/cargo/reference/config.html#resolverincompatible-rust-versions).
294    /// There are no changes to feature resolution compared to version 2.
295    ///
296    /// Version 3 of the feature resolver can be enabled by specifying `resolver
297    /// = "3"` in the workspace's `Cargo.toml`. It is also [the default resolver
298    /// version](https://doc.rust-lang.org/beta/edition-guide/rust-2024/cargo-resolver.html)
299    /// for [the Rust 2024
300    /// edition](https://doc.rust-lang.org/beta/edition-guide/rust-2024/index.html).
301    #[serde(rename = "3", alias = "v3")]
302    V3,
303}
304
305/// For a given Cargo build simulation, what platform to assume the initials are being built on.
306#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
307#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
308#[serde(rename_all = "kebab-case")]
309pub enum InitialsPlatform {
310    /// Assume that the initials are being built on the host platform.
311    ///
312    /// This is most useful for "continuing" simulations, where it is already known that some
313    /// packages are being built on the host and one wishes to find their dependencies.
314    Host,
315
316    /// Assume a standard build.
317    ///
318    /// In this mode, all initials other than proc-macros are built on the target platform. Proc-
319    /// macros, being compiler plugins, are built on the host.
320    ///
321    /// This is the default for `InitialsPlatform`.
322    Standard,
323
324    /// Perform a standard build, and also build proc-macros on the target.
325    ///
326    /// Proc-macro crates may include tests, which are run on the target platform. This option is
327    /// most useful for such situations.
328    ProcMacrosOnTarget,
329}
330
331/// The default for `InitialsPlatform`: the `Standard` option.
332impl Default for InitialsPlatform {
333    fn default() -> Self {
334        InitialsPlatform::Standard
335    }
336}
337
338/// The inputs to a `CargoSet` other than the initials.
339///
340/// The initials are kept separate so that one `CargoSetInputs` can be reused
341/// across many `CargoSet`s via [`to_cargo_set`](Self::to_cargo_set).
342///
343/// Both fields must refer to the same `PackageGraph` as the initials they
344/// are used with.
345#[derive(Clone, Debug)]
346#[non_exhaustive]
347pub struct CargoSetInputs<'g> {
348    /// The Cargo options used to build the `CargoSet`.
349    pub options: CargoOptions<'g>,
350
351    /// The features-only set: see [`CargoSet::new`].
352    pub features_only: FeatureSet<'g>,
353}
354
355assert_covariant!(CargoSetInputs);
356
357impl<'g> CargoSetInputs<'g> {
358    /// Creates a new `CargoSetInputs` from the given inputs.
359    pub fn new(options: CargoOptions<'g>, features_only: FeatureSet<'g>) -> Self {
360        Self {
361            options,
362            features_only,
363        }
364    }
365
366    /// Simulates a Cargo build of `initials` with these inputs.
367    ///
368    /// This is a shorthand for [`CargoSet::new`].
369    ///
370    /// ## Panics
371    ///
372    /// Panics if `initials` was built from a different package graph than
373    /// these inputs.
374    pub fn to_cargo_set(&self, initials: FeatureSet<'g>) -> Result<CargoSet<'g>, Error> {
375        CargoSet::new(initials, self.features_only.clone(), &self.options)
376    }
377}
378
379/// A set of packages and features, as would be built by Cargo.
380///
381/// Cargo implements a set of algorithms to figure out which packages or features are built in
382/// a given situation. `guppy` implements those algorithms.
383#[derive(Clone, Debug)]
384pub struct CargoSet<'g> {
385    pub(super) initials: FeatureSet<'g>,
386    pub(super) inputs: CargoSetInputs<'g>,
387    pub(super) target_features: FeatureSet<'g>,
388    pub(super) host_features: FeatureSet<'g>,
389    pub(super) target_direct_deps: PackageSet<'g>,
390    pub(super) host_direct_deps: PackageSet<'g>,
391    pub(super) proc_macro_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
392    pub(super) build_dep_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
393    pub(super) target_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
394    pub(super) host_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
395}
396
397assert_covariant!(CargoSet);
398
399impl<'g> CargoSet<'g> {
400    /// Simulates a Cargo build of this feature set, with the given options.
401    ///
402    /// The feature sets are expected to be entirely within the workspace. Its behavior outside the
403    /// workspace isn't defined and may be surprising.
404    ///
405    /// `CargoSet::new` takes two `FeatureSet` instances:
406    /// * `initials`, from which dependencies are followed to build the `CargoSet`.
407    /// * `features_only`, which are additional inputs that are only used for feature
408    ///   unification. This may be used to simulate, e.g. `cargo build --package foo --package bar`,
409    ///   when you only care about the results of `foo` but specifying `bar` influences the build.
410    ///
411    /// Note that even if a package is in `features_only`, it may be included in the final build set
412    /// through other means (for example, if it is also in `initials` or it is a dependency of one
413    /// of them).
414    ///
415    /// In many cases `features_only` is empty -- in that case you may wish to use
416    /// `FeatureSet::into_cargo_set()`, and it may be more convenient to use that if the code is
417    /// written in a "fluent" style.
418    ///
419    ///
420    pub fn new(
421        initials: FeatureSet<'g>,
422        features_only: FeatureSet<'g>,
423        opts: &CargoOptions<'_>,
424    ) -> Result<Self, Error> {
425        Self::new_internal(initials, features_only, None, opts)
426    }
427
428    /// Like `Cargo.new`, but takes an additional [`CargoLinkVisitor`] which can
429    /// be used to filter out some dependency edges, or to collect additional
430    /// information.
431    ///
432    /// [`visitor.visit_link`] is called for both target and host dependencies. It
433    /// is called after static filtering through
434    /// [`CargoOptions::add_omitted_packages`], but before any other decisions
435    /// are made.
436    ///
437    /// [`visitor.visit_link`]: CargoLinkVisitor::visit_link
438    pub fn with_cargo_link_visitor(
439        initials: FeatureSet<'g>,
440        features_only: FeatureSet<'g>,
441        mut visitor: impl CargoLinkVisitor<'g>,
442        opts: &CargoOptions<'_>,
443    ) -> Result<Self, Error> {
444        Self::new_internal(initials, features_only, Some(&mut visitor), opts)
445    }
446
447    /// Internal helper to deduplicate code across `CargoSet::new` and
448    /// `CargoSet::with_cargo_link_visitor`.
449    fn new_internal(
450        initials: FeatureSet<'g>,
451        features_only: FeatureSet<'g>,
452        visitor: Option<&mut dyn CargoLinkVisitor<'g>>,
453        opts: &CargoOptions<'_>,
454    ) -> Result<Self, Error> {
455        let graph = initials.graph().package_graph;
456        let build_state = CargoSetBuildState::new(graph, opts.resolve_package_ids(graph)?)?;
457        Ok(build_state.build(initials, features_only, visitor))
458    }
459
460    /// Creates a new `CargoIntermediateSet` based on the given query and options.
461    ///
462    /// This set contains an over-estimate of targets and features.
463    ///
464    /// Not part of the stable API, exposed for testing.
465    #[doc(hidden)]
466    pub fn new_intermediate(
467        initials: &FeatureSet<'g>,
468        opts: &CargoOptions<'_>,
469    ) -> Result<CargoIntermediateSet<'g>, Error> {
470        let graph = initials.graph().package_graph;
471        let build_state = CargoSetBuildState::new(graph, opts.resolve_package_ids(graph)?)?;
472        Ok(build_state.build_intermediate(initials.to_feature_query(DependencyDirection::Forward)))
473    }
474
475    /// Returns the feature graph for this `CargoSet` instance.
476    pub fn feature_graph(&self) -> &FeatureGraph<'g> {
477        self.initials.graph()
478    }
479
480    /// Returns the package graph for this `CargoSet` instance.
481    pub fn package_graph(&self) -> &'g PackageGraph {
482        self.feature_graph().package_graph
483    }
484
485    /// Returns the initial packages and features from which the `CargoSet` instance was
486    /// constructed.
487    pub fn initials(&self) -> &FeatureSet<'g> {
488        &self.initials
489    }
490
491    /// Returns the inputs, other than the initials, from which this `CargoSet`
492    /// instance was constructed.
493    pub fn inputs(&self) -> &CargoSetInputs<'g> {
494        &self.inputs
495    }
496
497    /// Returns the feature set enabled on the target platform.
498    ///
499    /// This represents the packages and features that are included as code in the final build
500    /// artifacts. This is relevant for both cross-compilation and auditing.
501    pub fn target_features(&self) -> &FeatureSet<'g> {
502        &self.target_features
503    }
504
505    /// Returns the feature set enabled on the host platform.
506    ///
507    /// This represents the packages and features that influence the final build artifacts, but
508    /// whose code is generally not directly included.
509    ///
510    /// This includes all procedural macros, including those specified in the initial query.
511    pub fn host_features(&self) -> &FeatureSet<'g> {
512        &self.host_features
513    }
514
515    /// Returns the feature set enabled on the specified build platform.
516    pub fn platform_features(&self, build_platform: BuildPlatform) -> &FeatureSet<'g> {
517        match build_platform {
518            BuildPlatform::Target => self.target_features(),
519            BuildPlatform::Host => self.host_features(),
520        }
521    }
522
523    /// Returns the feature sets across the target and host build platforms.
524    pub fn all_features(&self) -> [(BuildPlatform, &FeatureSet<'g>); 2] {
525        [
526            (BuildPlatform::Target, self.target_features()),
527            (BuildPlatform::Host, self.host_features()),
528        ]
529    }
530
531    /// Returns the set of workspace and direct dependency packages on the target platform.
532    ///
533    /// The packages in this set are a subset of the packages in `target_features`.
534    pub fn target_direct_deps(&self) -> &PackageSet<'g> {
535        &self.target_direct_deps
536    }
537
538    /// Returns the set of workspace and direct dependency packages on the host platform.
539    ///
540    /// The packages in this set are a subset of the packages in `host_features`.
541    pub fn host_direct_deps(&self) -> &PackageSet<'g> {
542        &self.host_direct_deps
543    }
544
545    /// Returns the set of workspace and direct dependency packages on the specified build platform.
546    pub fn platform_direct_deps(&self, build_platform: BuildPlatform) -> &PackageSet<'g> {
547        match build_platform {
548            BuildPlatform::Target => self.target_direct_deps(),
549            BuildPlatform::Host => self.host_direct_deps(),
550        }
551    }
552
553    /// Returns the set of workspace and direct dependency packages across the target and host
554    /// build platforms.
555    pub fn all_direct_deps(&self) -> [(BuildPlatform, &PackageSet<'g>); 2] {
556        [
557            (BuildPlatform::Target, self.target_direct_deps()),
558            (BuildPlatform::Host, self.host_direct_deps()),
559        ]
560    }
561
562    /// Returns `PackageLink` instances for procedural macro dependencies from target packages.
563    ///
564    /// Procedural macros straddle the line between target and host: they're built for the host
565    /// but generate code that is compiled for the target platform.
566    ///
567    /// ## Notes
568    ///
569    /// Procedural macro packages will be included in the *host* feature set.
570    /// See also [`Self::host_features`].
571    ///
572    /// The returned iterator will include proc macros that are depended on normally or in dev
573    /// builds from initials (if `include_dev` is set), but not the ones in the
574    /// `[build-dependencies]` section.
575    pub fn proc_macro_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
576        let package_graph = self.target_features.graph().package_graph;
577        self.proc_macro_edge_ixs
578            .iter()
579            .map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
580    }
581
582    /// Returns `PackageLink` instances for build dependencies from target packages.
583    ///
584    /// ## Notes
585    ///
586    /// For each link, the `from` is built on the target while the `to` is built on the host.
587    /// It is possible (though rare) that a build dependency is also included as a normal
588    /// dependency, or as a dev dependency in which case it will also be built on the target.
589    ///
590    /// The returned iterators will not include build dependencies of host packages -- those are
591    /// also built on the host.
592    pub fn build_dep_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
593        let package_graph = self.target_features.graph().package_graph;
594        self.build_dep_edge_ixs
595            .iter()
596            .map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
597    }
598
599    /// Returns `PackageLink` instances for normal dependencies between target packages.
600    ///
601    /// ## Notes
602    ///
603    /// For each link, both the `from` and the `to` package are built on the target.
604    ///
605    /// Target packages will be included in the *target* feature set.
606    /// See also [`Self::target_features`].
607    pub fn target_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
608        let package_graph = self.target_features.graph().package_graph;
609        self.target_edge_ixs
610            .iter()
611            .map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
612    }
613
614    /// Returns `PackageLink` instances for dependencies between host packages.
615    ///
616    /// ## Notes
617    ///
618    /// For each link, both the `from` and the `to` package are built on the host.
619    /// Typically most links are normal dependencies, but it is possible to have
620    /// build dependencies as well (e.g. dependencies of a build script used
621    /// in a proc-macro package).
622    ///
623    /// Host packages will be included in the *host* feature set.
624    /// See also [`Self::host_features`].
625    pub fn host_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
626        let package_graph = self.target_features.graph().package_graph;
627        self.host_edge_ixs
628            .iter()
629            .map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
630    }
631}
632
633/// Either the target or the host platform.
634///
635/// When Cargo computes the platforms it is building on, it computes two separate build graphs: one
636/// for the target platform and one for the host. This is most useful in cross-compilation
637/// situations where the target is different from the host, but the separate graphs are computed
638/// whether or not a build cross-compiles.
639///
640/// A `cargo check` can be looked at as a kind of cross-compilation as well--machine code is
641/// generated and run for the host platform but not the target platform. This is why `cargo check`
642/// output usually has some lines that say `Compiling` (for the host platform) and some that say
643/// `Checking` (for the target platform).
644#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
645pub enum BuildPlatform {
646    /// The target platform.
647    ///
648    /// This represents the packages and features that are included as code in the final build
649    /// artifacts.
650    Target,
651
652    /// The host platform.
653    ///
654    /// This represents build scripts, proc macros and other code that is run on the machine doing
655    /// the compiling.
656    Host,
657}
658
659impl BuildPlatform {
660    /// A list of all possible variants of `BuildPlatform`.
661    pub const VALUES: &'static [Self; 2] = &[BuildPlatform::Target, BuildPlatform::Host];
662
663    /// Returns the build platform that's not `self`.
664    pub fn flip(self) -> Self {
665        match self {
666            BuildPlatform::Host => BuildPlatform::Target,
667            BuildPlatform::Target => BuildPlatform::Host,
668        }
669    }
670}
671
672impl fmt::Display for BuildPlatform {
673    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674        match self {
675            BuildPlatform::Target => write!(f, "target"),
676            BuildPlatform::Host => write!(f, "host"),
677        }
678    }
679}
680
681/// An intermediate set representing an overestimate of what packages are built, but an accurate
682/// summary of what features are built given a particular package.
683///
684/// Not part of the stable API, exposed for cargo-compare.
685#[doc(hidden)]
686#[derive(Debug)]
687pub enum CargoIntermediateSet<'g> {
688    Unified(FeatureSet<'g>),
689    TargetHost {
690        target: FeatureSet<'g>,
691        host: FeatureSet<'g>,
692    },
693}
694
695impl<'g> CargoIntermediateSet<'g> {
696    #[doc(hidden)]
697    pub fn target_host_sets(&self) -> (&FeatureSet<'g>, &FeatureSet<'g>) {
698        match self {
699            CargoIntermediateSet::Unified(set) => (set, set),
700            CargoIntermediateSet::TargetHost { target, host } => (target, host),
701        }
702    }
703}