diff options
Diffstat (limited to 'src/package/dag.rs')
-rw-r--r-- | src/package/dag.rs | 250 |
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"))); + } + } |