Skip to main content

guppy/graph/
build_targets.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::sorted_set::SortedSet;
5use camino::Utf8Path;
6use std::{borrow::Borrow, cmp::Ordering};
7
8/// A build target in a package.
9///
10/// A build target consists of one or more source files which can be compiled into a crate.
11///
12/// For more, see [Cargo
13/// Targets](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html) in the Cargo
14/// reference.
15pub struct BuildTarget<'g> {
16    id: BuildTargetId<'g>,
17    inner: &'g BuildTargetImpl,
18}
19
20impl<'g> BuildTarget<'g> {
21    // The weird function signature is so that .map(BuildTarget::new) can be called.
22    pub(super) fn new((id, inner): (&'g OwnedBuildTargetId, &'g BuildTargetImpl)) -> Self {
23        Self {
24            id: id.as_borrowed(),
25            inner,
26        }
27    }
28
29    /// Returns the unique identifier for this build target.
30    #[inline]
31    pub fn id(&self) -> BuildTargetId<'g> {
32        self.id
33    }
34
35    /// Returns the name of this build target.
36    pub fn name(&self) -> &'g str {
37        match self.id {
38            BuildTargetId::Library | BuildTargetId::BuildScript => self
39                .inner
40                .lib_name
41                .as_ref()
42                .expect("library targets have lib_name set"),
43            other => other.name().expect("non-library targets can't return None"),
44        }
45    }
46
47    /// Returns the kind of this build target.
48    #[inline]
49    pub fn kind(&self) -> BuildTargetKind<'g> {
50        BuildTargetKind::new(&self.inner.kind)
51    }
52
53    /// Returns the features required for this build target.
54    ///
55    /// This setting has no effect on the library target.
56    ///
57    /// For more, see [The `required-features`
58    /// field](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#the-required-features-field)
59    /// in the Cargo reference.
60    #[inline]
61    pub fn required_features(&self) -> &'g [String] {
62        &self.inner.required_features
63    }
64
65    /// Returns the absolute path of the location where the source for this build target is located.
66    #[inline]
67    pub fn path(&self) -> &'g Utf8Path {
68        &self.inner.path
69    }
70
71    /// Returns the Rust edition for this build target.
72    #[inline]
73    pub fn edition(&self) -> &'g str {
74        &self.inner.edition
75    }
76
77    /// Returns true if documentation is generated for this build target by
78    /// default.
79    ///
80    /// This is true by default for library targets, as well as binaries that
81    /// don't share a name with the library they are in.
82    ///
83    /// For more information, see [the Cargo documentation].
84    ///
85    /// [the Cargo documentation]: https://doc.rust-lang.org/cargo/commands/cargo-doc.html#target-selection
86    #[inline]
87    pub fn doc_by_default(&self) -> bool {
88        self.inner.doc_by_default
89    }
90
91    /// Returns true if documentation tests are run by default for this build
92    /// target.
93    ///
94    /// This is true by default for library targets, as well as binaries that
95    /// don't share a name with the library they are in.
96    ///
97    /// For more information, see [the Cargo documentation].
98    ///
99    /// [the Cargo documentation]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field
100    #[inline]
101    pub fn doctest_by_default(&self) -> bool {
102        self.inner.doctest_by_default
103    }
104
105    /// Returns true if tests are run by default for this build target (i.e. if
106    /// tests are run even if `--all-targets` isn't specified).
107    ///
108    /// This is true by default for libraries, binaries, and test targets.
109    ///
110    /// For more information, see [the Cargo documentation].
111    ///
112    /// [the Cargo documentation]: https://doc.rust-lang.org/cargo/commands/cargo-test.html#target-selection
113    #[inline]
114    pub fn test_by_default(&self) -> bool {
115        self.inner.test_by_default
116    }
117}
118
119/// An identifier for a build target within a package.
120#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
121#[non_exhaustive]
122pub enum BuildTargetId<'g> {
123    /// A library target.
124    ///
125    /// There may be at most one of these in a package.
126    ///
127    /// Defined by the `[lib]` section in `Cargo.toml`.
128    Library,
129    /// A build script.
130    ///
131    /// There may be at most one of these in a package.
132    ///
133    /// Defined by the `build` attribute in `Cargo.toml`. For more about build scripts, see [Build
134    /// Scripts](https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html) in the Cargo
135    /// reference.
136    BuildScript,
137    /// A binary target with its name.
138    ///
139    /// Defined by the `[[bin]]` section in `Cargo.toml`.
140    Binary(&'g str),
141    /// An example target with its name.
142    ///
143    /// Examples are typically binary, but may be libraries or even both.
144    ///
145    /// Defined by the `[[example]]` section in `Cargo.toml`.
146    Example(&'g str),
147    /// A test target with its name.
148    ///
149    /// Tests are always binary targets.
150    ///
151    /// Defined by the `[[test]]` section in `Cargo.toml`.
152    Test(&'g str),
153    /// A benchmark target with its name.
154    ///
155    /// Benchmarks are always binary targets.
156    ///
157    /// Defined by the `[[bench]]` section in `Cargo.toml`.
158    Benchmark(&'g str),
159}
160
161impl<'g> BuildTargetId<'g> {
162    /// Returns the name embedded in this identifier, or `None` if this is a library target.
163    ///
164    /// To get the name of the library target, use `BuildTarget::name`.
165    pub fn name(&self) -> Option<&'g str> {
166        match self {
167            BuildTargetId::Library => None,
168            BuildTargetId::BuildScript => None,
169            BuildTargetId::Binary(name) => Some(name),
170            BuildTargetId::Example(name) => Some(name),
171            BuildTargetId::Test(name) => Some(name),
172            BuildTargetId::Benchmark(name) => Some(name),
173        }
174    }
175
176    pub(super) fn as_key(&self) -> &(dyn BuildTargetKey + 'g) {
177        self
178    }
179}
180
181/// The type of build target (library or binary).
182///
183/// Obtained through `BuildTarget::kind`.
184#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
185#[non_exhaustive]
186pub enum BuildTargetKind<'g> {
187    /// This build target is a library or example, with the specified crate types.
188    ///
189    /// The crate types are sorted and unique, and can therefore be treated like a set.
190    ///
191    /// Note that examples are typically binaries, but they may be libraries as well. Binary
192    /// examples will have the crate type `"bin"`.
193    ///
194    /// For more about crate types, see [The `crate-type`
195    /// field](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#the-crate-type-field)
196    /// in the Cargo reference.
197    LibraryOrExample(&'g [String]),
198    /// This build target is a procedural macro.
199    ///
200    /// This may only be returned for `BuildTargetId::Library`. This is expressed in a `Cargo.toml`
201    /// file as:
202    ///
203    /// ```toml
204    /// [lib]
205    /// proc-macro = true
206    /// ```
207    ///
208    /// For more about procedural macros, see [Procedural
209    /// Macros](https://doc.rust-lang.org/reference/procedural-macros.html) in the Rust reference.
210    ProcMacro,
211    /// This build target is a binary target.
212    ///
213    /// This kind is returned for build script, binary, test, and benchmark targets.
214    Binary,
215}
216
217impl<'g> BuildTargetKind<'g> {
218    fn new(inner: &'g BuildTargetKindImpl) -> Self {
219        match inner {
220            BuildTargetKindImpl::LibraryOrExample(crate_types) => {
221                BuildTargetKind::LibraryOrExample(crate_types.as_slice())
222            }
223            BuildTargetKindImpl::ProcMacro => BuildTargetKind::ProcMacro,
224            BuildTargetKindImpl::Binary => BuildTargetKind::Binary,
225        }
226    }
227}
228
229/// Stored data in a `BuildTarget`.
230#[derive(Clone, Debug)]
231pub(super) struct BuildTargetImpl {
232    pub(super) kind: BuildTargetKindImpl,
233    // This is only set if the id is BuildTargetId::Library.
234    pub(super) lib_name: Option<Box<str>>,
235    pub(super) required_features: Vec<String>,
236    pub(super) path: Box<Utf8Path>,
237    pub(super) edition: Box<str>,
238    pub(super) doc_by_default: bool,
239    pub(super) doctest_by_default: bool,
240    pub(super) test_by_default: bool,
241}
242
243/// Owned version of `BuildTargetId`.
244#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
245#[cfg_attr(all(test, feature = "proptest1"), derive(proptest_derive::Arbitrary))]
246pub(super) enum OwnedBuildTargetId {
247    Library,
248    BuildScript,
249    Binary(Box<str>),
250    Example(Box<str>),
251    Test(Box<str>),
252    Benchmark(Box<str>),
253}
254
255impl OwnedBuildTargetId {
256    fn as_borrowed(&self) -> BuildTargetId<'_> {
257        match self {
258            OwnedBuildTargetId::Library => BuildTargetId::Library,
259            OwnedBuildTargetId::BuildScript => BuildTargetId::BuildScript,
260            OwnedBuildTargetId::Binary(name) => BuildTargetId::Binary(name.as_ref()),
261            OwnedBuildTargetId::Example(name) => BuildTargetId::Example(name.as_ref()),
262            OwnedBuildTargetId::Test(name) => BuildTargetId::Test(name.as_ref()),
263            OwnedBuildTargetId::Benchmark(name) => BuildTargetId::Benchmark(name.as_ref()),
264        }
265    }
266}
267
268#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
269#[non_exhaustive]
270pub(super) enum BuildTargetKindImpl {
271    LibraryOrExample(SortedSet<String>),
272    ProcMacro,
273    Binary,
274}
275
276// Borrow for complex keys. See https://github.com/sunshowers/borrow-complex-key-example.
277pub(super) trait BuildTargetKey {
278    fn key(&self) -> BuildTargetId<'_>;
279}
280
281impl BuildTargetKey for BuildTargetId<'_> {
282    fn key(&self) -> BuildTargetId<'_> {
283        *self
284    }
285}
286
287impl BuildTargetKey for OwnedBuildTargetId {
288    fn key(&self) -> BuildTargetId<'_> {
289        self.as_borrowed()
290    }
291}
292
293impl<'g> Borrow<dyn BuildTargetKey + 'g> for OwnedBuildTargetId {
294    fn borrow(&self) -> &(dyn BuildTargetKey + 'g) {
295        self
296    }
297}
298
299impl PartialEq for dyn BuildTargetKey + '_ {
300    fn eq(&self, other: &Self) -> bool {
301        self.key() == other.key()
302    }
303}
304
305impl Eq for dyn BuildTargetKey + '_ {}
306
307// For Borrow to be upheld, PartialOrd and Ord should be consistent. This is checked by the proptest
308// below.
309impl PartialOrd for dyn BuildTargetKey + '_ {
310    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
311        Some(self.cmp(other))
312    }
313}
314
315impl Ord for dyn BuildTargetKey + '_ {
316    fn cmp(&self, other: &Self) -> Ordering {
317        self.key().cmp(&other.key())
318    }
319}
320
321#[cfg(all(test, feature = "proptest1"))]
322mod tests {
323    use super::*;
324    use proptest::prelude::*;
325
326    impl OwnedBuildTargetId {
327        fn as_key(&self) -> &dyn BuildTargetKey {
328            self
329        }
330    }
331
332    proptest! {
333        #[test]
334        fn consistent_borrow(id1 in any::<OwnedBuildTargetId>(), id2 in any::<OwnedBuildTargetId>()) {
335            prop_assert_eq!(
336                id1.eq(&id1),
337                id1.as_key().eq(id1.as_key()),
338                "consistent eq implementation (same IDs)"
339            );
340            prop_assert_eq!(
341                id1.eq(&id2),
342                id1.as_key().eq(id2.as_key()),
343                "consistent eq implementation (different IDs)"
344            );
345            prop_assert_eq!(
346                id1.partial_cmp(&id2),
347                id1.as_key().partial_cmp(id2.as_key()),
348                "consistent partial_cmp implementation"
349            );
350            prop_assert_eq!(
351                id1.cmp(&id2),
352                id1.as_key().cmp(id2.as_key()),
353                "consistent cmp implementation"
354            );
355        }
356    }
357}