Skip to main content

rust_manifest/
lib.rs

1#[macro_use]
2mod macros;
3mod toml_helpers;
4use toml_helpers::*;
5mod package;
6pub use package::*;
7mod profile_settings;
8pub use profile_settings::*;
9mod workspace;
10pub use workspace::*;
11
12use merge_it::*;
13#[cfg(feature = "schemars")]
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17use std::collections::{BTreeMap, BTreeSet};
18use std::mem;
19use std::path::PathBuf;
20use toml_edit::{Array, ArrayOfTables, DocumentMut, InlineTable, Item, Table, Value as TomlValue};
21
22// Some of the code for this module comes from the [`cargo_toml`](https://docs.rs/cargo_toml/0.22.3/cargo_toml/index.html) crate.
23
24/// The top-level `Cargo.toml` structure.
25///
26/// For more info, visit the [manifest guide](https://doc.rust-lang.org/cargo/reference/manifest.html#the-manifest-format)
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Merge, Default)]
28#[cfg_attr(feature = "schemars", derive(JsonSchema))]
29#[serde(rename_all = "kebab-case")]
30pub struct Manifest {
31	/// The workspace definition.
32	#[merge(with = merge_options)]
33	pub workspace: Option<Workspace>,
34
35	/// Package definition (a cargo crate)
36	#[merge(with = merge_options)]
37	pub package: Option<Package>,
38
39	/// Library target settings.
40	#[merge(with = merge_options)]
41	#[serde(default, skip_serializing_if = "Option::is_none")]
42	pub lib: Option<Product>,
43
44	/// Binary target settings.
45	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
46	pub bin: BTreeSet<Product>,
47
48	/// Platform-specific dependencies.
49	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
50	pub target: BTreeMap<String, Target>,
51
52	/// Override dependencies.
53	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
54	pub patch: BTreeMap<String, BTreeMap<String, Dependency>>,
55
56	/// Compilation/optimization settings
57	#[serde(default, skip_serializing_if = "Option::is_none")]
58	#[merge(with = merge_options)]
59	pub profile: Option<Profiles>,
60
61	/// Benchmarks
62	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
63	pub bench: BTreeSet<Product>,
64
65	/// Integration tests
66	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
67	pub test: BTreeSet<Product>,
68
69	/// Examples
70	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
71	pub example: BTreeSet<Product>,
72
73	/// Lints
74	#[serde(default, skip_serializing_if = "Option::is_none")]
75	pub lints: Option<Inheritable<Lints>>,
76
77	/// The `[features]` section.
78	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
79	pub features: BTreeMap<String, BTreeSet<String>>,
80
81	/// Normal dependencies
82	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
83	#[merge(with = merge_btree_maps)]
84	pub dependencies: BTreeMap<String, Dependency>,
85
86	/// Dev/test-only dependencies
87	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
88	#[merge(with = merge_btree_maps)]
89	pub dev_dependencies: BTreeMap<String, Dependency>,
90
91	/// Build-time dependencies
92	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
93	#[merge(with = merge_btree_maps)]
94	pub build_dependencies: BTreeMap<String, Dependency>,
95}
96
97impl Manifest {
98	/// Turns the manifest into a [`DocumentMut`] that can be used for more customized serialization.
99	pub fn as_document(&self) -> DocumentMut {
100		let mut document = DocumentMut::new();
101
102		if let Some(workspace) = &self.workspace {
103			document.insert("workspace", workspace.as_toml_value());
104		}
105
106		add_value!(self, document => package, lib, profile);
107
108		if !self.target.is_empty() {
109			let mut table = Table::from_iter(
110				self.target
111					.iter()
112					.map(|(name, target)| (toml_edit::Key::from(name), target.as_toml_value())),
113			);
114
115			table.set_implicit(true);
116
117			document["target"] = table.into();
118		}
119
120		if !self.bin.is_empty() {
121			let array =
122				ArrayOfTables::from_iter(self.bin.iter().map(|i| match i.as_toml_value() {
123					Item::Table(table) => table,
124					_ => panic!("Found non-tables for cargo toml bin"),
125				}));
126
127			document["bin"] = array.into();
128		}
129
130		if !self.bench.is_empty() {
131			let array = ArrayOfTables::from_iter(self.bench.iter().map(
132				|i| match i.as_toml_value() {
133					Item::Table(table) => table,
134					_ => panic!("Found non-tables for cargo toml bench"),
135				},
136			));
137
138			document["bench"] = array.into();
139		}
140
141		if !self.test.is_empty() {
142			let array =
143				ArrayOfTables::from_iter(self.test.iter().map(|i| match i.as_toml_value() {
144					Item::Table(table) => table,
145					_ => panic!("Found non-tables for cargo toml test"),
146				}));
147
148			document["test"] = array.into();
149		}
150
151		if !self.example.is_empty() {
152			let array = ArrayOfTables::from_iter(self.example.iter().map(
153				|i| match i.as_toml_value() {
154					Item::Table(table) => table,
155					_ => panic!("Found non-tables for cargo toml examples"),
156				},
157			));
158
159			document["example"] = array.into();
160		}
161
162		if let Some(lints) = &self.lints {
163			document["lints"] = match lints {
164				Inheritable::Workspace { workspace } => {
165					Table::from_iter([("workspace", *workspace)]).into()
166				}
167				Inheritable::Value(lints) => lints.as_toml_value(),
168			};
169		}
170
171		add_table!(self, document => dev_dependencies, build_dependencies, dependencies);
172
173		if !self.patch.is_empty() {
174			let mut table = Table::from_iter(self.patch.iter().map(|(name, deps)| {
175				let mut deps_table =
176					Table::from_iter(deps.iter().map(|(dep_name, dep)| {
177						(toml_edit::Key::from(dep_name), dep.as_toml_value())
178					}));
179
180				deps_table.set_implicit(true);
181
182				(toml_edit::Key::from(name), deps_table)
183			}));
184
185			table.set_implicit(true);
186
187			document["patch"] = table.into();
188		}
189
190		if !self.features.is_empty() {
191			document["features"] =
192				Table::from_iter(self.features.iter().map(|(name, features)| {
193					let mut array = Array::from_iter(features);
194					format_array(&mut array);
195
196					(toml_edit::Key::from(name.as_str()), array)
197				}))
198				.into();
199		}
200
201		document
202	}
203}
204
205/// Lint level.
206#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
207#[cfg_attr(feature = "schemars", derive(JsonSchema))]
208#[serde(rename_all = "kebab-case")]
209pub enum LintLevel {
210	Allow,
211	Warn,
212	Deny,
213	Forbid,
214}
215
216impl AsTomlValue for LintLevel {
217	fn as_toml_value(&self) -> Item {
218		let str = match self {
219			Self::Allow => "allow",
220			Self::Warn => "warn",
221			Self::Deny => "deny",
222			Self::Forbid => "forbid",
223		};
224
225		str.into()
226	}
227}
228
229#[track_caller]
230fn item_to_toml_value(item: Item) -> Option<TomlValue> {
231	let output = match item {
232		Item::Value(value) => value,
233		Item::Table(table) => table.into_inline_table().into(),
234		Item::ArrayOfTables(arr) => arr.into_array().into(),
235		_ => return None,
236	};
237
238	Some(output)
239}
240
241/// Lint definition.
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243#[cfg_attr(feature = "schemars", derive(JsonSchema))]
244#[serde(deny_unknown_fields)]
245pub struct Lint {
246	/// Lint level.
247	pub level: LintLevel,
248
249	/// Controls which lints or lint groups override other lint groups.
250	pub priority: Option<i8>,
251}
252
253impl AsTomlValue for Lint {
254	fn as_toml_value(&self) -> Item {
255		let mut table = InlineTable::new();
256
257		if let Some(level) = item_to_toml_value(self.level.as_toml_value()) {
258			table.insert("level", level);
259		}
260
261		if let Some(priority) = self.priority {
262			table.insert("priority", i64::from(priority).into());
263		}
264
265		table.into()
266	}
267}
268
269/// Dependencies that are platform-specific or enabled through custom `cfg()`.
270#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
271#[cfg_attr(feature = "schemars", derive(JsonSchema))]
272#[serde(default, rename_all = "kebab-case")]
273#[serde(deny_unknown_fields)]
274pub struct Target {
275	/// platform-specific normal dependencies
276	pub dependencies: BTreeMap<String, Dependency>,
277	/// platform-specific dev-only/test-only dependencies
278	pub dev_dependencies: BTreeMap<String, Dependency>,
279	/// platform-specific build-time dependencies
280	pub build_dependencies: BTreeMap<String, Dependency>,
281}
282
283impl AsTomlValue for Target {
284	fn as_toml_value(&self) -> Item {
285		let mut table = Table::new();
286
287		table.set_implicit(true);
288
289		add_table!(self, table => dev_dependencies, dependencies, build_dependencies);
290
291		table.into()
292	}
293}
294
295/// Dependency definition.
296///
297/// It can be a simple version number, or detailed settings.
298#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
299#[cfg_attr(feature = "schemars", derive(JsonSchema))]
300#[serde(untagged)]
301pub enum Dependency {
302	/// Simple version requirement (e.g. `^1.5`)
303	Simple(String),
304
305	/// A dependency inherited from the workspace.
306	Inherited(InheritedDependencyDetail), // Must be placed before `detailed` to deserialize correctly
307
308	/// A detailed dependency table.
309	Detailed(Box<DependencyDetail>),
310}
311
312impl AsTomlValue for Dependency {
313	fn as_toml_value(&self) -> Item {
314		match self {
315			Self::Simple(ver) => ver.into(),
316			Self::Inherited(dep) => dep.as_toml_value(),
317			Self::Detailed(dep) => dep.as_toml_value(),
318		}
319	}
320}
321
322impl Dependency {
323	/// Checks whether the dependency is marked as optional.
324	pub fn optional(&self) -> bool {
325		match self {
326			Self::Simple(_) => false,
327			Self::Inherited(dep) => dep.optional,
328			Self::Detailed(dep) => dep.optional,
329		}
330	}
331
332	/// Returns the features enabled in the dependency.
333	pub fn features(&self) -> Option<&BTreeSet<String>> {
334		match self {
335			Self::Simple(_) => None,
336			Self::Inherited(dep) => Some(&dep.features),
337			Self::Detailed(dep) => Some(&dep.features),
338		}
339	}
340
341	pub const fn as_simple(&self) -> Option<&String> {
342		if let Self::Simple(v) = self {
343			Some(v)
344		} else {
345			None
346		}
347	}
348
349	pub const fn as_inherited(&self) -> Option<&InheritedDependencyDetail> {
350		if let Self::Inherited(v) = self {
351			Some(v)
352		} else {
353			None
354		}
355	}
356
357	pub const fn as_detailed(&self) -> Option<&DependencyDetail> {
358		if let Self::Detailed(v) = self {
359			Some(v)
360		} else {
361			None
362		}
363	}
364}
365
366impl Merge for Dependency {
367	fn merge(&mut self, other: Self) {
368		match self {
369			Self::Simple(left_simple) => {
370				match other {
371					Self::Simple(right_simple) => *left_simple = right_simple,
372					Self::Inherited(right_inherited) => *self = Self::Inherited(right_inherited),
373					Self::Detailed(mut right_detailed) => {
374						if right_detailed.version.is_none() {
375							let version = mem::take(left_simple);
376
377							right_detailed.version = Some(version);
378						}
379
380						*self = Self::Detailed(right_detailed);
381					}
382				};
383			}
384			Self::Inherited(left_inherited) => match other {
385				// Merging inherited with a version is awkward, but reasonably
386				// it should be converted to detailed
387				Self::Simple(right_simple) => {
388					let features = mem::take(&mut left_inherited.features);
389
390					*self = Self::Detailed(
391						DependencyDetail {
392							version: Some(right_simple),
393							optional: left_inherited.optional,
394							features,
395							..Default::default()
396						}
397						.into(),
398					);
399				}
400				Self::Inherited(right) => left_inherited.merge(right),
401				Self::Detailed(mut right) => {
402					if left_inherited.optional {
403						right.optional = true;
404					}
405
406					let left_features = mem::take(&mut left_inherited.features);
407
408					right.features.extend(left_features);
409
410					*self = Self::Detailed(right);
411				}
412			},
413			Self::Detailed(left_detailed) => match other {
414				Self::Simple(right_simple) => {
415					if left_detailed.version.is_none() {
416						left_detailed.version = Some(right_simple);
417					}
418				}
419				Self::Inherited(mut right) => {
420					if left_detailed.optional {
421						right.optional = true;
422					}
423
424					let left_features = mem::take(&mut left_detailed.features);
425
426					right.features.extend(left_features);
427
428					*self = Self::Inherited(right);
429				}
430				Self::Detailed(right) => {
431					left_detailed.merge(*right);
432				}
433			},
434		}
435	}
436}
437
438// serde helper
439#[allow(clippy::trivially_copy_pass_by_ref)]
440pub(crate) const fn is_false(boolean: &bool) -> bool {
441	!*boolean
442}
443
444/// Describes a dependency inherited from the workspace.
445#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Merge)]
446#[cfg_attr(feature = "schemars", derive(JsonSchema))]
447#[serde(rename_all = "kebab-case")]
448#[serde(deny_unknown_fields)]
449pub struct InheritedDependencyDetail {
450	/// The features for the dependency.
451	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
452	pub features: BTreeSet<String>,
453
454	/// Makes the dependency optional.
455	#[serde(default, skip_serializing_if = "crate::is_false")]
456	#[merge(with = overwrite_if_true)]
457	pub optional: bool,
458
459	// Cannot be `default` or it breaks deserialization
460	/// Inherits the dependency from the workspace.
461	#[serde(skip_serializing_if = "crate::is_false")]
462	#[merge(with = overwrite_if_true)]
463	pub workspace: bool,
464}
465
466impl AsTomlValue for InheritedDependencyDetail {
467	fn as_toml_value(&self) -> Item {
468		let mut table = InlineTable::new();
469
470		add_bool!(self, table => workspace, optional);
471
472		add_string_list!(self, table => features);
473
474		table.into()
475	}
476}
477
478/// A detailed definition for a dependency.
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Merge, Default)]
480#[cfg_attr(feature = "schemars", derive(JsonSchema))]
481#[serde(default, rename_all = "kebab-case")]
482#[serde(deny_unknown_fields)]
483pub struct DependencyDetail {
484	/// Semver requirement. Note that a plain version number implies this version *or newer* compatible one.
485	#[serde(skip_serializing_if = "Option::is_none")]
486	pub version: Option<String>,
487
488	/// If defined, use this as the crate name instead of `[dependencies]`'s table key.
489	///
490	/// By using this, a crate can have multiple versions of the same dependency.
491	#[serde(skip_serializing_if = "Option::is_none")]
492	pub package: Option<String>,
493
494	/// Fetch this dependency from a custom 3rd party registry (alias defined in Cargo config), not crates-io.
495	///
496	/// This depends on local cargo configuration. It becomes `registry_index` after the crate is uploaded to a registry.
497	#[serde(skip_serializing_if = "Option::is_none")]
498	pub registry: Option<String>,
499
500	/// Directly define custom 3rd party registry URL (may be `sparse+https:`) instead of a config nickname.
501	#[serde(skip_serializing_if = "Option::is_none")]
502	pub registry_index: Option<String>,
503
504	/// This path is usually relative to the crate's manifest, but when using workspace inheritance, it may be relative to the workspace.
505	#[serde(skip_serializing_if = "Option::is_none")]
506	pub path: Option<String>,
507
508	/// Read dependency from git repo URL.
509	#[serde(skip_serializing_if = "Option::is_none")]
510	pub git: Option<String>,
511
512	/// Read dependency from git branch.
513	#[serde(skip_serializing_if = "Option::is_none")]
514	pub branch: Option<String>,
515
516	/// Read dependency from git tag.
517	#[serde(skip_serializing_if = "Option::is_none")]
518	pub tag: Option<String>,
519
520	/// Read dependency from git commit.
521	#[serde(skip_serializing_if = "Option::is_none")]
522	pub rev: Option<String>,
523
524	/// Enable these features of the dependency.
525	///
526	/// Note that Cargo interprets `default` in a special way.
527	#[serde(skip_serializing_if = "BTreeSet::is_empty")]
528	#[merge(with = BTreeSet::extend)]
529	pub features: BTreeSet<String>,
530
531	/// Makes the dependency optional.\
532	///
533	/// NB: Not allowed at workspace level
534	///
535	/// If not used with `dep:` or `?/` syntax in `[features]`, this also creates an implicit feature.
536	#[serde(skip_serializing_if = "crate::is_false")]
537	#[merge(with = overwrite_if_true)]
538	pub optional: bool,
539
540	/// Enable the `default` set of features of the dependency (enabled by default).
541	#[serde(skip_serializing_if = "Option::is_none")]
542	pub default_features: Option<bool>,
543}
544
545impl AsTomlValue for DependencyDetail {
546	fn as_toml_value(&self) -> Item {
547		let mut table = InlineTable::new();
548
549		add_string!(self, table => version, path, package, registry, registry_index, git, branch, tag, rev);
550
551		add_bool!(self, table => optional);
552
553		add_if_false!(self, table => default_features);
554
555		add_string_list!(self, table => features);
556
557		table.into()
558	}
559}
560
561/// A value that can be set to `{ workspace = true }`
562#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
563#[cfg_attr(feature = "schemars", derive(JsonSchema))]
564#[serde(untagged)]
565pub enum Inheritable<T> {
566	/// Inherit this setting from the `workspace`
567	#[serde(rename = "workspace")]
568	Workspace {
569		workspace: bool,
570	},
571	Value(T),
572}
573
574impl<T> Inheritable<T> {
575	pub const fn is_workspace(&self) -> bool {
576		matches!(self, Self::Workspace { workspace: true })
577	}
578
579	pub const fn as_value(&self) -> Option<&T> {
580		if let Self::Value(val) = self {
581			Some(val)
582		} else {
583			None
584		}
585	}
586}
587
588impl<T: Merge> Merge for Inheritable<T> {
589	fn merge(&mut self, other: Self) {
590		match self {
591			Self::Workspace { .. } => {
592				*self = other;
593			}
594			Self::Value(content_left) => {
595				match other {
596					Self::Workspace { workspace } => *self = Self::Workspace { workspace },
597					Self::Value(content_right) => content_left.merge(content_right),
598				};
599			}
600		}
601	}
602}
603
604impl<T: AsTomlValue> AsTomlValue for Inheritable<T> {
605	fn as_toml_value(&self) -> Item {
606		match self {
607			Self::Workspace { workspace } => {
608				InlineTable::from_iter([("workspace", *workspace)]).into()
609			}
610			Self::Value(value) => value.as_toml_value(),
611		}
612	}
613}
614
615impl<T: Default> Default for Inheritable<T> {
616	fn default() -> Self {
617		Self::Value(T::default())
618	}
619}
620
621impl<T: Default + PartialEq> Inheritable<T> {
622	/// Checks if the [`Inheritable`] is set to [`Value`](Inheritable::Value), and that the value is the default for that type.
623	pub fn is_default(&self) -> bool {
624		match self {
625			Self::Workspace { .. } => false,
626			Self::Value(v) => T::default() == *v,
627		}
628	}
629}
630
631/// Edition setting, which opts in to new Rust/Cargo behaviors.
632#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, Eq, PartialOrd, Ord)]
633#[cfg_attr(feature = "schemars", derive(JsonSchema))]
634pub enum Edition {
635	/// 2015
636	#[default]
637	#[serde(rename = "2015")]
638	E2015 = 2015,
639	/// 2018
640	#[serde(rename = "2018")]
641	E2018 = 2018,
642	/// 2021
643	#[serde(rename = "2021")]
644	E2021 = 2021,
645	/// 2024
646	#[serde(rename = "2024")]
647	E2024 = 2024,
648}
649
650impl AsTomlValue for Edition {
651	fn as_toml_value(&self) -> Item {
652		let str = match self {
653			Self::E2015 => "2015",
654			Self::E2018 => "2018",
655			Self::E2021 => "2021",
656			Self::E2024 => "2024",
657		};
658
659		str.into()
660	}
661}
662
663/// A way specify or disable README or `build.rs`.
664#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
665#[cfg_attr(feature = "schemars", derive(JsonSchema))]
666#[serde(
667	untagged,
668	expecting = "the value should be either a boolean or a file path"
669)]
670pub enum OptionalFile {
671	/// Opt-in to default, or explicit opt-out
672	Flag(bool),
673	/// Explicit path
674	Path(PathBuf),
675}
676
677impl AsTomlValue for OptionalFile {
678	fn as_toml_value(&self) -> Item {
679		match self {
680			Self::Flag(bool) => (*bool).into(),
681			Self::Path(path) => path.to_string_lossy().as_ref().into(),
682		}
683	}
684}
685
686/// Forbids or selects custom registry
687#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
688#[cfg_attr(feature = "schemars", derive(JsonSchema))]
689#[serde(
690	untagged,
691	expecting = "the value should be either a boolean, or an array of registry names"
692)]
693pub enum Publish {
694	Flag(bool),
695	Registry(BTreeSet<String>),
696}
697
698impl AsTomlValue for Publish {
699	fn as_toml_value(&self) -> Item {
700		match self {
701			Self::Flag(bool) => (*bool).into(),
702			Self::Registry(list) => toml_string_list(list),
703		}
704	}
705}
706
707/// The feature resolver version.
708///
709/// Needed in [`Workspace`], but implied by [`Edition`] in packages.
710#[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Serialize, Deserialize)]
711#[cfg_attr(feature = "schemars", derive(JsonSchema))]
712#[serde(expecting = "if there's a newer resolver, then this parser has to be updated")]
713#[repr(u8)]
714pub enum Resolver {
715	#[serde(rename = "1")]
716	/// The default for editions prior to 2021.
717	V1 = 1,
718	/// The default for the 2021 edition.
719	#[serde(rename = "2")]
720	V2 = 2,
721	/// The default for the 2024 edition.
722	#[serde(rename = "3")]
723	#[default]
724	V3 = 3,
725}
726
727impl AsTomlValue for Resolver {
728	fn as_toml_value(&self) -> Item {
729		match self {
730			Self::V1 => "1".into(),
731			Self::V2 => "2".into(),
732			Self::V3 => "3".into(),
733		}
734	}
735}
736
737/// A library/binary/test target.
738#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Merge, Default)]
739#[cfg_attr(feature = "schemars", derive(JsonSchema))]
740#[serde(default, rename_all = "kebab-case")]
741#[serde(deny_unknown_fields)]
742pub struct Product {
743	/// This field points at where the crate is located, relative to the `Cargo.toml`.
744	pub path: Option<String>,
745
746	/// The name of a product is the name of the library or binary that will be generated.
747	///
748	/// This is defaulted to the name of the package, with any dashes replaced
749	/// with underscores. (Rust `extern crate` declarations reference this name;
750	/// therefore the value must be a valid Rust identifier to be usable.)
751	pub name: Option<String>,
752
753	/// A flag for enabling unit tests for this product. This is used by `cargo test`.
754	#[serde(skip_serializing_if = "Option::is_none")]
755	pub test: Option<bool>,
756
757	/// A flag for enabling documentation tests for this product. This is only relevant
758	/// for libraries, it has no effect on other sections. This is used by
759	/// `cargo test`.
760	#[serde(skip_serializing_if = "Option::is_none")]
761	pub doctest: Option<bool>,
762
763	/// A flag for enabling benchmarks for this product. This is used by `cargo bench`.
764	#[serde(skip_serializing_if = "Option::is_none")]
765	pub bench: Option<bool>,
766
767	/// A flag for enabling documentation of this product. This is used by `cargo doc`.
768	#[serde(skip_serializing_if = "Option::is_none")]
769	pub doc: Option<bool>,
770
771	/// If the product is meant to be a "macros 1.1" procedural macro, this field must
772	/// be set to true.
773	#[serde(
774		alias = "proc_macro",
775		alias = "proc-macro",
776		skip_serializing_if = "crate::is_false"
777	)]
778	#[merge(with = overwrite_if_true)]
779	pub proc_macro: bool,
780
781	/// If set to false, `cargo test` will omit the `--test` flag to rustc, which
782	/// stops it from generating a test harness. This is useful when the binary being
783	/// built manages the test runner itself.
784	#[serde(skip_serializing_if = "Option::is_none")]
785	pub harness: Option<bool>,
786
787	/// The available options are "dylib", "rlib", "staticlib", "cdylib", and "proc-macro".
788	#[serde(skip_serializing_if = "BTreeSet::is_empty")]
789	pub crate_type: BTreeSet<String>,
790
791	/// The `required-features` field specifies which features the product needs in order to be built.
792	/// If any of the required features are not selected, the product will be skipped.
793	/// This is only relevant for the `[[bin]]`, `[[bench]]`, `[[test]]`, and `[[example]]` sections,
794	/// it has no effect on `[lib]`.
795	#[serde(skip_serializing_if = "BTreeSet::is_empty")]
796	pub required_features: BTreeSet<String>,
797}
798
799impl AsTomlValue for Product {
800	fn as_toml_value(&self) -> Item {
801		let mut table = Table::new();
802
803		add_string!(self, table => path, name);
804		add_bool!(self, table => proc_macro);
805		add_string_list!(self, table => crate_type, required_features);
806
807		add_if_false!(self, table => test, doctest, bench, doc, harness);
808
809		table.into()
810	}
811}