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}