// Copyright (c) The cargo-guppy Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::sorted_set::SortedSet;
use camino::Utf8Path;
use std::{borrow::Borrow, cmp::Ordering};
/// A build target in a package.
///
/// A build target consists of one or more source files which can be compiled into a crate.
///
/// For more, see [Cargo
/// Targets](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html) in the Cargo
/// reference.
pub struct BuildTarget<'g> {
id: BuildTargetId<'g>,
inner: &'g BuildTargetImpl,
}
impl<'g> BuildTarget<'g> {
// The weird function signature is so that .map(BuildTarget::new) can be called.
pub(super) fn new((id, inner): (&'g OwnedBuildTargetId, &'g BuildTargetImpl)) -> Self {
Self {
id: id.as_borrowed(),
inner,
}
}
/// Returns the unique identifier for this build target.
pub fn id(&self) -> BuildTargetId<'g> {
self.id
}
/// Returns the name of this build target.
pub fn name(&self) -> &'g str {
match self.id {
BuildTargetId::Library | BuildTargetId::BuildScript => self
.inner
.lib_name
.as_ref()
.expect("library targets have lib_name set"),
other => other.name().expect("non-library targets can't return None"),
}
}
/// Returns the kind of this build target.
pub fn kind(&self) -> BuildTargetKind<'g> {
BuildTargetKind::new(&self.inner.kind)
}
/// Returns the features required for this build target.
///
/// This setting has no effect on the library target.
///
/// For more, see [The `required-features`
/// field](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#the-required-features-field)
/// in the Cargo reference.
pub fn required_features(&self) -> &'g [String] {
&self.inner.required_features
}
/// Returns the absolute path of the location where the source for this build target is located.
pub fn path(&self) -> &'g Utf8Path {
&self.inner.path
}
/// Returns the Rust edition for this build target.
pub fn edition(&self) -> &'g str {
&self.inner.edition
}
/// Returns true if documentation tests are enabled for this build target.
pub fn doc_tests(&self) -> bool {
self.inner.doc_tests
}
}
/// An identifier for a build target within a package.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum BuildTargetId<'g> {
/// A library target.
///
/// There may be at most one of these in a package.
///
/// Defined by the `[lib]` section in `Cargo.toml`.
Library,
/// A build script.
///
/// There may be at most one of these in a package.
///
/// Defined by the `build` attribute in `Cargo.toml`. For more about build scripts, see [Build
/// Scripts](https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html) in the Cargo
/// reference.
BuildScript,
/// A binary target with its name.
///
/// Defined by the `[[bin]]` section in `Cargo.toml`.
Binary(&'g str),
/// An example target with its name.
///
/// Examples are typically binary, but may be libraries or even both.
///
/// Defined by the `[[example]]` section in `Cargo.toml`.
Example(&'g str),
/// A test target with its name.
///
/// Tests are always binary targets.
///
/// Defined by the `[[test]]` section in `Cargo.toml`.
Test(&'g str),
/// A benchmark target with its name.
///
/// Benchmarks are always binary targets.
///
/// Defined by the `[[bench]]` section in `Cargo.toml`.
Benchmark(&'g str),
}
impl<'g> BuildTargetId<'g> {
/// Returns the name embedded in this identifier, or `None` if this is a library target.
///
/// To get the name of the library target, use `BuildTarget::name`.
pub fn name(&self) -> Option<&'g str> {
match self {
BuildTargetId::Library => None,
BuildTargetId::BuildScript => None,
BuildTargetId::Binary(name) => Some(name),
BuildTargetId::Example(name) => Some(name),
BuildTargetId::Test(name) => Some(name),
BuildTargetId::Benchmark(name) => Some(name),
}
}
pub(super) fn as_key(&self) -> &(dyn BuildTargetKey + 'g) {
self
}
}
/// The type of build target (library or binary).
///
/// Obtained through `BuildTarget::kind`.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum BuildTargetKind<'g> {
/// This build target is a library or example, with the specified crate types.
///
/// The crate types are sorted and unique, and can therefore be treated like a set.
///
/// Note that examples are typically binaries, but they may be libraries as well. Binary
/// examples will have the crate type `"bin"`.
///
/// For more about crate types, see [The `crate-type`
/// field](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#the-crate-type-field)
/// in the Cargo reference.
LibraryOrExample(&'g [String]),
/// This build target is a procedural macro.
///
/// This may only be returned for `BuildTargetId::Library`. This is expressed in a `Cargo.toml`
/// file as:
///
/// ```toml
/// [lib]
/// proc-macro = true
/// ```
///
/// For more about procedural macros, see [Procedural
/// Macros](https://doc.rust-lang.org/reference/procedural-macros.html) in the Rust reference.
ProcMacro,
/// This build target is a binary target.
///
/// This kind is returned for build script, binary, test, and benchmark targets.
Binary,
}
impl<'g> BuildTargetKind<'g> {
fn new(inner: &'g BuildTargetKindImpl) -> Self {
match inner {
BuildTargetKindImpl::LibraryOrExample(crate_types) => {
BuildTargetKind::LibraryOrExample(crate_types.as_slice())
}
BuildTargetKindImpl::ProcMacro => BuildTargetKind::ProcMacro,
BuildTargetKindImpl::Binary => BuildTargetKind::Binary,
}
}
}
/// Stored data in a `BuildTarget`.
#[derive(Clone, Debug)]
pub(super) struct BuildTargetImpl {
pub(super) kind: BuildTargetKindImpl,
// This is only set if the id is BuildTargetId::Library.
pub(super) lib_name: Option<Box<str>>,
pub(super) required_features: Vec<String>,
pub(super) path: Box<Utf8Path>,
pub(super) edition: Box<str>,
pub(super) doc_tests: bool,
}
/// Owned version of `BuildTargetId`.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(all(test, feature = "proptest1"), derive(proptest_derive::Arbitrary))]
pub(super) enum OwnedBuildTargetId {
Library,
BuildScript,
Binary(Box<str>),
Example(Box<str>),
Test(Box<str>),
Benchmark(Box<str>),
}
impl OwnedBuildTargetId {
fn as_borrowed(&self) -> BuildTargetId {
match self {
OwnedBuildTargetId::Library => BuildTargetId::Library,
OwnedBuildTargetId::BuildScript => BuildTargetId::BuildScript,
OwnedBuildTargetId::Binary(name) => BuildTargetId::Binary(name.as_ref()),
OwnedBuildTargetId::Example(name) => BuildTargetId::Example(name.as_ref()),
OwnedBuildTargetId::Test(name) => BuildTargetId::Test(name.as_ref()),
OwnedBuildTargetId::Benchmark(name) => BuildTargetId::Benchmark(name.as_ref()),
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub(super) enum BuildTargetKindImpl {
LibraryOrExample(SortedSet<String>),
ProcMacro,
Binary,
}
// Borrow for complex keys. See https://github.com/sunshowers/borrow-complex-key-example.
pub(super) trait BuildTargetKey {
fn key(&self) -> BuildTargetId;
}
impl<'g> BuildTargetKey for BuildTargetId<'g> {
fn key(&self) -> BuildTargetId {
*self
}
}
impl BuildTargetKey for OwnedBuildTargetId {
fn key(&self) -> BuildTargetId {
self.as_borrowed()
}
}
impl<'g> Borrow<dyn BuildTargetKey + 'g> for OwnedBuildTargetId {
fn borrow(&self) -> &(dyn BuildTargetKey + 'g) {
self
}
}
impl<'g> PartialEq for (dyn BuildTargetKey + 'g) {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
impl<'g> Eq for (dyn BuildTargetKey + 'g) {}
// For Borrow to be upheld, PartialOrd and Ord should be consistent. This is checked by the proptest
// below.
impl<'g> PartialOrd for (dyn BuildTargetKey + 'g) {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.key().partial_cmp(&other.key())
}
}
impl<'g> Ord for (dyn BuildTargetKey + 'g) {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}
#[cfg(all(test, feature = "proptest1"))]
mod tests {
use super::*;
use proptest::prelude::*;
impl OwnedBuildTargetId {
fn as_key(&self) -> &dyn BuildTargetKey {
self
}
}
proptest! {
#[test]
fn consistent_borrow(id1 in any::<OwnedBuildTargetId>(), id2 in any::<OwnedBuildTargetId>()) {
prop_assert_eq!(
id1.eq(&id1),
id1.as_key().eq(id1.as_key()),
"consistent eq implementation (same IDs)"
);
prop_assert_eq!(
id1.eq(&id2),
id1.as_key().eq(id2.as_key()),
"consistent eq implementation (different IDs)"
);
prop_assert_eq!(
id1.partial_cmp(&id2),
id1.as_key().partial_cmp(id2.as_key()),
"consistent partial_cmp implementation"
);
prop_assert_eq!(
id1.cmp(&id2),
id1.as_key().cmp(id2.as_key()),
"consistent cmp implementation"
);
}
}
}