use crate::{
Error, PackageId,
graph::{
DependencyDirection, PackageGraph, PackageIx, PackageLink, PackageLinkContext, PackageSet,
cargo::build::CargoSetBuildState,
feature::{FeatureGraph, FeatureSet},
},
platform::PlatformSpec,
sorted_set::SortedSet,
};
use petgraph::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, fmt};
#[derive(Clone, Debug)]
pub struct CargoOptions<'a> {
pub(crate) resolver: CargoResolverVersion,
pub(crate) include_dev: bool,
pub(crate) initials_platform: InitialsPlatform,
pub(crate) host_platform: PlatformSpec,
pub(crate) target_platform: PlatformSpec,
pub(crate) omitted_packages: HashSet<&'a PackageId>,
}
impl<'a> CargoOptions<'a> {
pub fn new() -> Self {
Self {
resolver: CargoResolverVersion::V3,
include_dev: false,
initials_platform: InitialsPlatform::Standard,
host_platform: PlatformSpec::Any,
target_platform: PlatformSpec::Any,
omitted_packages: HashSet::new(),
}
}
pub fn set_resolver(&mut self, resolver: CargoResolverVersion) -> &mut Self {
self.resolver = resolver;
self
}
pub fn set_include_dev(&mut self, include_dev: bool) -> &mut Self {
self.include_dev = include_dev;
self
}
pub fn set_initials_platform(&mut self, initials_platform: InitialsPlatform) -> &mut Self {
self.initials_platform = initials_platform;
self
}
pub fn set_platform(&mut self, platform_spec: impl Into<PlatformSpec>) -> &mut Self {
let platform_spec = platform_spec.into();
self.target_platform = platform_spec.clone();
self.host_platform = platform_spec;
self
}
pub fn set_target_platform(&mut self, target_platform: impl Into<PlatformSpec>) -> &mut Self {
self.target_platform = target_platform.into();
self
}
pub fn set_host_platform(&mut self, host_platform: impl Into<PlatformSpec>) -> &mut Self {
self.host_platform = host_platform.into();
self
}
pub fn add_omitted_packages(
&mut self,
package_ids: impl IntoIterator<Item = &'a PackageId>,
) -> &mut Self {
self.omitted_packages.extend(package_ids);
self
}
pub(crate) fn resolve_package_ids<'g>(
&self,
graph: &'g PackageGraph,
) -> Result<CargoOptions<'g>, Error> {
let omitted_packages = self
.omitted_packages
.iter()
.map(|package_id| graph.metadata(package_id).map(|metadata| metadata.id()))
.collect::<Result<HashSet<_>, _>>()?;
Ok(CargoOptions {
resolver: self.resolver,
include_dev: self.include_dev,
initials_platform: self.initials_platform,
host_platform: self.host_platform.clone(),
target_platform: self.target_platform.clone(),
omitted_packages,
})
}
}
impl Default for CargoOptions<'_> {
fn default() -> Self {
Self::new()
}
}
pub trait CargoLinkVisitor<'g> {
fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool;
}
impl<'g, T> CargoLinkVisitor<'g> for &mut T
where
T: CargoLinkVisitor<'g>,
{
fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
impl<'g> CargoLinkVisitor<'g> for Box<dyn CargoLinkVisitor<'g> + '_> {
fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
impl<'g> CargoLinkVisitor<'g> for &mut dyn CargoLinkVisitor<'g> {
fn visit_link(&mut self, cx: &CargoLinkContext<'_, 'g>, link: PackageLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
#[derive(Clone, Debug)]
pub struct CargoLinkContext<'a, 'g> {
package_context: &'a PackageLinkContext<'g>,
build_platform: BuildPlatform,
platform_spec: &'a PlatformSpec,
build_dep_platform_spec: &'a PlatformSpec,
include_dev: bool,
}
impl<'a, 'g> CargoLinkContext<'a, 'g> {
pub(super) fn new(
package_context: &'a PackageLinkContext<'g>,
build_platform: BuildPlatform,
opts: &'a CargoOptions<'_>,
) -> Self {
let (platform_spec, build_dep_platform_spec) = match build_platform {
BuildPlatform::Target => (&opts.target_platform, &opts.host_platform),
BuildPlatform::Host => (&opts.host_platform, &opts.host_platform),
};
Self {
package_context,
build_platform,
platform_spec,
build_dep_platform_spec,
include_dev: opts.include_dev,
}
}
pub fn package_context(&self) -> &PackageLinkContext<'g> {
self.package_context
}
pub fn build_platform(&self) -> BuildPlatform {
self.build_platform
}
pub fn platform_spec(&self) -> &PlatformSpec {
self.platform_spec
}
pub fn build_dep_platform_spec(&self) -> &PlatformSpec {
self.build_dep_platform_spec
}
pub fn considers_dev_deps(&self, link: &PackageLink<'g>) -> bool {
self.include_dev && self.package_context.starts_from_initial(link)
}
pub fn considers_build_deps(&self, link: &PackageLink<'g>) -> bool {
link.from().has_build_script()
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum CargoResolverVersion {
#[serde(rename = "1", alias = "v1")]
V1,
#[serde(rename = "install", alias = "v1-install")]
V1Install,
#[serde(rename = "2", alias = "v2")]
V2,
#[serde(rename = "3", alias = "v3")]
V3,
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
#[serde(rename_all = "kebab-case")]
pub enum InitialsPlatform {
Host,
Standard,
ProcMacrosOnTarget,
}
impl Default for InitialsPlatform {
fn default() -> Self {
InitialsPlatform::Standard
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CargoSetInputs<'g> {
pub options: CargoOptions<'g>,
pub features_only: FeatureSet<'g>,
}
assert_covariant!(CargoSetInputs);
impl<'g> CargoSetInputs<'g> {
pub fn new(options: CargoOptions<'g>, features_only: FeatureSet<'g>) -> Self {
Self {
options,
features_only,
}
}
pub fn to_cargo_set(&self, initials: FeatureSet<'g>) -> Result<CargoSet<'g>, Error> {
CargoSet::new(initials, self.features_only.clone(), &self.options)
}
}
#[derive(Clone, Debug)]
pub struct CargoSet<'g> {
pub(super) initials: FeatureSet<'g>,
pub(super) inputs: CargoSetInputs<'g>,
pub(super) target_features: FeatureSet<'g>,
pub(super) host_features: FeatureSet<'g>,
pub(super) target_direct_deps: PackageSet<'g>,
pub(super) host_direct_deps: PackageSet<'g>,
pub(super) proc_macro_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
pub(super) build_dep_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
pub(super) target_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
pub(super) host_edge_ixs: SortedSet<EdgeIndex<PackageIx>>,
}
assert_covariant!(CargoSet);
impl<'g> CargoSet<'g> {
pub fn new(
initials: FeatureSet<'g>,
features_only: FeatureSet<'g>,
opts: &CargoOptions<'_>,
) -> Result<Self, Error> {
Self::new_internal(initials, features_only, None, opts)
}
pub fn with_cargo_link_visitor(
initials: FeatureSet<'g>,
features_only: FeatureSet<'g>,
mut visitor: impl CargoLinkVisitor<'g>,
opts: &CargoOptions<'_>,
) -> Result<Self, Error> {
Self::new_internal(initials, features_only, Some(&mut visitor), opts)
}
fn new_internal(
initials: FeatureSet<'g>,
features_only: FeatureSet<'g>,
visitor: Option<&mut dyn CargoLinkVisitor<'g>>,
opts: &CargoOptions<'_>,
) -> Result<Self, Error> {
let graph = initials.graph().package_graph;
let build_state = CargoSetBuildState::new(graph, opts.resolve_package_ids(graph)?)?;
Ok(build_state.build(initials, features_only, visitor))
}
#[doc(hidden)]
pub fn new_intermediate(
initials: &FeatureSet<'g>,
opts: &CargoOptions<'_>,
) -> Result<CargoIntermediateSet<'g>, Error> {
let graph = initials.graph().package_graph;
let build_state = CargoSetBuildState::new(graph, opts.resolve_package_ids(graph)?)?;
Ok(build_state.build_intermediate(initials.to_feature_query(DependencyDirection::Forward)))
}
pub fn feature_graph(&self) -> &FeatureGraph<'g> {
self.initials.graph()
}
pub fn package_graph(&self) -> &'g PackageGraph {
self.feature_graph().package_graph
}
pub fn initials(&self) -> &FeatureSet<'g> {
&self.initials
}
pub fn inputs(&self) -> &CargoSetInputs<'g> {
&self.inputs
}
pub fn target_features(&self) -> &FeatureSet<'g> {
&self.target_features
}
pub fn host_features(&self) -> &FeatureSet<'g> {
&self.host_features
}
pub fn platform_features(&self, build_platform: BuildPlatform) -> &FeatureSet<'g> {
match build_platform {
BuildPlatform::Target => self.target_features(),
BuildPlatform::Host => self.host_features(),
}
}
pub fn all_features(&self) -> [(BuildPlatform, &FeatureSet<'g>); 2] {
[
(BuildPlatform::Target, self.target_features()),
(BuildPlatform::Host, self.host_features()),
]
}
pub fn target_direct_deps(&self) -> &PackageSet<'g> {
&self.target_direct_deps
}
pub fn host_direct_deps(&self) -> &PackageSet<'g> {
&self.host_direct_deps
}
pub fn platform_direct_deps(&self, build_platform: BuildPlatform) -> &PackageSet<'g> {
match build_platform {
BuildPlatform::Target => self.target_direct_deps(),
BuildPlatform::Host => self.host_direct_deps(),
}
}
pub fn all_direct_deps(&self) -> [(BuildPlatform, &PackageSet<'g>); 2] {
[
(BuildPlatform::Target, self.target_direct_deps()),
(BuildPlatform::Host, self.host_direct_deps()),
]
}
pub fn proc_macro_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
let package_graph = self.target_features.graph().package_graph;
self.proc_macro_edge_ixs
.iter()
.map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
}
pub fn build_dep_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
let package_graph = self.target_features.graph().package_graph;
self.build_dep_edge_ixs
.iter()
.map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
}
pub fn target_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
let package_graph = self.target_features.graph().package_graph;
self.target_edge_ixs
.iter()
.map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
}
pub fn host_links<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageLink<'g>> + 'a {
let package_graph = self.target_features.graph().package_graph;
self.host_edge_ixs
.iter()
.map(move |edge_ix| package_graph.edge_ix_to_link(*edge_ix))
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BuildPlatform {
Target,
Host,
}
impl BuildPlatform {
pub const VALUES: &'static [Self; 2] = &[BuildPlatform::Target, BuildPlatform::Host];
pub fn flip(self) -> Self {
match self {
BuildPlatform::Host => BuildPlatform::Target,
BuildPlatform::Target => BuildPlatform::Host,
}
}
}
impl fmt::Display for BuildPlatform {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildPlatform::Target => write!(f, "target"),
BuildPlatform::Host => write!(f, "host"),
}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub enum CargoIntermediateSet<'g> {
Unified(FeatureSet<'g>),
TargetHost {
target: FeatureSet<'g>,
host: FeatureSet<'g>,
},
}
impl<'g> CargoIntermediateSet<'g> {
#[doc(hidden)]
pub fn target_host_sets(&self) -> (&FeatureSet<'g>, &FeatureSet<'g>) {
match self {
CargoIntermediateSet::Unified(set) => (set, set),
CargoIntermediateSet::TargetHost { target, host } => (target, host),
}
}
}