use std::path::Path;
use stow_types::error::Context;
#[derive(Debug)]
pub struct ProfileDivergence {
pub section: String,
pub key: String,
}
impl std::fmt::Display for ProfileDivergence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] sets `{}`", self.section, self.key)
}
}
pub async fn dev_profile_divergence(
workspace_root: &Path,
) -> stow_types::error::Result<Option<ProfileDivergence>> {
let manifest_path = workspace_root.join("Cargo.toml");
let contents = async_fs::read_to_string(&manifest_path)
.await
.wrap_err_with(|| format!("read workspace manifest {}", manifest_path.display()))?;
let manifest: toml::Value = toml::from_str(&contents)
.wrap_err_with(|| format!("parse workspace manifest {}", manifest_path.display()))?;
let Some(dev) = manifest
.get("profile")
.and_then(|profile| profile.get("dev"))
.and_then(toml::Value::as_table)
else {
return Ok(None);
};
if let Some(divergence) = table_divergence(dev, "profile.dev") {
return Ok(Some(divergence));
}
if let Some(wildcard) = dev
.get("package")
.and_then(toml::Value::as_table)
.and_then(|packages| packages.get("*"))
.and_then(toml::Value::as_table)
&& let Some(divergence) = table_divergence(wildcard, "profile.dev.package.\"*\"")
{
return Ok(Some(divergence));
}
Ok(None)
}
fn table_divergence(
table: &toml::map::Map<String, toml::Value>,
section: &str,
) -> Option<ProfileDivergence> {
let lto = table.get("lto")?;
let default = lto.as_bool() == Some(false) || lto.as_str() == Some("off");
(!default).then(|| ProfileDivergence {
section: section.to_owned(),
key: "lto".to_owned(),
})
}
#[cfg(test)]
mod tests {
use super::table_divergence;
fn table(source: &str) -> toml::map::Map<String, toml::Value> {
toml::from_str::<toml::Value>(source)
.unwrap()
.as_table()
.unwrap()
.clone()
}
#[test]
fn identity_expressible_knobs_do_not_diverge() {
let dev = table(
r#"
opt-level = 2
debug = "line-tables-only"
strip = "debuginfo"
debug-assertions = false
overflow-checks = false
panic = "abort"
codegen-units = 1
incremental = false
split-debuginfo = "packed"
lto = false
"#,
);
assert!(table_divergence(&dev, "profile.dev").is_none());
let dev = table(r#"lto = "off""#);
assert!(table_divergence(&dev, "profile.dev").is_none());
}
#[test]
fn lto_diverges() {
for source in ["lto = true", r#"lto = "fat""#, r#"lto = "thin""#] {
let dev = table(source);
let divergence = table_divergence(&dev, "profile.dev").expect("divergent");
assert_eq!(divergence.key, "lto");
}
}
#[test]
fn named_package_overrides_do_not_disqualify_the_workspace() {
let manifest: toml::Value = toml::from_str(
r"
[profile.dev.package.insta]
lto = true
",
)
.unwrap();
let dev = manifest["profile"]["dev"].as_table().unwrap();
assert!(table_divergence(dev, "profile.dev").is_none());
let wildcard = dev
.get("package")
.and_then(toml::Value::as_table)
.and_then(|packages| packages.get("*"));
assert!(wildcard.is_none());
}
}