use std::sync::Arc;
use crate::resolution::rust::dependency::{CargoDependencyKind, RustDependency};
use crate::resolution::rust::identity::{PackageId, TargetId};
use crate::resolution::rust::package::RustPackage;
use crate::resolution::rust::project::RustProject;
use crate::resolution::rust::target::{CargoTargetKind, RustTarget};
use super::activation::{Predicate, conjoin};
use super::error::{ClosureSite, SourceClosureFailure, SourceClosureFailureKind};
use super::failure::failure;
const TEST_MODE: &str = "cfg(test)";
pub(super) enum SelectedEdge<'a> {
Declared {
dependency: &'a RustDependency,
target: &'a RustTarget,
package: &'a RustPackage,
predicate: Predicate,
},
PackageLibrary {
target: &'a RustTarget,
package: &'a RustPackage,
},
}
pub(super) fn select(
project: &RustProject,
package: PackageId,
root: (bool, CargoTargetKind),
) -> Result<Vec<SelectedEdge<'_>>, SourceClosureFailure> {
let mut selected: Vec<SelectedEdge<'_>> = project
.package_dependencies(package)
.filter_map(|dependency| select_declared(project, dependency, root))
.collect::<Result<_, _>>()?;
selected.extend(package_library(project, package, root));
Ok(selected)
}
fn select_declared<'a>(
project: &'a RustProject,
dependency: &'a RustDependency,
root: (bool, CargoTargetKind),
) -> Option<Result<SelectedEdge<'a>, SourceClosureFailure>> {
let predicate = exposure(root, dependency.kind())
.map(|mode| conjoin(&Predicate::of(dependency.activation()), &mode))?;
let package = project.package(dependency.package()?)?;
Some(
library_target(project, dependency).map(|target| SelectedEdge::Declared {
dependency,
target,
package,
predicate,
}),
)
}
fn library_target<'a>(
project: &'a RustProject,
dependency: &RustDependency,
) -> Result<&'a RustTarget, SourceClosureFailure> {
let declared = dependency
.library()
.ok_or_else(|| missing_library(project, dependency))?;
project
.target(declared)
.ok_or_else(|| unresolved_library(project, (dependency, declared)))
}
fn package_library(
project: &RustProject,
package: PackageId,
root: (bool, CargoTargetKind),
) -> Option<SelectedEdge<'_>> {
match root {
(
true,
CargoTargetKind::Binary
| CargoTargetKind::Example
| CargoTargetKind::Test
| CargoTargetKind::Benchmark,
) => library_edge(project, package),
_ => None,
}
}
fn library_edge(project: &RustProject, package: PackageId) -> Option<SelectedEdge<'_>> {
let owner = project.package(package)?;
let target = project
.package_targets(package)
.find(|target| target.kind() == CargoTargetKind::Library)?;
Some(SelectedEdge::PackageLibrary {
target,
package: owner,
})
}
impl SelectedEdge<'_> {
pub(super) fn target(&self) -> &RustTarget {
match self {
Self::Declared { target, .. } | Self::PackageLibrary { target, .. } => target,
}
}
pub(super) fn package(&self) -> &RustPackage {
match self {
Self::Declared { package, .. } | Self::PackageLibrary { package, .. } => package,
}
}
pub(super) fn shared_name(&self) -> &Arc<str> {
match self {
Self::Declared { dependency, .. } => &dependency.name,
Self::PackageLibrary { target, .. } => &target.name,
}
}
pub(super) fn kind(&self) -> CargoDependencyKind {
match self {
Self::Declared { dependency, .. } => dependency.kind(),
Self::PackageLibrary { .. } => CargoDependencyKind::Normal,
}
}
pub(super) fn predicate(&self) -> Predicate {
match self {
Self::Declared { predicate, .. } => predicate.clone(),
Self::PackageLibrary { .. } => Predicate::Always,
}
}
pub(super) fn site(&self, project: &RustProject) -> ClosureSite {
match self {
Self::Declared { dependency, .. } => dependency_site(project, dependency),
Self::PackageLibrary { package, target } => ClosureSite::Dependency {
package: Box::from(package.name()),
dependency: Box::from(target.name()),
},
}
}
}
fn exposure(root: (bool, CargoTargetKind), kind: CargoDependencyKind) -> Option<Predicate> {
match root {
(false, _) => normal_only(kind),
(true, root_kind) => root_exposure(root_kind, kind),
}
}
fn normal_only(kind: CargoDependencyKind) -> Option<Predicate> {
matches!(kind, CargoDependencyKind::Normal).then_some(Predicate::Always)
}
fn root_exposure(root_kind: CargoTargetKind, kind: CargoDependencyKind) -> Option<Predicate> {
match (root_kind, kind) {
(CargoTargetKind::BuildScript, CargoDependencyKind::Build) => Some(Predicate::Always),
(CargoTargetKind::BuildScript, _) => None,
(_, CargoDependencyKind::Build) => None,
(_, CargoDependencyKind::Normal) => Some(Predicate::Always),
(CargoTargetKind::Library | CargoTargetKind::Binary, CargoDependencyKind::Development) => {
Some(Predicate::Stated(Arc::from(TEST_MODE)))
}
(_, CargoDependencyKind::Development) => Some(Predicate::Always),
}
}
fn dependency_site(project: &RustProject, dependency: &RustDependency) -> ClosureSite {
let package = project
.package(dependency.source())
.map(|owner| owner.name())
.unwrap_or("<unknown>");
ClosureSite::Dependency {
package: Box::from(package),
dependency: Box::from(dependency.name()),
}
}
fn missing_library(project: &RustProject, dependency: &RustDependency) -> SourceClosureFailure {
let site = dependency_site(project, dependency);
let message = format!("{site}: the depended-on package declares no library target");
failure(
SourceClosureFailureKind::MissingDependencyLibraryTarget,
(&site, None),
message,
)
}
fn unresolved_library(
project: &RustProject,
edge: (&RustDependency, TargetId),
) -> SourceClosureFailure {
let (dependency, declared) = edge;
let site = dependency_site(project, dependency);
let message = format!(
"{site}: the declared library target at local index {} is not in this project",
declared.index()
);
failure(
SourceClosureFailureKind::UnresolvedDependencyLibraryTarget,
(&site, None),
message,
)
}