Skip to content

Commit

Permalink
Add recursive dependency download functionality
Browse files Browse the repository at this point in the history
- Track and display download results in an ASCII table

- Make `source already exists` not an error, just a result type

- Introduced a `--recursive` flag to the `download` subcommand to
  enable downloading of both the main packages and all their
  dependencies.

- Added `--image` and `--env` options to specify the Docker image
  and environment variables, ensuring the correct dependency tree
  is resolved based on the build environment. Took this approach
  from the tree-of command.

- Used a `HashSet` to avoid duplicate processing of packages and
  their dependencies.

This update improves clarity and tracking of the download outcomes
and allows for more comprehensive package management by
ensuring that all necessary dependencies are downloaded alongside
the requested packages.

Signed-off-by: Nico Steinle <[email protected]>
  • Loading branch information
ammernico committed Sep 4, 2024
1 parent c4f28b3 commit 02a6df8
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 6 deletions.
36 changes: 36 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,42 @@ pub fn cli() -> Command {
.help("Set timeout for download in seconds")
.value_parser(clap::value_parser!(u64))
)

.arg(Arg::new("recursive")
.action(ArgAction::SetTrue)
.required(false)
.long("recursive")
.help("Download the sources and all the dependency sources")
)

.arg(Arg::new("image")
.required(false)
.value_name("IMAGE NAME")
.short('I')
.long("image")
.help("Name of the Docker image to use")
.long_help(indoc::indoc!(r#"
Name of the Docker image to use.
Required because tree might look different on different images because of
conditions on dependencies.
"#))
)

.arg(Arg::new("env")
.required(false)
.action(ArgAction::Append)
.short('E')
.long("env")
.value_parser(env_pass_validator)
.help("Additional env to be passed when building packages")
.long_help(indoc::indoc!(r#"
Additional env to be passed when building packages.
Required because tree might look different on different images because of
conditions on dependencies.
"#))
)
)
.subcommand(Command::new("of")
.about("Get the paths of the sources of a package")
Expand Down
95 changes: 90 additions & 5 deletions src/commands/source/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,35 @@
// SPDX-License-Identifier: EPL-2.0
//

use std::collections::HashSet;
use std::concat;
use std::fmt;
use std::fmt::Display;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::anyhow;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use ascii_table::{Align, AsciiTable};
use clap::ArgMatches;
use futures::stream::{FuturesUnordered, StreamExt};
use regex::Regex;
use tokio::io::AsyncWriteExt;
use tokio::sync::{Mutex, Semaphore};
use tracing::{info, trace, warn};
use tracing::{debug, error, info, trace, warn};

use crate::config::*;
use crate::package::Package;
use crate::config::Configuration;
use crate::package::condition::ConditionData;
use crate::package::Dag;
use crate::package::PackageName;
use crate::package::PackageVersionConstraint;
use crate::repository::Repository;
use crate::util::docker::ImageNameLookup;
use crate::util::EnvironmentVariableName;

use crate::package::Package;
use crate::source::*;
use crate::util::progress::ProgressBars;

Expand All @@ -42,6 +51,17 @@ enum DownloadResult {
MarkedManual,
}

impl fmt::Display for DownloadResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DownloadResult::Forced => write!(f, "forced"),
DownloadResult::Skipped => write!(f, "skipped"),
DownloadResult::Succeeded => write!(f, "succeeded"),
DownloadResult::MarkedManual => write!(f, "marked manual"),
}
}
}

/// A wrapper around the indicatif::ProgressBar
///
/// A wrapper around the indicatif::ProgressBar that is used to synchronize status information from
Expand Down Expand Up @@ -270,6 +290,7 @@ pub async fn download(
progressbars: ProgressBars,
) -> Result<()> {
let force = matches.get_flag("force");
let recursive = matches.get_flag("recursive");
let timeout = matches.get_one::<u64>("timeout").copied();
let cache = PathBuf::from(config.source_cache_root());
let sc = SourceCache::new(cache);
Expand All @@ -294,9 +315,45 @@ pub async fn download(
NUMBER_OF_MAX_CONCURRENT_DOWNLOADS,
));

let r = find_packages(&repo, pname, pvers, matching_regexp)?;
let found_packages = find_packages(&repo, pname, pvers, matching_regexp)?;

let packages_to_download: HashSet<Package> = match recursive {
true => {
debug!("Finding package dependencies recursively");

let image_name_lookup = ImageNameLookup::create(config.docker().images())?;
let image_name = matches
.get_one::<String>("image")
.map(|s| image_name_lookup.expand(s))
.transpose()?;

let additional_env = matches
.get_many::<String>("env")
.unwrap_or_default()
.map(AsRef::as_ref)
.map(crate::util::env::parse_to_env)
.collect::<Result<Vec<(EnvironmentVariableName, String)>>>()?;

let condition_data = ConditionData {
image_name: image_name.as_ref(),
env: &additional_env,
};

let dependencies: Vec<Package> = found_packages
.iter()
.flat_map(|package| {
Dag::for_root_package((*package).clone(), &repo, None, &condition_data)
.map(|d| d.dag().graph().node_weights().cloned().collect::<Vec<_>>())
.unwrap_or_else(|_| Vec::new())
})
.collect();

HashSet::from_iter(dependencies)
}
false => HashSet::from_iter(found_packages.into_iter().cloned()),
};

let r: Vec<(SourceEntry, Result<DownloadResult>)> = r
let r: Vec<(SourceEntry, Result<DownloadResult>)> = packages_to_download
.iter()
.flat_map(|p| {
sc.sources_for(p).into_iter().map(|source| {
Expand All @@ -314,6 +371,34 @@ pub async fn download(
.collect()
.await;

let mut ascii_table = AsciiTable::default();
ascii_table
.column(0)
.set_header("Source")
.set_align(Align::Left);
ascii_table.column(1).set_header("").set_align(Align::Left);

let data: Vec<Vec<&dyn Display>> = r
.iter()
.filter_map(|v| {
if v.1.is_ok() {
let result = v.1.as_ref().unwrap() as &dyn Display;
let row: Vec<&dyn Display> = vec![v.0.package_name(), result];
Some(row)
} else {
None
}
})
.collect();

ascii_table.print(data);

for p in r {
if p.1.is_err() {
error!("{}: {:?}", p.0.package_name(), p.1);
}
}

super::verify(matches, config, repo, progressbars).await?;

Ok(())
Expand Down
4 changes: 3 additions & 1 deletion src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use anyhow::anyhow;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use getset::Getters;
use tracing::trace;
use url::Url;

Expand All @@ -37,9 +38,10 @@ impl SourceCache {
}
}

#[derive(Debug)]
#[derive(Debug, Getters)]
pub struct SourceEntry {
cache_root: PathBuf,
#[getset(get = "pub")]
package_name: PackageName,
package_version: PackageVersion,
package_source_name: String,
Expand Down

0 comments on commit 02a6df8

Please sign in to comment.