use crate::checks::Check;
use crate::context::{Os, ProjectContext};
use crate::finding::*;
pub struct LinkerCheck;
impl Check for LinkerCheck {
fn name(&self) -> &'static str {
"linker"
}
fn run(&self, ctx: &ProjectContext) -> Vec<Finding> {
let mut findings = Vec::new();
if has_custom_linker(ctx) {
return findings;
}
match ctx.os {
Os::Linux => {
if ctx.installed_linkers.mold {
findings.push(Finding {
severity: Severity::Fix,
category: Category::Linker,
impact: Impact::High,
title: "Default system linker detected".into(),
description: "mold is installed and would be 2-5x faster for linking.".into(),
fix: Some(Fix {
description: "Use mold as the linker".into(),
kind: FixKind::CargoConfig {
key_path: "[target.x86_64-unknown-linux-gnu]".into(),
value: "linker = \"clang\"\nrustflags = [\"-C\", \"link-arg=-fuse-ld=mold\"]".into(),
},
}),
});
} else if ctx.installed_linkers.lld {
findings.push(Finding {
severity: Severity::Fix,
category: Category::Linker,
impact: Impact::High,
title: "Default system linker detected".into(),
description: "lld is installed and would be 2-3x faster for linking.".into(),
fix: Some(Fix {
description: "Use lld as the linker".into(),
kind: FixKind::CargoConfig {
key_path: "[target.x86_64-unknown-linux-gnu]".into(),
value: "linker = \"clang\"\nrustflags = [\"-C\", \"link-arg=-fuse-ld=lld\"]".into(),
},
}),
});
} else {
findings.push(Finding {
severity: Severity::Warn,
category: Category::Linker,
impact: Impact::High,
title: "No fast linker installed".into(),
description: "Neither mold nor lld detected. Installing one can speed up linking 2-5x.".into(),
fix: Some(Fix {
description: "Install mold".into(),
kind: FixKind::ShellCommand("sudo apt install mold # or: cargo install mold".into()),
}),
});
}
}
Os::Macos => {
if !has_split_debuginfo(ctx) {
findings.push(Finding {
severity: Severity::Fix,
category: Category::Linker,
impact: Impact::Medium,
title: "split-debuginfo not enabled on macOS".into(),
description: "Using split-debuginfo=unpacked avoids bundling debug info during linking.".into(),
fix: Some(Fix {
description: "Enable split-debuginfo in dev profile".into(),
kind: FixKind::CargoToml {
section: "profile.dev".into(),
key: "split-debuginfo".into(),
value: "\"unpacked\"".into(),
},
}),
});
}
}
_ => {}
}
findings
}
}
fn has_custom_linker(ctx: &ProjectContext) -> bool {
if let Some(config) = &ctx.cargo_config {
if let Some(target) = config.get("target").and_then(|t| t.as_table()) {
for (_target_name, target_config) in target {
if let Some(table) = target_config.as_table() {
if table.contains_key("linker") {
return true;
}
if let Some(flags) = table.get("rustflags").and_then(|f| f.as_array()) {
let flags_str: Vec<&str> = flags.iter().filter_map(|v| v.as_str()).collect();
if flags_str.iter().any(|f| f.contains("fuse-ld")) {
return true;
}
}
}
}
}
if let Some(build) = config.get("build").and_then(|b| b.as_table()) {
if build.contains_key("linker") || build.contains_key("rustflags") {
return true;
}
}
}
if let Some(flags) = &ctx.env_vars.rustflags {
if flags.contains("fuse-ld") {
return true;
}
}
false
}
fn has_split_debuginfo(ctx: &ProjectContext) -> bool {
if let Some(profile) = ctx.cargo_toml.get("profile").and_then(|p| p.as_table()) {
if let Some(dev) = profile.get("dev").and_then(|d| d.as_table()) {
if dev.contains_key("split-debuginfo") {
return true;
}
}
}
false
}