1#![doc = include_str!("../README.md")]
2
3use cargo::{
4 core::{PackageId, PackageSet, SourceId, SourceMap},
5 sources::CRATES_IO_INDEX,
6 util::IntoUrl,
7};
8use semver::Version;
9use std::{collections::HashSet, io::Write, path::PathBuf, str::FromStr, task::Poll};
10use url::Url;
11
12#[derive(Debug)]
26pub struct PackageFetcher {
27 config: cargo::Config,
28}
29
30impl PackageFetcher {
31 pub fn new() -> Result<Self, String> {
36 Ok(Self {
37 config: cargo::Config::default().map_err(|e| e.to_string())?,
38 })
39 }
40
41 pub fn with_out(out: Box<dyn Write>, verbosity: Option<Verbosity>) -> Result<Self, String> {
45 let mut shell = cargo::core::Shell::from_write(out);
46 shell.set_verbosity(verbosity.unwrap_or_default().into());
47 let new_self = Self::new()?;
48 {
49 let mut sh = new_self.config.shell();
50 *sh = shell;
51 }
52 Ok(new_self)
53 }
54
55 pub fn resolve_package<N: AsRef<str>>(
61 &self,
62 name: N,
63 version: Option<&str>,
64 source: &PackageSource,
65 yanked_whitelist: Option<HashSet<Package>>,
66 ) -> Result<Vec<Package>, String> {
67 let _lock = self.config.acquire_package_cache_lock().map_err(|e| e.to_string())?;
68 let src = source.to_source_id().map_err(|e| e.to_string())?;
69
70 let whitelist: HashSet<PackageId>;
71
72 if let Some(wl) = yanked_whitelist {
73 whitelist = wl.iter().map(|p| p.package_id).collect();
74 } else {
75 whitelist = Default::default();
76 };
77
78 let mut src = src.load(&self.config, &whitelist).map_err(|e| e.to_string())?;
79
80 let dep = cargo::core::Dependency::parse(name.as_ref(), version, src.source_id())
81 .map_err(|e| e.to_string())?;
82
83 let mut pkgs = vec![];
84
85 src.block_until_ready().map_err(|e| e.to_string())?;
86 let Poll::Ready(res) = src.query(&dep, cargo::core::QueryKind::Exact, &mut |sum| {pkgs.push(Package {package_id: sum.package_id()})}) else {
87 return Err("cargo returned a `Poll::Pending` after `block_until_ready`".into());
88 };
89
90 res.map_err(|e| e.to_string())?;
91
92 Ok(pkgs)
93 }
94
95 pub fn resolve_first<N: AsRef<str>>(
99 &self,
100 name: N,
101 version: Option<&str>,
102 source: &PackageSource,
103 yanked_whitelist: Option<HashSet<Package>>,
104 ) -> Result<Package, String> {
105 let _lock = self.config.acquire_package_cache_lock().map_err(|e| e.to_string())?;
106 let src = source.to_source_id().map_err(|e| e.to_string())?;
107
108 let whitelist: HashSet<PackageId>;
109
110 if let Some(wl) = yanked_whitelist {
111 whitelist = wl.iter().map(|p| p.package_id).collect();
112 } else {
113 whitelist = Default::default();
114 };
115
116 let mut src = src.load(&self.config, &whitelist).map_err(|e| e.to_string())?;
117
118 let dep = cargo::core::Dependency::parse(name.as_ref(), version, src.source_id())
119 .map_err(|e| e.to_string())?;
120
121 let mut pkg: Option<PackageId> = None;
122
123 src.block_until_ready().map_err(|e| e.to_string())?;
124 let Poll::Ready(res) = src.query(&dep, cargo::core::QueryKind::Exact, &mut |sum| {pkg = Some(sum.package_id())}) else {
125 return Err("cargo returned a `Poll::Pending` after `block_until_ready`".into());
126 };
127
128 res.map_err(|e| e.to_string())?;
129
130 if let Some(pkg) = pkg {
131 Ok(Package { package_id: pkg })
132 } else {
133 Err("cargo wasn't able to find the requested package".into())
134 }
135 }
136
137 pub fn fetch(&mut self, package: Package) -> Result<PathBuf, String> {
139 let _lock = self.config.acquire_package_cache_lock().map_err(|e| e.to_string())?;
140 let mut map = SourceMap::new();
141
142 let whitelist: HashSet<PackageId> = std::iter::once(package.package_id).collect();
143
144 let mut source = package
145 .package_id
146 .source_id()
147 .load(&self.config, &whitelist)
148 .map_err(|e| e.to_string())?;
149
150 source.block_until_ready().map_err(|e| e.to_string())?;
151
152 map.insert(source);
153
154 let package_set = PackageSet::new(&[package.package_id], map, &self.config).map_err(|e| e.to_string())?;
155 Ok(package_set
156 .get_one(package.package_id)
157 .map_err(|e| e.to_string())?
158 .root()
159 .into())
160 }
161
162 pub fn fetch_many(
173 &mut self,
174 packages: &[Package],
175 ) -> Result<Vec<PathBuf>, String> {
176 let _lock = self.config.acquire_package_cache_lock().map_err(|e| e.to_string())?;
177 let mut map = SourceMap::new();
178
179 let whitelist: HashSet<PackageId> = packages.iter().map(|p| p.package_id).collect();
180
181 for package in packages {
182 let mut source = package
183 .package_id
184 .source_id()
185 .load(&self.config, &whitelist)
186 .map_err(|e| e.to_string())?;
187 source.block_until_ready().map_err(|e| e.to_string())?;
188 map.insert(source);
189 }
190
191 let packages: Vec<PackageId> = packages.iter().map(|p| p.package_id).collect();
192 let package_set = PackageSet::new(&packages, map, &self.config).map_err(|e| e.to_string())?;
193 Ok(package_set
194 .get_many(package_set.package_ids())
195 .map_err(|e| e.to_string())?
196 .iter()
197 .map(|p| p.root().to_owned())
198 .collect())
199 }
200}
201
202#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
204pub enum Verbosity {
205 #[default]
206 Verbose,
207 Normal,
208 Quiet,
209}
210
211impl From<Verbosity> for cargo::core::Verbosity {
212 fn from(value: Verbosity) -> Self {
213 match value {
214 Verbosity::Verbose => Self::Verbose,
215 Verbosity::Normal => Self::Normal,
216 Verbosity::Quiet => Self::Quiet,
217 }
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub struct Package {
230 package_id: PackageId,
231}
232
233impl Package {
234 pub fn new<S: AsRef<str>>(name: S, version: Version, source: &PackageSource) -> Result<Self, String> {
237 Ok(Package {
238 package_id: PackageId::new(
239 name.as_ref(),
240 version,
241 source.to_source_id().map_err(|e| e.to_string())?,
242 )
243 .map_err(|e| e.to_string())?,
244 })
245 }
246
247 pub fn from_str_ver<S: AsRef<str>, V: AsRef<str>>(
249 name: S,
250 version: V,
251 source: &PackageSource,
252 ) -> Result<Self, String> {
253 Ok(Package {
254 package_id: PackageId::new(
255 name.as_ref(),
256 Version::from_str(version.as_ref()).map_err(|e| e.to_string())?,
257 source.to_source_id().map_err(|e| e.to_string())?,
258 )
259 .map_err(|e| e.to_string())?,
260 })
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
266pub enum GitReference {
267 DefaultBranch,
268 Branch(String),
269 Revision(String),
270 Tag(String),
271}
272
273impl From<GitReference> for cargo::core::GitReference {
274 fn from(value: GitReference) -> Self {
275 match value {
276 GitReference::DefaultBranch => Self::DefaultBranch,
277 GitReference::Branch(branch) => Self::Branch(branch),
278 GitReference::Revision(rev) => Self::Rev(rev),
279 GitReference::Tag(tag) => Self::Tag(tag),
280 }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
288pub enum PackageSource {
289 Path(PathBuf),
294 Git { url: Url, git_ref: GitReference },
299 RemoteRegistry(Url),
304 LocalRegistry(PathBuf),
309 CratesIo,
317}
318
319impl PackageSource {
320 pub fn path<P: Into<PathBuf>>(path: P) -> Result<Self, String> {
322 let mut p = path.into();
323 if !p.is_absolute() {
324 p = p.canonicalize().map_err(|e| e.to_string())?;
325 }
326 Ok(Self::Path(p))
327 }
328
329 pub fn git<U: AsRef<str>>(url: U, git_ref: Option<GitReference>) -> Result<Self, <Url as FromStr>::Err> {
331 Ok(Self::Git {
332 url: Url::from_str(url.as_ref())?,
333 git_ref: git_ref.unwrap_or(GitReference::DefaultBranch),
334 })
335 }
336
337 pub fn remote<U: TryInto<Url>>(url: U) -> Result<Self, U::Error> {
339 Ok(Self::RemoteRegistry(url.try_into()?))
340 }
341
342 pub fn local<P: Into<PathBuf>>(path: P) -> Self {
344 Self::LocalRegistry(path.into())
345 }
346
347 pub fn crates_io() -> Self {
349 Self::CratesIo
350 }
351
352 fn to_source_id(&self) -> cargo::CargoResult<SourceId> {
353 match self {
354 PackageSource::Path(path) => SourceId::for_path(path),
355 PackageSource::Git { url, git_ref } => SourceId::for_git(url, git_ref.clone().into()),
356 PackageSource::RemoteRegistry(url) => SourceId::for_registry(url),
357 PackageSource::LocalRegistry(path) => SourceId::for_local_registry(path),
358 PackageSource::CratesIo => SourceId::for_registry(&CRATES_IO_INDEX.into_url().unwrap()),
359 }
360 }
361}