use rpm_spec::ast::{Text, TextSegment};
use rpm_spec_profile::{Family, Profile};
#[derive(Debug, Clone, Copy)]
pub(crate) struct PolicyRegistry {
pub systemd_macros: &'static [&'static str],
pub tmpfiles_create_macros: &'static [&'static str],
pub disttag: DistTagPolicy,
pub hardcoded_dist_substrings: &'static [&'static str],
pub build_tool_to_buildrequires: &'static [(&'static str, &'static str)],
pub scriptlet_required_deps: &'static [(&'static str, &'static str)],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DistTagPolicy {
Required,
NotApplicable,
}
impl PolicyRegistry {
pub fn for_family(family: Option<Family>) -> Self {
match family {
Some(Family::Fedora) | Some(Family::Rhel) => Self {
systemd_macros: FEDORA_SYSTEMD_MACROS,
tmpfiles_create_macros: FEDORA_TMPFILES_MACROS,
disttag: DistTagPolicy::Required,
hardcoded_dist_substrings: FEDORA_HARDCODED_DIST,
build_tool_to_buildrequires: DEFAULT_BUILD_TOOL_BRS,
scriptlet_required_deps: DEFAULT_SCRIPTLET_DEPS,
},
Some(Family::Opensuse) => Self {
systemd_macros: OPENSUSE_SYSTEMD_MACROS,
tmpfiles_create_macros: OPENSUSE_TMPFILES_MACROS,
disttag: DistTagPolicy::NotApplicable,
hardcoded_dist_substrings: &[],
build_tool_to_buildrequires: DEFAULT_BUILD_TOOL_BRS,
scriptlet_required_deps: DEFAULT_SCRIPTLET_DEPS,
},
Some(Family::Mageia) => Self {
systemd_macros: MAGEIA_SYSTEMD_MACROS,
tmpfiles_create_macros: FEDORA_TMPFILES_MACROS,
disttag: DistTagPolicy::NotApplicable,
hardcoded_dist_substrings: &[".mga"],
build_tool_to_buildrequires: DEFAULT_BUILD_TOOL_BRS,
scriptlet_required_deps: DEFAULT_SCRIPTLET_DEPS,
},
Some(Family::Alt) => Self {
systemd_macros: ALT_SYSTEMD_MACROS,
tmpfiles_create_macros: ALT_TMPFILES_MACROS,
disttag: DistTagPolicy::NotApplicable,
hardcoded_dist_substrings: &[],
build_tool_to_buildrequires: DEFAULT_BUILD_TOOL_BRS,
scriptlet_required_deps: DEFAULT_SCRIPTLET_DEPS,
},
Some(Family::Generic) | None => Self::generic(),
Some(_) => Self::generic(),
}
}
pub fn for_profile(profile: &Profile) -> Self {
Self::for_family(profile.identity.family)
}
pub fn generic() -> Self {
Self {
systemd_macros: &[],
tmpfiles_create_macros: &[],
disttag: DistTagPolicy::NotApplicable,
hardcoded_dist_substrings: &[],
build_tool_to_buildrequires: &[],
scriptlet_required_deps: &[],
}
}
}
impl Default for PolicyRegistry {
fn default() -> Self {
Self::generic()
}
}
pub(crate) fn line_references_any_macro(line: &Text, macros: &[&str]) -> bool {
line.segments.iter().any(|seg| match seg {
TextSegment::Macro(m) => macros.contains(&m.name.as_str()),
_ => false,
})
}
const FEDORA_SYSTEMD_MACROS: &[&str] = &[
"systemd_post",
"systemd_preun",
"systemd_postun",
"systemd_postun_with_restart",
"systemd_user_post",
"systemd_user_preun",
"systemd_user_postun_with_restart",
"systemd_requires",
"systemd_ordering",
];
const FEDORA_TMPFILES_MACROS: &[&str] = &["tmpfiles_create", "tmpfiles_create_package"];
const FEDORA_HARDCODED_DIST: &[&str] = &[".fc", ".el"];
const OPENSUSE_SYSTEMD_MACROS: &[&str] = &[
"service_add_pre",
"service_add_post",
"service_del_preun",
"service_del_postun",
"service_del_postun_with_restart",
];
const OPENSUSE_TMPFILES_MACROS: &[&str] = &["tmpfiles_create"];
const MAGEIA_SYSTEMD_MACROS: &[&str] = FEDORA_SYSTEMD_MACROS;
const ALT_SYSTEMD_MACROS: &[&str] = &[
"post_service",
"preun_service",
"postun_service",
"post_systemd_unit",
"preun_systemd_unit",
];
const ALT_TMPFILES_MACROS: &[&str] = &["systemd_tmpfiles_create", "tmpfiles_create"];
const DEFAULT_BUILD_TOOL_BRS: &[(&str, &str)] = &[
("cmake", "cmake"),
("meson", "meson"),
("ninja", "ninja-build"),
("autoreconf", "autoconf"),
("automake", "automake"),
("libtoolize", "libtool"),
("pkg-config", "pkgconfig"),
("pkgconf", "pkgconfig"),
("desktop-file-install", "desktop-file-utils"),
("desktop-file-validate", "desktop-file-utils"),
("appstreamcli", "appstream"),
("update-mime-database", "shared-mime-info"),
("gtk-update-icon-cache", "gtk-update-icon-cache"),
("glib-compile-schemas", "glib2"),
];
const DEFAULT_SCRIPTLET_DEPS: &[(&str, &str)] = &[
("systemctl", "systemd"),
("useradd", "shadow-utils"),
("groupadd", "shadow-utils"),
("usermod", "shadow-utils"),
("groupmod", "shadow-utils"),
("getent", "glibc-common"),
("update-alternatives", "alternatives"),
("install-info", "info"),
("glib-compile-schemas", "glib2"),
("gtk-update-icon-cache", "gtk-update-icon-cache"),
("update-mime-database", "shared-mime-info"),
];
#[cfg(test)]
mod tests {
use super::*;
use rpm_spec::ast::{ConditionalMacro, MacroKind, MacroRef, Text, TextSegment};
fn macro_seg(name: &str) -> TextSegment {
TextSegment::macro_ref(MacroRef {
kind: MacroKind::Braced,
name: name.into(),
args: Vec::new(),
conditional: ConditionalMacro::None,
with_value: None,
})
}
#[test]
fn fedora_disttag_is_required() {
let p = PolicyRegistry::for_family(Some(Family::Fedora));
assert_eq!(p.disttag, DistTagPolicy::Required);
assert!(p.systemd_macros.contains(&"systemd_post"));
assert!(!p.systemd_macros.contains(&"service_add_post"));
}
#[test]
fn opensuse_uses_service_macros() {
let p = PolicyRegistry::for_family(Some(Family::Opensuse));
assert_eq!(p.disttag, DistTagPolicy::NotApplicable);
assert!(p.systemd_macros.contains(&"service_add_post"));
assert!(!p.systemd_macros.contains(&"systemd_post"));
}
#[test]
fn generic_is_silent_baseline() {
let p = PolicyRegistry::for_family(None);
assert_eq!(p.disttag, DistTagPolicy::NotApplicable);
assert!(p.systemd_macros.is_empty());
assert!(p.tmpfiles_create_macros.is_empty());
}
#[test]
fn alt_uses_its_own_systemd_macros() {
let p = PolicyRegistry::for_family(Some(Family::Alt));
assert!(p.systemd_macros.contains(&"post_service"));
assert!(
p.tmpfiles_create_macros
.contains(&"systemd_tmpfiles_create")
);
}
#[test]
fn mageia_has_dist_substrings_but_not_required() {
let p = PolicyRegistry::for_family(Some(Family::Mageia));
assert_eq!(p.disttag, DistTagPolicy::NotApplicable);
assert_eq!(p.hardcoded_dist_substrings, &[".mga"]);
}
#[test]
fn default_is_generic() {
let p = PolicyRegistry::default();
assert!(p.systemd_macros.is_empty());
assert_eq!(p.disttag, DistTagPolicy::NotApplicable);
}
#[test]
fn line_references_any_macro_finds_known_macro() {
let line = Text {
segments: vec![
TextSegment::Literal(" ".into()),
macro_seg("systemd_post"),
TextSegment::Literal(" foo.service".into()),
],
};
assert!(line_references_any_macro(
&line,
&["systemd_post", "service_add_post"],
));
}
#[test]
fn line_references_any_macro_misses_unknown() {
let line = Text {
segments: vec![macro_seg("some_other_macro")],
};
assert!(!line_references_any_macro(&line, &["systemd_post"]));
}
#[test]
fn line_references_any_macro_ignores_literals() {
let line = Text::from("systemd_post foo");
assert!(!line_references_any_macro(&line, &["systemd_post"]));
}
}