summaryrefslogtreecommitdiffstats
path: root/src/package/dag.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/package/dag.rs')
-rw-r--r--src/package/dag.rs250
1 files changed, 235 insertions, 15 deletions
diff --git a/src/package/dag.rs b/src/package/dag.rs
index aea416c..c805121 100644
--- a/src/package/dag.rs
+++ b/src/package/dag.rs
@@ -17,16 +17,23 @@ use anyhow::Error;
use anyhow::Result;
use anyhow::anyhow;
use daggy::Walker;
+use getset::Getters;
use indicatif::ProgressBar;
+use itertools::Itertools;
use log::trace;
use ptree::Style;
use ptree::TreeItem;
use resiter::AndThen;
-use getset::Getters;
use crate::package::Package;
+use crate::package::PackageName;
+use crate::package::PackageVersionConstraint;
+use crate::package::condition::ConditionCheckable;
+use crate::package::condition::ConditionData;
+use crate::package::dependency::ParseDependency;
use crate::repository::Repository;
+
#[derive(Debug, Getters)]
pub struct Dag {
#[getset(get = "pub")]
@@ -41,15 +48,69 @@ impl Dag {
p: Package,
repo: &Repository,
progress: Option<&ProgressBar>,
+ conditional_data: &ConditionData<'_>, // required for selecting packages with conditional dependencies
) -> Result<Self> {
+
+ /// helper fn with bad name to check the dependency condition of a dependency and parse the dependency into a tuple of
+ /// name and version for further processing
+ fn process<D: ConditionCheckable + ParseDependency>(d: &D, conditional_data: &ConditionData<'_>)
+ -> Result<(bool, PackageName, PackageVersionConstraint)>
+ {
+ // Check whether the condition of the dependency matches our data
+ let take = d.check_condition(conditional_data)?;
+ let (name, version) = d.parse_as_name_and_version()?;
+
+ // (dependency check result, name of the dependency, version of the dependency)
+ Ok((take, name, version))
+ }
+
+ /// Helper fn to get the dependencies of a package
+ ///
+ /// This function helps getting the dependencies of a package as an iterator over
+ /// (Name, Version).
+ ///
+ /// It also filters out dependencies that do not match the `conditional_data` passed and
+ /// makes the dependencies unique over (name, version).
+ fn get_package_dependencies<'a>(package: &'a Package, conditional_data: &'a ConditionData<'_>)
+ -> impl Iterator<Item = Result<(PackageName, PackageVersionConstraint)>> + 'a
+ {
+
+ package.dependencies()
+ .build()
+ .iter()
+ .map(move |d| process(d, conditional_data))
+ .chain({
+ package.dependencies()
+ .runtime()
+ .iter()
+ .map(move |d| process(d, conditional_data))
+ })
+
+ // Now filter out all dependencies where their condition did not match our
+ // `conditional_data`.
+ .filter(|res| match res {
+ Ok((true, _, _)) => true,
+ Ok((false, _, _)) => false,
+ Err(_) => true,
+ })
+
+ // Map out the boolean from the condition, because we don't need that later on
+ .map(|res| res.map(|(_, name, vers)| (name, vers)))
+
+ // Make all dependencies unique, because we don't want to build one dependency
+ // multiple times
+ .unique_by(|res| res.as_ref().ok().cloned())
+ }
+
fn add_sub_packages<'a>(
repo: &'a Repository,
mappings: &mut HashMap<&'a Package, daggy::NodeIndex>,
dag: &mut daggy::Dag<&'a Package, i8>,
p: &'a Package,
- progress: Option<&ProgressBar>
+ progress: Option<&ProgressBar>,
+ conditional_data: &ConditionData<'_>,
) -> Result<()> {
- p.get_self_packaged_dependencies()
+ get_package_dependencies(p, conditional_data)
.and_then_ok(|(name, constr)| {
trace!("Dependency for {} {} found: {:?}", p.name(), p.version(), name);
let packs = repo.find_with_version(&name, &constr);
@@ -69,7 +130,7 @@ impl Dag {
mappings.insert(p, idx);
trace!("Recursing for: {:?}", p);
- add_sub_packages(repo, mappings, dag, p, progress)
+ add_sub_packages(repo, mappings, dag, p, progress, conditional_data)
})
} else {
Ok(())
@@ -78,9 +139,13 @@ impl Dag {
.collect::<Result<()>>()
}
- fn add_edges(mappings: &HashMap<&Package, daggy::NodeIndex>, dag: &mut daggy::Dag<&Package, i8>) -> Result<()> {
+ fn add_edges(mappings: &HashMap<&Package, daggy::NodeIndex>,
+ dag: &mut daggy::Dag<&Package, i8>,
+ conditional_data: &ConditionData<'_>,
+ ) -> Result<()>
+ {
for (package, idx) in mappings {
- package.get_self_packaged_dependencies()
+ get_package_dependencies(package, conditional_data)
.and_then_ok(|(name, constr)| {
mappings
.iter()
@@ -103,8 +168,8 @@ impl Dag {
trace!("Making package Tree for {:?}", p);
let root_idx = dag.add_node(&p);
mappings.insert(&p, root_idx);
- add_sub_packages(repo, &mut mappings, &mut dag, &p, progress)?;
- add_edges(&mappings, &mut dag)?;
+ add_sub_packages(repo, &mut mappings, &mut dag, &p, progress, conditional_data)?;
+ add_edges(&mappings, &mut dag, conditional_data)?;
trace!("Finished makeing package Tree");
Ok(Dag {
@@ -159,11 +224,14 @@ mod tests {
use std::collections::BTreeMap;
+ use crate::package::Dependencies;
+ use crate::package::Dependency;
+ use crate::package::condition::Condition;
+ use crate::package::condition::OneOrMore;
use crate::package::tests::package;
use crate::package::tests::pname;
use crate::package::tests::pversion;
- use crate::package::Dependencies;
- use crate::package::Dependency;
+ use crate::util::docker::ImageName;
use indicatif::ProgressBar;
@@ -182,7 +250,13 @@ mod tests {
let repo = Repository::from(btree);
let progress = ProgressBar::hidden();
- let r = Dag::for_root_package(p1, &repo, Some(&progress));
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
+
assert!(r.is_ok());
}
@@ -214,7 +288,12 @@ mod tests {
let repo = Repository::from(btree);
let progress = ProgressBar::hidden();
- let dag = Dag::for_root_package(p1, &repo, Some(&progress));
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
assert!(dag.is_ok());
let dag = dag.unwrap();
let ps = dag.all_packages();
@@ -303,7 +382,12 @@ mod tests {
let repo = Repository::from(btree);
let progress = ProgressBar::hidden();
- let r = Dag::for_root_package(p1, &repo, Some(&progress));
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
assert!(r.is_ok());
let r = r.unwrap();
let ps = r.all_packages();
@@ -446,7 +530,12 @@ mod tests {
let repo = Repository::from(btree);
let progress = ProgressBar::hidden();
- let r = Dag::for_root_package(p1, &repo, Some(&progress));
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
assert!(r.is_ok());
let r = r.unwrap();
let ps = r.all_packages();
@@ -551,7 +640,12 @@ mod tests {
let repo = Repository::from(btree);
let progress = ProgressBar::hidden();
- let r = Dag::for_root_package(p1, &repo, Some(&progress));
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
assert!(r.is_ok());
let r = r.unwrap();
let ps = r.all_packages();
@@ -560,5 +654,131 @@ mod tests {
assert!(ps.iter().any(|p| *p.name() == pname("p3")));
assert!(ps.iter().any(|p| *p.name() == pname("p4")));
}
+
+
+ /// Build a repository with two packages and a condition for their dependency
+ fn repo_with_ab_packages_with_condition(cond: Condition) -> (Package, Repository) {
+ let mut btree = BTreeMap::new();
+
+ let mut p1 = {
+ let name = "a";
+ let vers = "1";
+ let pack = package(name, vers, "https://rust-lang.org", "123");
+ btree.insert((pname(name), pversion(vers)), pack.clone());
+ pack
+ };
+
+ {
+ let name = "b";
+ let vers = "2";
+ let pack = package(name, vers, "https://rust-lang.org", "124");
+ btree.insert((pname(name), pversion(vers)), pack);
+ }
+
+ {
+ let d = Dependency::new_conditional(String::from("b =2"), cond);
+ let ds = Dependencies::with_runtime_dependency(d);
+ p1.set_dependencies(ds);
+ }
+
+ (p1, Repository::from(btree))
+ }
+
+ // Test whether the dependency DAG is correctly build if there is NO conditional data passed
+ //
+ // Because the dependency is conditional with "fooimage" required as build-image, the
+ // dependency DAG should NOT contain package "b"
+ #[test]
+ fn test_add_two_dependent_packages_with_image_conditional() {
+ let condition = {
+ let in_image = Some(OneOrMore::<String>::One(String::from("fooimage")));
+ Condition::new(None, None, in_image)
+ };
+ let (p1, repo) = repo_with_ab_packages_with_condition(condition);
+
+ let condition_data = ConditionData {
+ image_name: None,
+ env: &[],
+ };
+
+ let progress = ProgressBar::hidden();
+
+ let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
+ assert!(dag.is_ok());
+ let dag = dag.unwrap();
+ let ps = dag.all_packages();
+
+ assert!(ps.iter().any(|p| *p.name() == pname("a")));
+ assert!(ps.iter().any(|p| *p.version() == pversion("1")));
+
+ // Not in the tree:
+ assert!(!ps.iter().any(|p| *p.name() == pname("b")), "'b' should not be in tree, but is: {:?}", ps);
+ assert!(!ps.iter().any(|p| *p.version() == pversion("2")), "'2' should not be in tree, but is: {:?}", ps);
+ }
+
+ // Test whether the dependency DAG is correctly build if a image is used, but not the one
+ // required
+ //
+ // Because the dependency is conditional with "fooimage" required as build-image, but
+ // "barimage" is used, the dependency DAG should NOT contain package "b"
+ #[test]
+ fn test_add_two_dependent_packages_with_image_conditional_but_other_image_provided() {
+ let condition = {
+ let in_image = Some(OneOrMore::<String>::One(String::from("fooimage")));
+ Condition::new(None, None, in_image)
+ };
+ let (p1, repo) = repo_with_ab_packages_with_condition(condition);
+
+ let img_name = ImageName::from("barimage");
+ let condition_data = ConditionData {
+ image_name: Some(&img_name),
+ env: &[],
+ };
+
+ let progress = ProgressBar::hidden();
+
+ let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
+ assert!(dag.is_ok());
+ let dag = dag.unwrap();
+ let ps = dag.all_packages();
+
+ assert!(ps.iter().any(|p| *p.name() == pname("a")));
+ assert!(ps.iter().any(|p| *p.version() == pversion("1")));
+
+ // Not in the tree:
+ assert!(!ps.iter().any(|p| *p.name() == pname("b")));
+ assert!(!ps.iter().any(|p| *p.version() == pversion("2")));
+ }
+
+ // Test whether the dependency DAG is correctly build if the right image name is passed
+ #[test]
+ fn test_add_two_dependent_packages_with_image_conditional_and_image_provided() {
+ let condition = {
+ let in_image = Some(OneOrMore::<String>::One(String::from("fooimage")));
+ Condition::new(None, None, in_image)
+ };
+ let (p1, repo) = repo_with_ab_packages_with_condition(condition);
+
+ let img_name = ImageName::from("fooimage");
+ let condition_data = ConditionData {
+ image_name: Some(&img_name),
+ env: &[],
+ };
+
+ let progress = ProgressBar::hidden();
+
+ let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data);
+ assert!(dag.is_ok());
+ let dag = dag.unwrap();
+ let ps = dag.all_packages();
+
+ assert!(ps.iter().any(|p| *p.name() == pname("a")));
+ assert!(ps.iter().any(|p| *p.version() == pversion("1")));
+
+ // IN the tree:
+ assert!(ps.iter().any(|p| *p.name() == pname("b")));
+ assert!(ps.iter().any(|p| *p.version() == pversion("2")));
+ }
+
}