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}