Skip to main content

rust_manifest/
profile_settings.rs

1use super::*;
2
3/// Handling of LTO in a build profile
4#[derive(Debug, Clone, PartialEq, Serialize, Eq, PartialOrd, Ord, Deserialize)]
5#[cfg_attr(feature = "schemars", derive(JsonSchema))]
6#[serde(try_from = "RawLto", into = "RawLto")]
7#[cfg_attr(feature = "schemars", schemars(with = "RawLto"))]
8#[serde(rename_all = "kebab-case")]
9pub enum LtoSetting {
10	None,
11	ThinLocal,
12	Thin,
13	Fat,
14}
15
16#[derive(Debug, Serialize, Deserialize)]
17#[cfg_attr(feature = "schemars", derive(JsonSchema))]
18#[serde(untagged)]
19enum RawLto {
20	Bool(bool),
21	String(String),
22}
23
24impl TryFrom<RawLto> for LtoSetting {
25	type Error = String;
26
27	fn try_from(value: RawLto) -> Result<Self, Self::Error> {
28		match value {
29			RawLto::Bool(false) => Ok(Self::ThinLocal),
30			RawLto::Bool(true) => Ok(Self::Fat),
31
32			RawLto::String(s) => match s.as_str() {
33				"off" => Ok(Self::None),
34				"thin-local" => Ok(Self::ThinLocal),
35				"thin" => Ok(Self::Thin),
36				"fat" => Ok(Self::Fat),
37				_ => Err(format!("Unknown LTO setting: {s}")),
38			},
39		}
40	}
41}
42
43impl From<LtoSetting> for RawLto {
44	fn from(setting: LtoSetting) -> Self {
45		match setting {
46			LtoSetting::None => Self::String("off".to_string()),
47			LtoSetting::ThinLocal => Self::Bool(false),
48			LtoSetting::Thin => Self::String("thin".to_string()),
49			LtoSetting::Fat => Self::Bool(true),
50		}
51	}
52}
53
54impl AsTomlValue for LtoSetting {
55	fn as_toml_value(&self) -> Item {
56		match self {
57			Self::None => "off".into(),
58			Self::ThinLocal => false.into(),
59			Self::Thin => "thin".into(),
60			Self::Fat => true.into(),
61		}
62	}
63}
64
65/// Verbosity of debug info in a [`Profile`]
66#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
67#[serde(try_from = "RawDebugSetting", into = "RawDebugSetting")]
68#[cfg_attr(feature = "schemars", derive(JsonSchema))]
69#[cfg_attr(feature = "schemars", schemars(with = "RawDebugSetting"))]
70pub enum DebugSetting {
71	None,               // 0, false, "none"
72	LineDirectivesOnly, // "line-directives-only"
73	LineTablesOnly,     // "line-tables-only"
74	Limited,            // 1, "limited"
75	Full,               // 2, true, "full"
76}
77
78#[derive(Debug, Serialize, Deserialize)]
79#[cfg_attr(feature = "schemars", derive(JsonSchema))]
80#[serde(untagged)]
81enum RawDebugSetting {
82	Bool(bool),
83	Integer(u8),
84	String(String),
85}
86
87impl TryFrom<RawDebugSetting> for DebugSetting {
88	type Error = String;
89
90	fn try_from(value: RawDebugSetting) -> Result<Self, Self::Error> {
91		match value {
92			RawDebugSetting::Bool(false) | RawDebugSetting::Integer(0) => Ok(Self::None),
93			RawDebugSetting::Integer(1) => Ok(Self::Limited),
94			RawDebugSetting::Bool(true) | RawDebugSetting::Integer(2) => Ok(Self::Full),
95			RawDebugSetting::Integer(n) => {
96				Err(format!("Invalid debug level: {n}. Use 0, 1, or 2."))
97			}
98
99			RawDebugSetting::String(s) => match s.as_str() {
100				"none" | "false" | "0" => Ok(Self::None),
101
102				"line-directives-only" => Ok(Self::LineDirectivesOnly),
103				"line-tables-only" => Ok(Self::LineTablesOnly),
104
105				"limited" | "1" => Ok(Self::Limited),
106				"full" | "true" | "2" => Ok(Self::Full),
107				_ => Err(format!("Unknown debug setting: '{s}'")),
108			},
109		}
110	}
111}
112
113impl From<DebugSetting> for RawDebugSetting {
114	fn from(setting: DebugSetting) -> Self {
115		match setting {
116			DebugSetting::None => Self::Bool(false),
117			DebugSetting::Full => Self::Bool(true),
118
119			DebugSetting::Limited => Self::Integer(1),
120
121			DebugSetting::LineDirectivesOnly => Self::String("line-directives-only".to_string()),
122			DebugSetting::LineTablesOnly => Self::String("line-tables-only".to_string()),
123		}
124	}
125}
126
127impl AsTomlValue for DebugSetting {
128	fn as_toml_value(&self) -> Item {
129		let raw: RawDebugSetting = (*self).into();
130
131		match raw {
132			RawDebugSetting::Bool(bool) => bool.into(),
133			RawDebugSetting::Integer(int) => (i64::from(int)).into(),
134			RawDebugSetting::String(str) => str.into(),
135		}
136	}
137}
138
139/// Handling of debug symbols in a build profile
140#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
141#[serde(try_from = "RawStripSetting", into = "RawStripSetting")]
142#[cfg_attr(feature = "schemars", derive(JsonSchema))]
143#[cfg_attr(feature = "schemars", schemars(with = "RawStripSetting"))]
144pub enum StripSetting {
145	None,
146	Debuginfo,
147	Symbols,
148}
149
150#[derive(Debug, Serialize, Deserialize)]
151#[cfg_attr(feature = "schemars", derive(JsonSchema))]
152#[serde(untagged)]
153enum RawStripSetting {
154	Bool(bool),
155	String(String),
156}
157
158impl TryFrom<RawStripSetting> for StripSetting {
159	type Error = String;
160
161	fn try_from(value: RawStripSetting) -> Result<Self, Self::Error> {
162		match value {
163			RawStripSetting::Bool(false) => Ok(Self::None),
164			RawStripSetting::Bool(true) => Ok(Self::Symbols),
165
166			RawStripSetting::String(s) => match s.as_str() {
167				"debuginfo" => Ok(Self::Debuginfo),
168				"symbols" | "true" => Ok(Self::Symbols),
169
170				"none" | "false" => Ok(Self::None),
171				_ => Err(format!(
172					"Invalid strip setting: '{s}'. Use 'none', 'debuginfo', 'symbols', true, or false."
173				)),
174			},
175		}
176	}
177}
178
179impl From<StripSetting> for RawStripSetting {
180	fn from(setting: StripSetting) -> Self {
181		match setting {
182			StripSetting::None => Self::String("none".to_string()),
183			StripSetting::Debuginfo => Self::String("debuginfo".to_string()),
184			StripSetting::Symbols => Self::String("symbols".to_string()),
185		}
186	}
187}
188
189impl AsTomlValue for StripSetting {
190	fn as_toml_value(&self) -> Item {
191		let raw: RawStripSetting = (*self).into();
192
193		match raw {
194			RawStripSetting::Bool(bool) => bool.into(),
195			RawStripSetting::String(str) => str.into(),
196		}
197	}
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201#[cfg_attr(feature = "schemars", derive(JsonSchema))]
202#[serde(try_from = "RawOptLevel", into = "RawOptLevel")]
203pub enum OptLevel {
204	Zero,
205	One,
206	Two,
207	Three,
208	S,
209	Z,
210}
211
212impl AsTomlValue for OptLevel {
213	fn as_toml_value(&self) -> Item {
214		match self {
215			Self::Zero => Item::from(0),
216			Self::One => Item::from(1),
217			Self::Two => Item::from(2),
218			Self::Three => Item::from(3),
219			Self::S => Item::from("s"),
220			Self::Z => Item::from("z"),
221		}
222	}
223}
224
225#[derive(Debug, Deserialize, Serialize)]
226#[cfg_attr(feature = "schemars", derive(JsonSchema))]
227#[serde(untagged)]
228enum RawOptLevel {
229	Integer(u8),
230	String(String),
231}
232
233impl TryFrom<RawOptLevel> for OptLevel {
234	type Error = String;
235
236	fn try_from(value: RawOptLevel) -> Result<Self, Self::Error> {
237		match value {
238			RawOptLevel::Integer(0) => Ok(Self::Zero),
239			RawOptLevel::Integer(1) => Ok(Self::One),
240			RawOptLevel::Integer(2) => Ok(Self::Two),
241			RawOptLevel::Integer(3) => Ok(Self::Three),
242
243			RawOptLevel::String(s) => match s.as_str() {
244				"0" => Ok(Self::Zero),
245				"1" => Ok(Self::One),
246				"2" => Ok(Self::Two),
247				"3" => Ok(Self::Three),
248				"s" => Ok(Self::S),
249				"z" => Ok(Self::Z),
250				_ => Err(format!("Invalid opt-level: {s}")),
251			},
252
253			_ => Err("opt-level must be 0-3, 's', or 'z'".to_string()),
254		}
255	}
256}
257
258impl From<OptLevel> for RawOptLevel {
259	fn from(val: OptLevel) -> Self {
260		match val {
261			OptLevel::Zero => Self::Integer(0),
262			OptLevel::One => Self::Integer(1),
263			OptLevel::Two => Self::Integer(2),
264			OptLevel::Three => Self::Integer(3),
265			OptLevel::S => Self::String("s".to_string()),
266			OptLevel::Z => Self::String("z".to_string()),
267		}
268	}
269}
270
271/// Compilation/optimization settings for a workspace
272#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Merge)]
273#[cfg_attr(feature = "schemars", derive(JsonSchema))]
274#[serde(rename_all = "kebab-case")]
275#[serde(deny_unknown_fields)]
276pub struct Profile {
277	/// The opt-level setting controls the `-C opt-level` flag which controls the level of optimization.
278	#[serde(default, skip_serializing_if = "Option::is_none")]
279	pub opt_level: Option<OptLevel>,
280
281	/// The debug setting controls the `-C debuginfo` flag which controls the amount of debug information included in the compiled binary.
282	#[serde(default, skip_serializing_if = "Option::is_none")]
283	pub debug: Option<DebugSetting>,
284
285	/// The split-debuginfo setting controls the `-C split-debuginfo` flag which controls whether debug information, if generated, is either placed in the executable itself or adjacent to it.
286	#[serde(default, skip_serializing_if = "Option::is_none")]
287	pub split_debuginfo: Option<String>,
288
289	/// The rpath setting controls the `-C rpath` flag which controls whether or not rpath is enabled.
290	#[serde(default, skip_serializing_if = "Option::is_none")]
291	pub rpath: Option<bool>,
292
293	/// The lto setting controls rustc’s `-C lto`, `-C linker-plugin-lto`, and `-C embed-bitcode` options, which control LLVM’s link time optimizations.
294	///
295	/// LTO can produce better optimized code, using whole-program analysis, at the cost of longer linking time.
296	#[serde(default, skip_serializing_if = "Option::is_none")]
297	pub lto: Option<LtoSetting>,
298
299	/// The debug-assertions setting controls the `-C debug-assertions` flag which turns `cfg(debug_assertions)` conditional compilation on or off.
300	#[serde(default, skip_serializing_if = "Option::is_none")]
301	pub debug_assertions: Option<bool>,
302
303	/// The codegen-units setting controls the `-C codegen-units` flag which controls how many “code generation units” a crate will be split into.
304	///
305	/// More code generation units allows more of a crate to be processed in parallel possibly reducing compile time, but may produce slower code.
306	#[serde(default, skip_serializing_if = "Option::is_none")]
307	pub codegen_units: Option<u16>,
308
309	/// The panic setting controls the `-C panic` flag which controls which panic strategy to use.
310	///
311	/// The valid options are:
312	/// - `unwind`: Unwind the stack upon panic.
313	/// - `abort`: Terminate the process upon panic.
314	#[serde(default, skip_serializing_if = "Option::is_none")]
315	pub panic: Option<String>,
316
317	/// The incremental setting controls the `-C incremental` flag which controls whether or not incremental compilation is enabled.
318	#[serde(default, skip_serializing_if = "Option::is_none")]
319	pub incremental: Option<bool>,
320
321	/// The overflow-checks setting controls the `-C overflow-checks` flag which controls the behavior of runtime integer overflow.
322	#[serde(default, skip_serializing_if = "Option::is_none")]
323	pub overflow_checks: Option<bool>,
324
325	/// The strip option controls the `-C strip` flag, which directs rustc to strip either symbols or debuginfo from a binary.
326	#[serde(default, skip_serializing_if = "Option::is_none")]
327	pub strip: Option<StripSetting>,
328
329	/// Profile settings can be overridden for specific packages and build-time crates.
330	///
331	/// To override the settings for a specific package, use the package table to change the settings for the named package:
332	/// ```toml
333	/// # The `foo` package will use the -Copt-level=3 flag.
334	/// [profile.dev.package.foo]
335	/// opt-level = 3
336	/// ```
337	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
338	pub package: BTreeMap<String, Self>,
339
340	/// To override the settings for build scripts, proc macros, and their dependencies, use the build-override table:
341	/// ```toml
342	/// # Set the settings for build scripts and proc-macros.
343	/// [profile.dev.build-override]
344	/// opt-level = 3
345	/// ```
346	#[serde(default, skip_serializing_if = "Option::is_none")]
347	pub build_override: Option<Box<Self>>,
348
349	/// Specifies which profile the custom profile inherits settings from when the setting is not specified.
350	#[serde(default, skip_serializing_if = "Option::is_none")]
351	pub inherits: Option<String>,
352}
353
354impl AsTomlValue for Profile {
355	fn as_toml_value(&self) -> Item {
356		let mut table = Table::new();
357
358		table.set_implicit(true);
359
360		add_value!(self, table => debug, lto, strip, opt_level, build_override);
361		add_string!(self, table => split_debuginfo, panic, inherits);
362
363		add_optional_bool!(self, table => rpath, debug_assertions, incremental, overflow_checks);
364
365		if let Some(codegen_units) = self.codegen_units {
366			table["codegen-units"] = i64::from(codegen_units).into();
367		}
368
369		if !self.package.is_empty() {
370			let mut pkg_table = Table::from_iter(
371				self.package
372					.iter()
373					.map(|(k, v)| (toml_edit::Key::from(k), v.as_toml_value())),
374			);
375
376			pkg_table.set_implicit(true);
377
378			table.insert("package", pkg_table.into());
379		};
380
381		table.into()
382	}
383}
384
385/// Custom build/optimization settings
386#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Merge)]
387#[cfg_attr(feature = "schemars", derive(JsonSchema))]
388#[merge(with = merge_options)]
389pub struct Profiles {
390	/// Used for `--release`
391	#[serde(skip_serializing_if = "Option::is_none")]
392	pub release: Option<Profile>,
393
394	/// Used by default
395	#[serde(skip_serializing_if = "Option::is_none")]
396	pub dev: Option<Profile>,
397
398	/// Used for `cargo test`
399	#[serde(skip_serializing_if = "Option::is_none")]
400	pub test: Option<Profile>,
401
402	/// Used for `cargo bench`
403	#[serde(skip_serializing_if = "Option::is_none")]
404	pub bench: Option<Profile>,
405
406	/// User-supplied for `cargo --profile=name`
407	#[serde(flatten)]
408	#[merge(with = BTreeMap::extend)]
409	pub custom: BTreeMap<String, Profile>,
410}
411
412impl AsTomlValue for Profiles {
413	fn as_toml_value(&self) -> Item {
414		let mut table = Table::new();
415
416		table.set_implicit(true);
417
418		add_value!(self, table => dev, test, bench, release);
419		add_table!(self, table => custom);
420
421		table.into()
422	}
423}