release_plz_core/
download.rs1use std::path::Path;
4
5use anyhow::{Context, anyhow};
6use cargo_metadata::{Package, camino::Utf8PathBuf};
7use cargo_utils::CARGO_TOML;
8use tracing::{info, instrument, warn};
9
10use crate::clone::{Cloner, ClonerSource, Crate};
11
12#[derive(Debug)]
13pub struct PackageDownloader {
14 packages: Vec<String>,
15 directory: String,
16 registry: Option<String>,
17 cargo_cwd: Option<Utf8PathBuf>,
18}
19
20impl PackageDownloader {
21 pub fn new(
22 packages: impl IntoIterator<Item = impl Into<String>>,
23 directory: impl Into<String>,
24 ) -> Self {
25 Self {
26 packages: packages.into_iter().map(Into::into).collect(),
27 directory: directory.into(),
28 registry: None,
29 cargo_cwd: None,
30 }
31 }
32
33 pub fn with_registry(self, registry: String) -> Self {
34 Self {
35 registry: Some(registry),
36 ..self
37 }
38 }
39
40 pub fn with_cargo_cwd(self, cargo_cwd: Utf8PathBuf) -> Self {
41 Self {
42 cargo_cwd: Some(cargo_cwd),
43 ..self
44 }
45 }
46
47 #[instrument]
48 pub fn download(&self) -> anyhow::Result<Vec<Package>> {
49 let source: ClonerSource = match &self.registry {
50 Some(registry) => ClonerSource::registry(registry),
51 None => ClonerSource::crates_io(),
52 };
53 info!(
54 "downloading packages from cargo registry {}",
55 source.cargo_source
56 );
57 let crates: Vec<Crate> = self
58 .packages
59 .iter()
60 .map(|package_name| Crate::new(package_name.clone(), None))
61 .collect();
62 let mut cloner_builder = Cloner::builder()
63 .with_directory(&self.directory)
64 .with_source(source);
65 if let Some(cwd) = &self.cargo_cwd {
66 cloner_builder = cloner_builder.with_cargo_cwd(cwd.clone());
67 }
68 let downloaded_packages = cloner_builder
69 .build()
70 .context("can't build cloner")?
71 .clone(&crates)
72 .context("error while downloading packages")?;
73
74 downloaded_packages
75 .iter()
76 .map(|(_package, path)| read_package(path))
77 .collect()
78 }
79}
80
81pub fn read_package(directory: impl AsRef<Path>) -> anyhow::Result<Package> {
83 let manifest_path = directory.as_ref().join(CARGO_TOML);
84 let metadata = cargo_metadata::MetadataCommand::new()
85 .no_deps()
86 .manifest_path(manifest_path)
87 .exec()
88 .context("failed to execute cargo_metadata")?;
89 let package = metadata
90 .packages
91 .first()
92 .ok_or_else(|| anyhow!("cannot retrieve package at {:?}", directory.as_ref()))?;
93 Ok(package.clone())
94}
95
96#[cfg(test)]
97mod tests {
98 use fake::Fake;
99 use tempfile::tempdir;
100
101 use super::*;
102
103 #[test]
104 #[ignore = "requires network"]
105 fn one_package_is_downloaded() {
106 let package_name = "rand";
107 let temp_dir = tempdir().unwrap();
108 let directory = temp_dir.as_ref().to_str().expect("invalid tempdir path");
109 let packages = PackageDownloader::new([package_name], directory)
110 .download()
111 .unwrap();
112 let rand = &packages[0];
113 assert_eq!(rand.name, package_name);
114 }
115
116 #[test]
117 #[ignore = "requires network"]
118 fn two_packages_are_downloaded() {
119 let first_package = "rand";
120 let second_package = "rust-gh-example";
121 let temp_dir = tempdir().unwrap();
122 let directory = temp_dir.as_ref().to_str().expect("invalid tempdir path");
123 let packages = PackageDownloader::new([first_package, second_package], directory)
124 .download()
125 .unwrap();
126 assert_eq!(&packages[0].name, first_package);
127 assert_eq!(&packages[1].name, second_package);
128 }
129
130 #[test]
131 #[ignore = "requires network"]
132 fn downloading_non_existing_package_does_not_error() {
133 let package: String = 15.fake();
135 let temp_dir = tempdir().unwrap();
136 let directory = temp_dir.as_ref().to_str().expect("invalid tempdir path");
137 PackageDownloader::new([&package], directory)
138 .download()
139 .unwrap();
140 }
141}