guppy/graph/build_targets.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
// 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> {
Some(self.cmp(other))
}
}
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"
);
}
}
}