use {
crate::{
control::ControlParagraph,
error::{DebianError, Result},
package_version::PackageVersion,
},
once_cell::sync::Lazy,
regex::Regex,
std::{
cmp::Ordering,
fmt::{Display, Formatter},
ops::{Deref, DerefMut},
str::FromStr,
},
};
pub static RE_DEPENDENCY: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?x)
# Package name is alphanumeric, terminating at whitespace, [ or (
(?P<package>[^\s\[(]+)
# Any number of optional spaces.
\s*
# Relationships are within an optional parenthesis.
(?:\(
# Optional spaces after (
\s*
# The relationship operator.
(?P<relop>(<<|<=|=|>=|>>))
# Optional spaces after the operator.
\s*
# Version string is everything up to space or closing parenthesis.
(?P<version>[^\s)]+)
# Trailing space before ).
\s*
\))?
# Any amount of space after optional relationship definition.
\s*
# Architecture restrictions are within an optional [..] field.
(?:\[
# Optional whitespace after [
\s*
# Optional negation operator.
(?P<arch_negate>!)?
\s*
# The architecture. May have spaces to delimit multiple values.
(?P<arch>[^\]]+)
\])?
",
)
.unwrap()
});
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VersionRelationship {
StrictlyEarlier,
EarlierOrEqual,
ExactlyEqual,
LaterOrEqual,
StrictlyLater,
}
impl Display for VersionRelationship {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Self::StrictlyEarlier => write!(f, "<<"),
Self::EarlierOrEqual => write!(f, "<="),
Self::ExactlyEqual => write!(f, "="),
Self::LaterOrEqual => write!(f, ">="),
Self::StrictlyLater => write!(f, ">>"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DependencyVersionConstraint {
pub relationship: VersionRelationship,
pub version: PackageVersion,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SingleDependency {
pub package: String,
pub version_constraint: Option<DependencyVersionConstraint>,
pub architectures: Option<(bool, Vec<String>)>,
}
impl Display for SingleDependency {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.package)?;
if let Some(constraint) = &self.version_constraint {
write!(f, " ({} {})", constraint.relationship, constraint.version)?;
}
if let Some((negate, arch)) = &self.architectures {
write!(f, " [{}{}]", if *negate { "!" } else { "" }, arch.join(" "))?;
}
Ok(())
}
}
impl SingleDependency {
pub fn parse(s: &str) -> Result<Self> {
let caps = RE_DEPENDENCY
.captures(s)
.ok_or_else(|| DebianError::DependencyParse(s.to_string()))?;
let package = caps["package"].to_string();
let dependency = match (caps.name("relop"), caps.name("version")) {
(Some(relop), Some(version)) => {
let relationship = match relop.as_str() {
"<<" => VersionRelationship::StrictlyEarlier,
"<=" => VersionRelationship::EarlierOrEqual,
"=" => VersionRelationship::ExactlyEqual,
">=" => VersionRelationship::LaterOrEqual,
">>" => VersionRelationship::StrictlyLater,
v => panic!("unexpected version relationship: {}", v),
};
let version = PackageVersion::parse(version.as_str())?;
Some(DependencyVersionConstraint {
relationship,
version,
})
}
_ => None,
};
let architectures = match (caps.name("arch_negate"), caps.name("arch")) {
(Some(_), Some(arch)) => Some((
true,
arch.as_str()
.split_ascii_whitespace()
.map(|x| x.to_string())
.collect::<Vec<_>>(),
)),
(None, Some(arch)) => Some((
false,
arch.as_str()
.split_ascii_whitespace()
.map(|x| x.to_string())
.collect::<Vec<_>>(),
)),
_ => None,
};
Ok(Self {
package,
version_constraint: dependency,
architectures,
})
}
pub fn package_satisfies(
&self,
package: &str,
version: &PackageVersion,
architecture: &str,
) -> bool {
if self.package == package {
if let Some((negate, arches)) = &self.architectures {
let contains = arches.iter().any(|x| x == architecture);
if (*negate && contains) || (!*negate && !contains) {
return false;
}
}
if let Some(constaint) = &self.version_constraint {
matches!(
(version.cmp(&constaint.version), constaint.relationship),
(
Ordering::Equal,
VersionRelationship::ExactlyEqual
| VersionRelationship::LaterOrEqual
| VersionRelationship::EarlierOrEqual,
) | (
Ordering::Less,
VersionRelationship::StrictlyEarlier | VersionRelationship::EarlierOrEqual,
) | (
Ordering::Greater,
VersionRelationship::StrictlyLater | VersionRelationship::LaterOrEqual,
)
)
} else {
true
}
} else {
false
}
}
pub fn package_satisfies_virtual(
&self,
package: &str,
provides: Option<&DependencyVersionConstraint>,
) -> bool {
if self.package == package {
if let (Some(wanted_constraint), Some(provides)) =
(&self.version_constraint.as_ref(), provides)
{
matches!(
(
provides.version.cmp(&wanted_constraint.version),
wanted_constraint.relationship,
provides.relationship,
),
(
Ordering::Equal,
VersionRelationship::ExactlyEqual
| VersionRelationship::LaterOrEqual
| VersionRelationship::EarlierOrEqual,
VersionRelationship::ExactlyEqual
| VersionRelationship::LaterOrEqual
| VersionRelationship::EarlierOrEqual,
)
|
(
Ordering::Less,
VersionRelationship::EarlierOrEqual | VersionRelationship::StrictlyEarlier,
VersionRelationship::EarlierOrEqual | VersionRelationship::StrictlyEarlier,
) |
(
Ordering::Greater,
VersionRelationship::LaterOrEqual | VersionRelationship::StrictlyLater,
VersionRelationship::LaterOrEqual | VersionRelationship::StrictlyLater,
)
)
} else {
true
}
} else {
false
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct DependencyVariants(Vec<SingleDependency>);
impl Display for DependencyVariants {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<_>>()
.join(" | ")
)
}
}
impl Deref for DependencyVariants {
type Target = Vec<SingleDependency>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DependencyVariants {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl DependencyVariants {
pub fn package_satisfies(&self, package: &str, version: &PackageVersion, arch: &str) -> bool {
self.0
.iter()
.any(|variant| variant.package_satisfies(package, version, arch))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DependencyList {
dependencies: Vec<DependencyVariants>,
}
impl Display for DependencyList {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"{}",
self.dependencies
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl DependencyList {
pub fn parse(s: &str) -> Result<Self> {
let mut els = vec![];
for el in s.split(',') {
let el = el.trim();
let mut variants = DependencyVariants::default();
for alt in el.split('|') {
let alt = alt.trim();
variants.push(SingleDependency::parse(alt)?);
}
els.push(variants);
}
Ok(Self { dependencies: els })
}
pub fn package_satisfies(&self, package: &str, version: &PackageVersion, arch: &str) -> bool {
self.dependencies
.iter()
.any(|variants| variants.package_satisfies(package, version, arch))
}
pub fn requirements(&self) -> impl Iterator<Item = &DependencyVariants> {
self.dependencies.iter()
}
}
#[derive(Clone, Copy, Debug)]
pub enum BinaryDependency {
Depends,
Recommends,
Suggests,
Enhances,
PreDepends,
}
impl FromStr for BinaryDependency {
type Err = DebianError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"Depends" => Ok(Self::Depends),
"Recommends" => Ok(Self::Recommends),
"Suggests" => Ok(Self::Suggests),
"Enhances" => Ok(Self::Enhances),
"Pre-Depends" => Ok(Self::PreDepends),
_ => Err(Self::Err::UnknownBinaryDependencyField(s.to_string())),
}
}
}
impl Display for BinaryDependency {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Depends => "Depends",
Self::Recommends => "Recommends",
Self::Suggests => "Suggests",
Self::Enhances => "Enhances",
Self::PreDepends => "Pre-Depends",
}
)
}
}
impl BinaryDependency {
pub fn values() -> &'static [Self] {
&[
Self::Depends,
Self::Recommends,
Self::Suggests,
Self::Enhances,
Self::PreDepends,
]
}
}
#[derive(Clone, Debug)]
pub struct PackageDependencyFields {
pub depends: Option<DependencyList>,
pub recommends: Option<DependencyList>,
pub suggests: Option<DependencyList>,
pub enhances: Option<DependencyList>,
pub pre_depends: Option<DependencyList>,
pub breaks: Option<DependencyList>,
pub conflicts: Option<DependencyList>,
pub provides: Option<DependencyList>,
pub replaces: Option<DependencyList>,
pub build_depends: Option<DependencyList>,
pub build_depends_indep: Option<DependencyList>,
pub build_depends_arch: Option<DependencyList>,
pub build_conflicts: Option<DependencyList>,
pub build_conflicts_indep: Option<DependencyList>,
pub build_conflicts_arch: Option<DependencyList>,
pub built_using: Option<DependencyList>,
}
impl PackageDependencyFields {
pub fn from_paragraph(para: &ControlParagraph) -> Result<Self> {
let get_field = |field| -> Result<Option<DependencyList>> {
if let Some(value) = para.field_str(field) {
Ok(Some(DependencyList::parse(value)?))
} else {
Ok(None)
}
};
Ok(Self {
depends: get_field("Depends")?,
recommends: get_field("Recommends")?,
suggests: get_field("Suggests")?,
enhances: get_field("Enhances")?,
pre_depends: get_field("Pre-Depends")?,
breaks: get_field("Breaks")?,
conflicts: get_field("Conflicts")?,
provides: get_field("Provides")?,
replaces: get_field("Replaces")?,
build_depends: get_field("Build-Depends")?,
build_depends_indep: get_field("Build-Depends-Indep")?,
build_depends_arch: get_field("Build-Depends-Arch")?,
build_conflicts: get_field("Build-Conflicts")?,
build_conflicts_indep: get_field("Build-Conflicts-Indep")?,
build_conflicts_arch: get_field("Build-Conflicts-Arch")?,
built_using: get_field("Built-Using")?,
})
}
pub fn binary_dependency(&self, field: BinaryDependency) -> Option<&DependencyList> {
match field {
BinaryDependency::Depends => self.depends.as_ref(),
BinaryDependency::Recommends => self.recommends.as_ref(),
BinaryDependency::Suggests => self.suggests.as_ref(),
BinaryDependency::Enhances => self.enhances.as_ref(),
BinaryDependency::PreDepends => self.pre_depends.as_ref(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_depends() -> Result<()> {
let dl = DependencyList::parse("libc6 (>= 2.4), libx11-6")?;
assert_eq!(dl.dependencies.len(), 2);
assert_eq!(dl.dependencies[0].0.len(), 1);
assert_eq!(dl.dependencies[1].0.len(), 1);
assert_eq!(
dl.dependencies[0].0[0],
SingleDependency {
package: "libc6".into(),
version_constraint: Some(DependencyVersionConstraint {
relationship: VersionRelationship::LaterOrEqual,
version: PackageVersion::parse("2.4").unwrap()
}),
architectures: None,
}
);
assert_eq!(
dl.dependencies[1].0[0],
SingleDependency {
package: "libx11-6".into(),
version_constraint: None,
architectures: None,
}
);
let dl = DependencyList::parse("libc [amd64]")?;
assert_eq!(dl.dependencies.len(), 1);
assert_eq!(dl.dependencies[0].0.len(), 1);
assert_eq!(
dl.dependencies[0].0[0],
SingleDependency {
package: "libc".into(),
version_constraint: None,
architectures: Some((false, vec!["amd64".into()])),
}
);
let dl = DependencyList::parse("libc [!amd64 i386]")?;
assert_eq!(dl.dependencies.len(), 1);
assert_eq!(dl.dependencies[0].0.len(), 1);
assert_eq!(
dl.dependencies[0].0[0],
SingleDependency {
package: "libc".into(),
version_constraint: None,
architectures: Some((true, vec!["amd64".into(), "i386".into()])),
}
);
Ok(())
}
#[test]
fn satisfies_version_constraints() -> Result<()> {
let dl = DependencyList::parse("libc (= 2.4)")?;
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.5")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"other",
&PackageVersion::parse("2.4")?,
"ignored"
));
let dl = DependencyList::parse("libc (<= 2.4)")?;
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"ignored"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.5")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"other",
&PackageVersion::parse("2.4")?,
"ignored"
));
let dl = DependencyList::parse("libc (>= 2.4)")?;
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"ignored"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"ignored"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.5")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"other",
&PackageVersion::parse("2.4")?,
"ignored"
));
let dl = DependencyList::parse("libc (<< 2.4)")?;
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.5")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"other",
&PackageVersion::parse("2.3")?,
"ignored"
));
let dl = DependencyList::parse("libc (>> 2.4)")?;
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"ignored"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.5")?,
"ignored"
));
assert!(!dl.dependencies[0].package_satisfies(
"other",
&PackageVersion::parse("2.5")?,
"ignored"
));
Ok(())
}
#[test]
fn satisfies_architecture_constraints() -> Result<()> {
let dl = DependencyList::parse("libc [amd64]")?;
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"amd64"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"x86"
));
let dl = DependencyList::parse("libc [amd64 i386]")?;
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"amd64"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"i386"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"arm64"
));
let dl = DependencyList::parse("libc [!amd64]")?;
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"amd64"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"x86"
));
let dl = DependencyList::parse("libc [!amd64 i386]")?;
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.4")?,
"amd64"
));
assert!(!dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"i386"
));
assert!(dl.dependencies[0].package_satisfies(
"libc",
&PackageVersion::parse("2.3")?,
"arm64"
));
Ok(())
}
}