mod package_set;
use crate::{
graph::{
cargo::{CargoOptions, CargoResolverVersion, CargoSet, InitialsPlatform},
feature::FeatureSet,
DependencyDirection, PackageGraph, PackageMetadata, PackageSet, PackageSource,
},
platform::PlatformSpecSummary,
Error,
};
pub use guppy_summaries::*;
pub use package_set::*;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
impl<'g> CargoSet<'g> {
pub fn to_summary(&self, opts: &CargoOptions<'_>) -> Result<Summary, Error> {
let initials = self.initials();
let metadata =
CargoOptionsSummary::new(initials.graph().package_graph, self.features_only(), opts)?;
let target_features = self.target_features();
let host_features = self.host_features();
let mut summary = Summary::with_metadata(&metadata).map_err(Error::TomlSerializeError)?;
summary.target_packages =
target_features.to_package_map(initials, self.target_direct_deps());
summary.host_packages = host_features.to_package_map(initials, self.host_direct_deps());
Ok(summary)
}
}
impl<'g> FeatureSet<'g> {
fn to_package_map(
&self,
initials: &FeatureSet<'g>,
direct_deps: &PackageSet<'g>,
) -> PackageMap {
self.packages_with_features(DependencyDirection::Forward)
.map(|feature_list| {
let package = feature_list.package();
let status = if initials.contains_package_ix(package.package_ix()) {
PackageStatus::Initial
} else if package.in_workspace() {
PackageStatus::Workspace
} else if direct_deps.contains_ix(package.package_ix()) {
PackageStatus::Direct
} else {
PackageStatus::Transitive
};
let info = PackageInfo {
status,
features: feature_list
.named_features()
.map(|feature| feature.to_owned())
.collect(),
optional_deps: feature_list
.optional_deps()
.map(|dep| dep.to_owned())
.collect(),
};
(feature_list.package().to_summary_id(), info)
})
.collect()
}
}
impl PackageGraph {
pub fn metadata_by_summary_id(&self, summary_id: &SummaryId) -> Result<PackageMetadata, Error> {
match &summary_id.source {
SummarySource::Workspace { workspace_path } => {
self.workspace().member_by_path(workspace_path)
}
_ => {
let mut filter = self.packages().filter(|package| {
package.name() == summary_id.name
&& package.version() == &summary_id.version
&& package.source() == summary_id.source
});
filter
.next()
.ok_or_else(|| Error::UnknownSummaryId(summary_id.clone()))
}
}
}
}
impl<'g> PackageMetadata<'g> {
pub fn to_summary_id(&self) -> SummaryId {
SummaryId {
name: self.name().to_string(),
version: self.version().clone(),
source: self.source().to_summary_source(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct CargoOptionsSummary {
#[serde(alias = "version")]
pub resolver: CargoResolverVersion,
pub include_dev: bool,
#[serde(flatten)]
pub initials_platform: InitialsPlatformSummary,
#[serde(default)]
pub host_platform: PlatformSpecSummary,
#[serde(default)]
pub target_platform: PlatformSpecSummary,
#[serde(skip_serializing_if = "PackageSetSummary::is_empty", default)]
pub omitted_packages: PackageSetSummary,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub features_only: Vec<FeaturesOnlySummary>,
}
impl CargoOptionsSummary {
pub fn new(
graph: &PackageGraph,
features_only: &FeatureSet<'_>,
opts: &CargoOptions<'_>,
) -> Result<Self, Error> {
let omitted_packages =
PackageSetSummary::from_package_ids(graph, opts.omitted_packages.iter().copied())?;
let mut features_only = features_only
.packages_with_features(DependencyDirection::Forward)
.map(|features| FeaturesOnlySummary {
summary_id: features.package().to_summary_id(),
features: features
.named_features()
.map(|feature| feature.to_owned())
.collect(),
optional_deps: features
.optional_deps()
.map(|feature| feature.to_owned())
.collect(),
})
.collect::<Vec<_>>();
features_only.sort_unstable();
Ok(Self {
resolver: opts.resolver,
include_dev: opts.include_dev,
initials_platform: InitialsPlatformSummary::V2 {
initials_platform: opts.initials_platform,
},
host_platform: PlatformSpecSummary::new(&opts.host_platform),
target_platform: PlatformSpecSummary::new(&opts.target_platform),
omitted_packages,
features_only,
})
}
pub fn to_cargo_options<'g>(
&'g self,
package_graph: &'g PackageGraph,
) -> Result<CargoOptions<'g>, Error> {
let omitted_packages = self
.omitted_packages
.to_package_set(package_graph, "resolving omitted-packages")?;
let mut options = CargoOptions::new();
options
.set_resolver(self.resolver)
.set_include_dev(self.include_dev)
.set_initials_platform(self.initials_platform.into())
.set_host_platform(
self.host_platform.to_platform_spec().map_err(|err| {
Error::TargetSpecError("parsing host platform".to_string(), err)
})?,
)
.set_target_platform(self.target_platform.to_platform_spec().map_err(|err| {
Error::TargetSpecError("parsing target platform".to_string(), err)
})?)
.add_omitted_packages(omitted_packages.package_ids(DependencyDirection::Forward));
Ok(options)
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged, rename_all = "kebab-case")]
#[non_exhaustive]
pub enum InitialsPlatformSummary {
#[serde(rename_all = "kebab-case")]
V1 {
proc_macros_on_target: bool,
},
#[serde(rename_all = "kebab-case")]
V2 {
initials_platform: InitialsPlatform,
},
}
impl From<InitialsPlatformSummary> for InitialsPlatform {
fn from(s: InitialsPlatformSummary) -> Self {
match s {
InitialsPlatformSummary::V1 {
proc_macros_on_target,
} => {
if proc_macros_on_target {
InitialsPlatform::ProcMacrosOnTarget
} else {
InitialsPlatform::Standard
}
}
InitialsPlatformSummary::V2 { initials_platform } => initials_platform,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct FeaturesOnlySummary {
#[serde(flatten)]
pub summary_id: SummaryId,
pub features: BTreeSet<String>,
#[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
pub optional_deps: BTreeSet<String>,
}
impl<'g> PackageSource<'g> {
pub fn to_summary_source(&self) -> SummarySource {
match self {
PackageSource::Workspace(path) => SummarySource::workspace(path),
PackageSource::Path(path) => SummarySource::path(path),
PackageSource::External(source) => {
if *source == PackageSource::CRATES_IO_REGISTRY {
SummarySource::crates_io()
} else {
SummarySource::external(*source)
}
}
}
}
}
impl<'g> PartialEq<SummarySource> for PackageSource<'g> {
fn eq(&self, summary_source: &SummarySource) -> bool {
match summary_source {
SummarySource::Workspace { workspace_path } => {
self == &PackageSource::Workspace(workspace_path)
}
SummarySource::Path { path } => self == &PackageSource::Path(path),
SummarySource::CratesIo => {
self == &PackageSource::External(PackageSource::CRATES_IO_REGISTRY)
}
SummarySource::External { source } => self == &PackageSource::External(source),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_old_metadata() {
let metadata = "\
version = 'v1'
include-dev = true
proc-macros-on-target = false
";
let summary: CargoOptionsSummary = toml::from_str(metadata).expect("parsed correctly");
assert_eq!(
InitialsPlatform::from(summary.initials_platform),
InitialsPlatform::Standard
);
}
}