use cargo::core::Dependency;
use itertools::Itertools;
use std::cmp;
use std::fmt;
use crate::config::{testing_ignore_debpolv, Config};
use crate::debian::{self, control::base_deb_name, Package};
use crate::errors::*;
#[derive(Eq, Clone)]
#[allow(clippy::upper_case_acronyms)]
enum V {
M(u64),
MM(u64, u64),
MMP(u64, u64, u64),
}
impl V {
fn new(p: &semver::Comparator) -> Result<Self> {
use self::V::*;
let mmp = match (p.minor, p.patch) {
(None, None) => M(p.major),
(Some(minor), None) => MM(p.major, minor),
(Some(minor), Some(patch)) => MMP(p.major, minor, patch),
(None, Some(_)) => debcargo_bail!("semver had patch without minor"),
};
Ok(mmp)
}
fn inclast(&self) -> V {
use self::V::*;
match *self {
M(major) => M(major + 1),
MM(major, minor) => MM(major, minor + 1),
MMP(major, minor, patch) => MMP(major, minor, patch + 1),
}
}
fn mmp(&self) -> (u64, u64, u64) {
use self::V::*;
match *self {
M(major) => (major, 0, 0),
MM(major, minor) => (major, minor, 0),
MMP(major, minor, patch) => (major, minor, patch),
}
}
}
impl cmp::Ord for V {
fn cmp(&self, other: &V) -> cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl cmp::PartialOrd for V {
fn partial_cmp(&self, other: &V) -> Option<cmp::Ordering> {
self.mmp().partial_cmp(&other.mmp())
}
}
impl cmp::PartialEq for V {
fn eq(&self, other: &V) -> bool {
self.mmp() == other.mmp()
}
}
impl fmt::Display for V {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::V::*;
match *self {
M(major) => write!(f, "{}", major),
MM(major, minor) => write!(f, "{}.{}", major, minor),
MMP(major, minor, patch) => write!(f, "{}.{}.{}", major, minor, patch),
}
}
}
struct VRange {
ge: Option<V>,
lt: Option<V>,
}
impl VRange {
fn new() -> Self {
VRange { ge: None, lt: None }
}
fn constrain_ge(&mut self, ge: V) -> &Self {
match self.ge {
Some(ref ge_) if &ge < ge_ => (),
_ => self.ge = Some(ge),
};
self
}
fn constrain_lt(&mut self, lt: V) -> &Self {
match self.lt {
Some(ref lt_) if < >= lt_ => (),
_ => self.lt = Some(lt),
};
self
}
fn to_deb_or_clause(&self, base: &str, suffix: &str) -> Result<String> {
use debian::dependency::V::*;
match (&self.ge, &self.lt) {
(None, None) => Ok(format!("{}{}", base, suffix)),
(Some(ge), None) => Ok(format!("{}{} (>= {}-~~)", base, suffix, ge)),
(None, Some(lt)) => Ok(format!("{}{} (<< {}-~~)", base, suffix, lt)),
(Some(ge), Some(lt)) => {
if ge >= lt {
debcargo_bail!("bad version range: >= {}, << {}", ge, lt);
}
let mut ranges = vec![];
let (lt_maj, lt_min, lt_pat) = lt.mmp();
let (ge_maj, ge_min, ge_pat) = ge.mmp();
if ge_maj < lt_maj {
ranges.push((M(ge_maj), Some((true, ge))));
ranges.extend((ge_maj + 1..lt_maj).map(|maj| (M(maj), None)));
ranges.push((M(lt_maj), Some((false, lt))));
} else {
assert!(ge_maj == lt_maj);
if ge_min < lt_min {
ranges.push((MM(ge_maj, ge_min), Some((true, ge))));
ranges.extend((ge_min + 1..lt_min).map(|min| (MM(ge_maj, min), None)));
ranges.push((MM(lt_maj, lt_min), Some((false, lt))));
} else {
assert!(ge_min == lt_min);
ranges.push((MMP(ge_maj, ge_min, ge_pat), Some((true, ge))));
ranges.extend(
(ge_pat + 1..lt_pat).map(|pat| (MMP(ge_maj, ge_min, pat), None)),
);
ranges.push((MMP(lt_maj, lt_min, lt_pat), Some((false, lt))));
}
};
Ok(ranges
.iter()
.rev()
.filter_map(|(ver, cons)| match cons {
None => Some(format!("{}-{}{}", base, ver, suffix)),
Some((true, c)) => {
if c == &ver {
Some(format!("{}-{}{}", base, ver, suffix))
} else {
Some(format!("{}-{}{} (>= {}-~~)", base, ver, suffix, c))
}
}
Some((false, c)) => {
if c == &ver {
None
} else {
Some(format!("{}-{}{} (<< {}-~~)", base, ver, suffix, c))
}
}
})
.join(" | "))
}
}
}
}
fn coerce_unacceptable_predicate<'a>(
dep: &Dependency,
p: &'a semver::Comparator,
allow_prerelease_deps: bool,
) -> Result<&'a semver::Op> {
let mmp = &V::new(p)?;
if !p.pre.is_empty() {
if allow_prerelease_deps || testing_ignore_debpolv() {
debcargo_warn!(
"Coercing removal of prerelease part of dependency: {} {:?}",
dep.package_name(),
p
)
} else {
debcargo_bail!(
"Cannot represent prerelease part of dependency: {} {:?}",
dep.package_name(),
p
)
}
}
use debian::dependency::V::M;
use semver::Op::*;
match (&p.op, mmp) {
(&Greater, &M(0)) => Ok(&p.op),
(&GreaterEq, &M(0)) => {
debcargo_warn!(
"Coercing unrepresentable dependency version predicate 'GtEq 0' to 'Gt 0': {} {:?}",
dep.package_name(),
p
);
Ok(&Greater)
}
(_, _) => Ok(&p.op),
}
}
fn generate_version_constraints(
vr: &mut VRange,
dep: &Dependency,
p: &semver::Comparator,
op: &semver::Op,
) -> Result<()> {
let mmp = V::new(p)?;
use debian::dependency::V::*;
use semver::Op::*;
match (*op, &mmp) {
(Less, &M(0)) | (Less, &MM(0, 0)) | (Less, &MMP(0, 0, 0)) => debcargo_bail!(
"Unrepresentable dependency version predicate: {} {:?}",
dep.package_name(),
p
),
(Less, _) => {
vr.constrain_lt(mmp);
}
(LessEq, _) => {
vr.constrain_lt(mmp.inclast());
}
(Greater, _) => {
vr.constrain_ge(mmp.inclast());
}
(GreaterEq, _) => {
vr.constrain_ge(mmp);
}
(Exact, _) | (Wildcard, _) => {
vr.constrain_lt(mmp.inclast());
vr.constrain_ge(mmp);
}
(Tilde, &M(_)) | (Tilde, &MM(_, _)) => {
vr.constrain_lt(mmp.inclast());
vr.constrain_ge(mmp);
}
(Tilde, &MMP(major, minor, _)) => {
vr.constrain_lt(MM(major, minor + 1));
vr.constrain_ge(mmp);
}
(Caret, &MMP(0, 0, _)) => {
vr.constrain_lt(mmp.inclast());
vr.constrain_ge(mmp);
}
(Caret, &MMP(0, minor, _)) | (Caret, &MM(0, minor)) => {
vr.constrain_lt(MM(0, minor + 1));
vr.constrain_ge(mmp);
}
(Caret, &MMP(major, _, _)) | (Caret, &MM(major, _)) | (Caret, &M(major)) => {
vr.constrain_lt(M(major + 1));
vr.constrain_ge(mmp);
}
(_, _) => {
panic!("Op is non-exhuastive for some reason");
}
}
Ok(())
}
pub fn deb_dep(config: &Config, dep: &Dependency) -> Result<Vec<String>> {
let dep_dashed = base_deb_name(&dep.package_name());
let mut suffixes = Vec::new();
if dep.uses_default_features() {
suffixes.push("+default-dev".to_string());
}
for feature in dep.features() {
suffixes.push(format!("+{}-dev", base_deb_name(feature)));
}
if suffixes.is_empty() {
suffixes.push("-dev".to_string());
}
let req = semver::VersionReq::parse(&dep.version_req().to_string()).unwrap();
let mut deps = Vec::new();
for suffix in suffixes {
let base = format!("{}-{}", Package::pkg_prefix(), dep_dashed);
let mut vr = VRange::new();
for p in &req.comparators {
let op = coerce_unacceptable_predicate(dep, p, config.allow_prerelease_deps)?;
generate_version_constraints(&mut vr, dep, p, op)?;
}
deps.push(vr.to_deb_or_clause(&base, &suffix)?);
}
Ok(deps)
}
pub fn deb_deps(config: &Config, cdeps: &[Dependency]) -> Result<Vec<String>> {
let mut deps = Vec::new();
for dep in cdeps {
deps.extend(deb_dep(config, dep)?.iter().map(String::to_string));
}
deps.sort();
deps.dedup();
Ok(deps)
}
pub fn deb_dep_add_nocheck(x: &str) -> String {
x.to_string()
.split('|')
.map(|x| x.trim_end().to_string() + " <!nocheck> ")
.join("|")
.trim_end()
.to_string()
}