feat: add support for customizable installation directories in build flags

This commit is contained in:
2026-03-08 21:11:50 -05:00
parent be0288440a
commit a10c7227be
9 changed files with 648 additions and 26 deletions
+13 -18
View File
@@ -442,25 +442,20 @@ fn default_configure_install_dirs(
flags: &crate::package::BuildFlags,
help_text: Option<&str>,
) -> Vec<String> {
let libdir = if flags.lib32_variant {
"/usr/lib32"
} else {
"/usr/lib"
};
let bindir = nonempty_trimmed(&flags.bindir).unwrap_or("/usr/bin");
let dirs = crate::builder::install_dirs(flags);
let defaults = [
("--bindir", bindir),
("--sbindir", "/usr/bin"),
("--libdir", libdir),
("--libexecdir", libdir),
("--sysconfdir", "/etc"),
("--localstatedir", "/var"),
("--sharedstatedir", "/var/lib"),
("--includedir", "/usr/include"),
("--datarootdir", "/usr/share"),
("--datadir", "/usr/share"),
("--mandir", "/usr/share/man"),
("--infodir", "/usr/share/info"),
("--bindir", dirs.bindir),
("--sbindir", dirs.sbindir),
("--libdir", dirs.libdir),
("--libexecdir", dirs.libexecdir),
("--sysconfdir", dirs.sysconfdir),
("--localstatedir", dirs.localstatedir),
("--sharedstatedir", dirs.sharedstatedir),
("--includedir", dirs.includedir),
("--datarootdir", dirs.datarootdir),
("--datadir", dirs.datadir),
("--mandir", dirs.mandir),
("--infodir", dirs.infodir),
];
defaults
+134 -8
View File
@@ -35,12 +35,8 @@ pub fn build(
let env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
// Extract prefix from configure flags (cmake-style -DCMAKE_INSTALL_PREFIX=)
let prefix = flags
.configure
.iter()
.find(|s| s.contains("CMAKE_INSTALL_PREFIX="))
.and_then(|s| s.split('=').nth(1))
.unwrap_or(&flags.prefix);
let prefix = cmake_cache_entry_value(&flags.configure, "CMAKE_INSTALL_PREFIX")
.unwrap_or(flags.prefix.as_str());
// Generate toolchain file if cross-compiling
let toolchain_file = if let Some(cc_cfg) = cross {
@@ -64,6 +60,9 @@ pub fn build(
cmake_cmd.arg("-B").arg(&build_dir);
cmake_cmd.arg(format!("-DCMAKE_INSTALL_PREFIX={}", prefix));
cmake_cmd.arg("-DCMAKE_BUILD_TYPE=Release");
for arg in cmake_install_dir_args(flags) {
cmake_cmd.arg(arg);
}
// Add toolchain file for cross-compilation
if let Some(ref tf) = toolchain_file {
@@ -283,12 +282,47 @@ fn cmake_configure_flags_specify_generator(flags: &[String]) -> bool {
}
fn cmake_configure_flags_set_make_program(flags: &[String]) -> bool {
flags.iter().any(|flag| {
cmake_cache_entry_value(flags, "CMAKE_MAKE_PROGRAM").is_some()
}
fn cmake_cache_entry_value<'a>(flags: &'a [String], variable: &str) -> Option<&'a str> {
let plain_prefix = format!("-D{variable}=");
let typed_prefix = format!("-D{variable}:");
flags.iter().find_map(|flag| {
let trimmed = flag.trim();
trimmed.starts_with("-DCMAKE_MAKE_PROGRAM=")
if let Some(value) = trimmed.strip_prefix(&plain_prefix) {
return Some(value);
}
trimmed
.strip_prefix(&typed_prefix)
.and_then(|rest| rest.split_once('=').map(|(_, value)| value))
})
}
fn cmake_install_dir_args(flags: &crate::package::BuildFlags) -> Vec<String> {
let dirs = crate::builder::install_dirs(flags);
let defaults = [
("CMAKE_INSTALL_BINDIR", dirs.bindir),
("CMAKE_INSTALL_SBINDIR", dirs.sbindir),
("CMAKE_INSTALL_LIBDIR", dirs.libdir),
("CMAKE_INSTALL_LIBEXECDIR", dirs.libexecdir),
("CMAKE_INSTALL_SYSCONFDIR", dirs.sysconfdir),
("CMAKE_INSTALL_LOCALSTATEDIR", dirs.localstatedir),
("CMAKE_INSTALL_SHAREDSTATEDIR", dirs.sharedstatedir),
("CMAKE_INSTALL_INCLUDEDIR", dirs.includedir),
("CMAKE_INSTALL_DATAROOTDIR", dirs.datarootdir),
("CMAKE_INSTALL_DATADIR", dirs.datadir),
("CMAKE_INSTALL_MANDIR", dirs.mandir),
("CMAKE_INSTALL_INFODIR", dirs.infodir),
];
defaults
.into_iter()
.filter(|(variable, _)| cmake_cache_entry_value(&flags.configure, variable).is_none())
.map(|(variable, value)| format!("-D{variable}={value}"))
.collect()
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
@@ -429,6 +463,98 @@ mod tests {
]));
}
#[test]
fn test_cmake_cache_entry_value_supports_plain_and_typed_entries() {
let flags = vec![
"-DCMAKE_INSTALL_PREFIX=/usr".to_string(),
"-DCMAKE_INSTALL_LIBDIR:PATH=/usr/lib64".to_string(),
];
assert_eq!(
cmake_cache_entry_value(&flags, "CMAKE_INSTALL_PREFIX"),
Some("/usr")
);
assert_eq!(
cmake_cache_entry_value(&flags, "CMAKE_INSTALL_LIBDIR"),
Some("/usr/lib64")
);
assert_eq!(
cmake_cache_entry_value(&flags, "CMAKE_INSTALL_BINDIR"),
None
);
}
#[test]
fn test_cmake_install_dir_args_include_defaults() {
let args = cmake_install_dir_args(&BuildFlags::default());
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=/usr/bin"));
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SBINDIR=/usr/bin"));
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_LIBDIR=/usr/lib"));
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_LIBEXECDIR=/usr/lib")
);
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SYSCONFDIR=/etc"));
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_LOCALSTATEDIR=/var")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_SHAREDSTATEDIR=/var/lib")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_INCLUDEDIR=/usr/include")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_DATAROOTDIR=/usr/share")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_DATADIR=/usr/share")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_MANDIR=/usr/share/man")
);
assert!(
args.iter()
.any(|a| a == "-DCMAKE_INSTALL_INFODIR=/usr/share/info")
);
}
#[test]
fn test_cmake_install_dir_args_respect_explicit_user_overrides() {
let flags = BuildFlags {
configure: vec![
"-DCMAKE_INSTALL_SBINDIR=/sbin".to_string(),
"-DCMAKE_INSTALL_LIBDIR:PATH=/custom/lib".to_string(),
"-DCMAKE_INSTALL_DATADIR=/custom/share".to_string(),
],
..BuildFlags::default()
};
let args = cmake_install_dir_args(&flags);
assert!(
!args
.iter()
.any(|a| a.starts_with("-DCMAKE_INSTALL_SBINDIR="))
);
assert!(
!args
.iter()
.any(|a| a.starts_with("-DCMAKE_INSTALL_LIBDIR="))
);
assert!(
!args
.iter()
.any(|a| a.starts_with("-DCMAKE_INSTALL_DATADIR="))
);
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=/usr/bin"));
}
#[test]
fn resolve_actual_src_prefers_srcdir_then_specdir_and_handles_absolute() {
let tmp = tempdir().unwrap();
+54
View File
@@ -162,10 +162,29 @@ fn meson_setup_args(
env_vars: &[(String, String)],
) -> Vec<String> {
let mut args = Vec::new();
let dirs = crate::builder::install_dirs(flags);
if !has_option(&flags.configure, "--prefix") {
args.push(format!("--prefix={}", flags.prefix));
}
for (option, value) in [
("--bindir", dirs.bindir),
("--sbindir", dirs.sbindir),
("--libdir", dirs.libdir),
("--libexecdir", dirs.libexecdir),
("--sysconfdir", dirs.sysconfdir),
("--localstatedir", dirs.localstatedir),
("--sharedstatedir", dirs.sharedstatedir),
("--includedir", dirs.includedir),
("--datarootdir", dirs.datarootdir),
("--datadir", dirs.datadir),
("--mandir", dirs.mandir),
("--infodir", dirs.infodir),
] {
if !has_option(&flags.configure, option) {
args.push(format!("{option}={value}"));
}
}
if !has_option(&flags.configure, "--buildtype") {
args.push("--buildtype=release".to_string());
}
@@ -275,6 +294,23 @@ mod tests {
assert!(args.iter().any(|a| a == "--buildtype=release"));
}
#[test]
fn test_meson_setup_args_include_install_dirs() {
let args = meson_setup_args(&BuildFlags::default(), None, &[]);
assert!(args.iter().any(|a| a == "--bindir=/usr/bin"));
assert!(args.iter().any(|a| a == "--sbindir=/usr/bin"));
assert!(args.iter().any(|a| a == "--libdir=/usr/lib"));
assert!(args.iter().any(|a| a == "--libexecdir=/usr/lib"));
assert!(args.iter().any(|a| a == "--sysconfdir=/etc"));
assert!(args.iter().any(|a| a == "--localstatedir=/var"));
assert!(args.iter().any(|a| a == "--sharedstatedir=/var/lib"));
assert!(args.iter().any(|a| a == "--includedir=/usr/include"));
assert!(args.iter().any(|a| a == "--datarootdir=/usr/share"));
assert!(args.iter().any(|a| a == "--datadir=/usr/share"));
assert!(args.iter().any(|a| a == "--mandir=/usr/share/man"));
assert!(args.iter().any(|a| a == "--infodir=/usr/share/info"));
}
#[test]
fn test_meson_setup_args_honor_explicit_prefix() {
let flags = BuildFlags {
@@ -288,6 +324,24 @@ mod tests {
assert!(args.iter().any(|a| a == "--prefix=/opt"));
}
#[test]
fn test_meson_setup_args_honor_explicit_install_dirs() {
let flags = BuildFlags {
configure: vec![
"--sbindir=/sbin".to_string(),
"--libdir=/custom/lib".to_string(),
"--datadir=/custom/share".to_string(),
],
..BuildFlags::default()
};
let args = meson_setup_args(&flags, None, &[]);
assert!(!args.iter().any(|a| a == "--sbindir=/usr/bin"));
assert!(!args.iter().any(|a| a == "--libdir=/usr/lib"));
assert!(!args.iter().any(|a| a == "--datadir=/usr/share"));
assert!(args.iter().any(|a| a == "--bindir=/usr/bin"));
}
#[test]
fn test_resolve_build_dir_uses_flag() {
let flags = BuildFlags {
+90
View File
@@ -20,6 +20,22 @@ use std::process::{Command, Stdio};
pub type EnvVars = Vec<(String, String)>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InstallDirs {
pub bindir: String,
pub sbindir: String,
pub libdir: String,
pub libexecdir: String,
pub sysconfdir: String,
pub localstatedir: String,
pub sharedstatedir: String,
pub includedir: String,
pub datarootdir: String,
pub datadir: String,
pub mandir: String,
pub infodir: String,
}
pub fn set_env_var(env_vars: &mut EnvVars, key: &str, value: impl Into<String>) {
let value = value.into();
if let Some((_, existing)) = env_vars.iter_mut().find(|(k, _)| k == key) {
@@ -29,6 +45,46 @@ pub fn set_env_var(env_vars: &mut EnvVars, key: &str, value: impl Into<String>)
}
}
fn default_libdir_for_variant(lib32_variant: bool) -> &'static str {
if lib32_variant {
"/usr/lib32"
} else {
"/usr/lib"
}
}
fn configured_install_dir(value: &str, default: &str) -> String {
let trimmed = value.trim();
if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
}
}
pub(crate) fn install_dirs(flags: &crate::package::BuildFlags) -> InstallDirs {
let libdir = configured_install_dir(
&flags.libdir,
default_libdir_for_variant(flags.lib32_variant),
);
let datarootdir = configured_install_dir(&flags.datarootdir, "/usr/share");
InstallDirs {
bindir: configured_install_dir(&flags.bindir, "/usr/bin"),
sbindir: configured_install_dir(&flags.sbindir, "/usr/bin"),
libdir: libdir.clone(),
libexecdir: configured_install_dir(&flags.libexecdir, &libdir),
sysconfdir: configured_install_dir(&flags.sysconfdir, "/etc"),
localstatedir: configured_install_dir(&flags.localstatedir, "/var"),
sharedstatedir: configured_install_dir(&flags.sharedstatedir, "/var/lib"),
includedir: configured_install_dir(&flags.includedir, "/usr/include"),
datarootdir: datarootdir.clone(),
datadir: configured_install_dir(&flags.datadir, &datarootdir),
mandir: configured_install_dir(&flags.mandir, "/usr/share/man"),
infodir: configured_install_dir(&flags.infodir, "/usr/share/info"),
}
}
fn compiler_flag_sets(
flags: &crate::package::BuildFlags,
) -> (Vec<String>, Vec<String>, Vec<String>, Vec<String>) {
@@ -551,4 +607,38 @@ mod tests {
"expected default CC to take precedence over passthrough CC"
);
}
#[test]
fn test_install_dirs_use_defaults_and_lib32_fallbacks() {
let default_dirs = install_dirs(&BuildFlags::default());
assert_eq!(default_dirs.bindir, "/usr/bin");
assert_eq!(default_dirs.sbindir, "/usr/bin");
assert_eq!(default_dirs.libdir, "/usr/lib");
assert_eq!(default_dirs.libexecdir, "/usr/lib");
assert_eq!(default_dirs.datarootdir, "/usr/share");
assert_eq!(default_dirs.datadir, "/usr/share");
let lib32_dirs = install_dirs(&BuildFlags {
lib32_variant: true,
..BuildFlags::default()
});
assert_eq!(lib32_dirs.libdir, "/usr/lib32");
assert_eq!(lib32_dirs.libexecdir, "/usr/lib32");
}
#[test]
fn test_install_dirs_respect_explicit_overrides_and_derived_defaults() {
let dirs = install_dirs(&BuildFlags {
bindir: "/opt/bin".into(),
libdir: "/opt/lib64".into(),
datarootdir: "/opt/share-root".into(),
..BuildFlags::default()
});
assert_eq!(dirs.bindir, "/opt/bin");
assert_eq!(dirs.libdir, "/opt/lib64");
assert_eq!(dirs.libexecdir, "/opt/lib64");
assert_eq!(dirs.datarootdir, "/opt/share-root");
assert_eq!(dirs.datadir, "/opt/share-root");
}
}