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    /// For more information, see [the Cargo documentation].
95    ///
96    /// [the Cargo documentation]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field
97    #[inline]
98    pub fn doctest_by_default(&self) -> bool {
99        self.inner.doctest_by_default
100    }
101
102    /// Previous name for [`Self::doctest_by_default`].
103    #[deprecated(since = "0.17.16", note = "use `doctest_by_default` instead")]
104    #[inline]
105    pub fn doc_tests(&self) -> bool {
106        self.inner.doctest_by_default
107    }
108
109    /// Returns true if tests are run by default for this build target (i.e. if
110    /// tests are run even if `--all-targets` isn't specified).
111    ///
112    /// This is true by default for libraries, binaries, and test targets.
113    ///
114    /// For more information, see [the Cargo documentation].
115    ///
116    /// [the Cargo documentation]: https://doc.rust-lang.org/cargo/commands/cargo-test.html#target-selection
117    #[inline]
118    pub fn test_by_default(&self) -> bool {
119        self.inner.test_by_default
120    }
121}
122
123/// An identifier for a build target within a package.
124#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
125#[non_exhaustive]
126pub enum BuildTargetId<'g> {
127    /// A library target.
128    ///
129    /// There may be at most one of these in a package.
130    ///
131    /// Defined by the `[lib]` section in `Cargo.toml`.
132    Library,
133    /// A build script.
134    ///
135    /// There may be at most one of these in a package.
136    ///
137    /// Defined by the `build` attribute in `Cargo.toml`. For more about build scripts, see [Build
138    /// Scripts](https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html) in the Cargo
139    /// reference.
140    BuildScript,
141    /// A binary target with its name.
142    ///
143    /// Defined by the `[[bin]]` section in `Cargo.toml`.
144    Binary(&'g str),
145    /// An example target with its name.
146    ///
147    /// Examples are typically binary, but may be libraries or even both.
148    ///
149    /// Defined by the `[[example]]` section in `Cargo.toml`.
150    Example(&'g str),
151    /// A test target with its name.
152    ///
153    /// Tests are always binary targets.
154    ///
155    /// Defined by the `[[test]]` section in `Cargo.toml`.
156    Test(&'g str),
157    /// A benchmark target with its name.
158    ///
159    /// Benchmarks are always binary targets.
160    ///
161    /// Defined by the `[[bench]]` section in `Cargo.toml`.
162    Benchmark(&'g str),
163}
164
165impl<'g> BuildTargetId<'g> {
166    /// Returns the name embedded in this identifier, or `None` if this is a library target.
167    ///
168    /// To get the name of the library target, use `BuildTarget::name`.
169    pub fn name(&self) -> Option<&'g str> {
170        match self {
171            BuildTargetId::Library => None,
172            BuildTargetId::BuildScript => None,
173            BuildTargetId::Binary(name) => Some(name),
174            BuildTargetId::Example(name) => Some(name),
175            BuildTargetId::Test(name) => Some(name),
176            BuildTargetId::Benchmark(name) => Some(name),
177        }
178    }
179
180    pub(super) fn as_key(&self) -> &(dyn BuildTargetKey + 'g) {
181        self
182    }
183}
184
185/// The type of build target (library or binary).
186///
187/// Obtained through `BuildTarget::kind`.
188#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
189#[non_exhaustive]
190pub enum BuildTargetKind<'g> {
191    /// This build target is a library or example, with the specified crate types.
192    ///
193    /// The crate types are sorted and unique, and can therefore be treated like a set.
194    ///
195    /// Note that examples are typically binaries, but they may be libraries as well. Binary
196    /// examples will have the crate type `"bin"`.
197    ///
198    /// For more about crate types, see [The `crate-type`
199    /// field](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#the-crate-type-field)
200    /// in the Cargo reference.
201    LibraryOrExample(&'g [String]),
202    /// This build target is a procedural macro.
203    ///
204    /// This may only be returned for `BuildTargetId::Library`. This is expressed in a `Cargo.toml`
205    /// file as:
206    ///
207    /// ```toml
208    /// [lib]
209    /// proc-macro = true
210    /// ```
211    ///
212    /// For more about procedural macros, see [Procedural
213    /// Macros](https://doc.rust-lang.org/reference/procedural-macros.html) in the Rust reference.
214    ProcMacro,
215    /// This build target is a binary target.
216    ///
217    /// This kind is returned for build script, binary, test, and benchmark targets.
218    Binary,
219}
220
221impl<'g> BuildTargetKind<'g> {
222    fn new(inner: &'g BuildTargetKindImpl) -> Self {
223        match inner {
224            BuildTargetKindImpl::LibraryOrExample(crate_types) => {
225                BuildTargetKind::LibraryOrExample(crate_types.as_slice())
226            }
227            BuildTargetKindImpl::ProcMacro => BuildTargetKind::ProcMacro,
228            BuildTargetKindImpl::Binary => BuildTargetKind::Binary,
229        }
230    }
231}
232
233/// Stored data in a `BuildTarget`.
234#[derive(Clone, Debug)]
235pub(super) struct BuildTargetImpl {
236    pub(super) kind: BuildTargetKindImpl,
237    // This is only set if the id is BuildTargetId::Library.
238    pub(super) lib_name: Option<Box<str>>,
239    pub(super) required_features: Vec<String>,
240    pub(super) path: Box<Utf8Path>,
241    pub(super) edition: Box<str>,
242    pub(super) doc_by_default: bool,
243    pub(super) doctest_by_default: bool,
244    pub(super) test_by_default: bool,
245}
246
247/// Owned version of `BuildTargetId`.
248#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
249#[cfg_attr(all(test, feature = "proptest1"), derive(proptest_derive::Arbitrary))]
250pub(super) enum OwnedBuildTargetId {
251    Library,
252    BuildScript,
253    Binary(Box<str>),
254    Example(Box<str>),
255    Test(Box<str>),
256    Benchmark(Box<str>),
257}
258
259impl OwnedBuildTargetId {
260    fn as_borrowed(&self) -> BuildTargetId {
261        match self {
262            OwnedBuildTargetId::Library => BuildTargetId::Library,
263            OwnedBuildTargetId::BuildScript => BuildTargetId::BuildScript,
264            OwnedBuildTargetId::Binary(name) => BuildTargetId::Binary(name.as_ref()),
265            OwnedBuildTargetId::Example(name) => BuildTargetId::Example(name.as_ref()),
266            OwnedBuildTargetId::Test(name) => BuildTargetId::Test(name.as_ref()),
267            OwnedBuildTargetId::Benchmark(name) => BuildTargetId::Benchmark(name.as_ref()),
268        }
269    }
270}
271
272#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
273#[non_exhaustive]
274pub(super) enum BuildTargetKindImpl {
275    LibraryOrExample(SortedSet<String>),
276    ProcMacro,
277    Binary,
278}
279
280// Borrow for complex keys. See https://github.com/sunshowers/borrow-complex-key-example.
281pub(super) trait BuildTargetKey {
282    fn key(&self) -> BuildTargetId;
283}
284
285impl BuildTargetKey for BuildTargetId<'_> {
286    fn key(&self) -> BuildTargetId {
287        *self
288    }
289}
290
291impl BuildTargetKey for OwnedBuildTargetId {
292    fn key(&self) -> BuildTargetId {
293        self.as_borrowed()
294    }
295}
296
297impl<'g> Borrow<dyn BuildTargetKey + 'g> for OwnedBuildTargetId {
298    fn borrow(&self) -> &(dyn BuildTargetKey + 'g) {
299        self
300    }
301}
302
303impl PartialEq for (dyn BuildTargetKey + '_) {
304    fn eq(&self, other: &Self) -> bool {
305        self.key() == other.key()
306    }
307}
308
309impl Eq for (dyn BuildTargetKey + '_) {}
310
311// For Borrow to be upheld, PartialOrd and Ord should be consistent. This is checked by the proptest
312// below.
313impl PartialOrd for (dyn BuildTargetKey + '_) {
314    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
315        Some(self.cmp(other))
316    }
317}
318
319impl Ord for (dyn BuildTargetKey + '_) {
320    fn cmp(&self, other: &Self) -> Ordering {
321        self.key().cmp(&other.key())
322    }
323}
324
325#[cfg(all(test, feature = "proptest1"))]
326mod tests {
327    use super::*;
328    use proptest::prelude::*;
329
330    impl OwnedBuildTargetId {
331        fn as_key(&self) -> &dyn BuildTargetKey {
332            self
333        }
334    }
335
336    proptest! {
337        #[test]
338        fn consistent_borrow(id1 in any::<OwnedBuildTargetId>(), id2 in any::<OwnedBuildTargetId>()) {
339            prop_assert_eq!(
340                id1.eq(&id1),
341                id1.as_key().eq(id1.as_key()),
342                "consistent eq implementation (same IDs)"
343            );
344            prop_assert_eq!(
345                id1.eq(&id2),
346                id1.as_key().eq(id2.as_key()),
347                "consistent eq implementation (different IDs)"
348            );
349            prop_assert_eq!(
350                id1.partial_cmp(&id2),
351                id1.as_key().partial_cmp(id2.as_key()),
352                "consistent partial_cmp implementation"
353            );
354            prop_assert_eq!(
355                id1.cmp(&id2),
356                id1.as_key().cmp(id2.as_key()),
357                "consistent cmp implementation"
358            );
359        }
360    }
361}