]>
Commit | Line | Data |
---|---|---|
97a13504 | 1 | use std::collections::HashSet; |
213afc02 | 2 | use std::fmt::{self, Debug, Formatter}; |
a6dad622 | 3 | use std::path::{Path, PathBuf}; |
82093ad9 | 4 | use std::task::Poll; |
a6dad622 | 5 | |
1dae5acb | 6 | use crate::core::source::MaybePackage; |
a645c4fe | 7 | use crate::core::{Dependency, Package, PackageId, QueryKind, Source, SourceId, Summary}; |
1dae5acb | 8 | use crate::ops; |
ebca5190 WL |
9 | use crate::util::{internal, CargoResult, Config}; |
10 | use anyhow::Context as _; | |
1dae5acb | 11 | use cargo_util::paths; |
7d49029a | 12 | use filetime::FileTime; |
c072ba42 | 13 | use ignore::gitignore::GitignoreBuilder; |
9ed82b57 | 14 | use log::{trace, warn}; |
3591a5bd | 15 | use walkdir::WalkDir; |
e05b4dc8 | 16 | |
2fe0bf83 | 17 | pub struct PathSource<'cfg> { |
b02023ee | 18 | source_id: SourceId, |
a6dad622 | 19 | path: PathBuf, |
21b7418a | 20 | updated: bool, |
5d0cb3f2 | 21 | packages: Vec<Package>, |
2fe0bf83 | 22 | config: &'cfg Config, |
e56965fb | 23 | recursive: bool, |
98322afd | 24 | } |
62bff631 | 25 | |
2fe0bf83 | 26 | impl<'cfg> PathSource<'cfg> { |
f7c91ba6 | 27 | /// Invoked with an absolute path to a directory that contains a `Cargo.toml`. |
e56965fb AC |
28 | /// |
29 | /// This source will only return the package at precisely the `path` | |
30 | /// specified, and it will be an error if there's not a package at `path`. | |
e5a11190 | 31 | pub fn new(path: &Path, source_id: SourceId, config: &'cfg Config) -> PathSource<'cfg> { |
9224a5ae | 32 | PathSource { |
e5a11190 | 33 | source_id, |
a6dad622 | 34 | path: path.to_path_buf(), |
21b7418a | 35 | updated: false, |
5d0cb3f2 | 36 | packages: Vec::new(), |
0247dc42 | 37 | config, |
e56965fb AC |
38 | recursive: false, |
39 | } | |
40 | } | |
41 | ||
42 | /// Creates a new source which is walked recursively to discover packages. | |
43 | /// | |
44 | /// This is similar to the `new` method except that instead of requiring a | |
45 | /// valid package to be present at `root` the folder is walked entirely to | |
46 | /// crawl for packages. | |
47 | /// | |
48 | /// Note that this should be used with care and likely shouldn't be chosen | |
49 | /// by default! | |
e5a11190 | 50 | pub fn new_recursive(root: &Path, id: SourceId, config: &'cfg Config) -> PathSource<'cfg> { |
e56965fb AC |
51 | PathSource { |
52 | recursive: true, | |
1e682848 | 53 | ..PathSource::new(root, id, config) |
9224a5ae | 54 | } |
62bff631 | 55 | } |
c57fef45 | 56 | |
51d23560 AC |
57 | pub fn preload_with(&mut self, pkg: Package) { |
58 | assert!(!self.updated); | |
59 | assert!(!self.recursive); | |
60 | assert!(self.packages.is_empty()); | |
61 | self.updated = true; | |
62 | self.packages.push(pkg); | |
63 | } | |
64 | ||
339146de | 65 | pub fn root_package(&mut self) -> CargoResult<Package> { |
7a2facba | 66 | trace!("root_package; source={:?}", self); |
9224a5ae | 67 | |
82655b46 | 68 | self.update()?; |
21b7418a | 69 | |
a6dad622 | 70 | match self.packages.iter().find(|p| p.root() == &*self.path) { |
9224a5ae | 71 | Some(pkg) => Ok(pkg.clone()), |
0d44a826 EH |
72 | None => Err(internal(format!( |
73 | "no package found in source {:?}", | |
74 | self.path | |
75 | ))), | |
9224a5ae YKCL |
76 | } |
77 | } | |
8d5acdfc | 78 | |
bc60f64b | 79 | pub fn read_packages(&self) -> CargoResult<Vec<Package>> { |
8d5acdfc AC |
80 | if self.updated { |
81 | Ok(self.packages.clone()) | |
e56965fb | 82 | } else if self.recursive { |
e5a11190 | 83 | ops::read_packages(&self.path, self.source_id, self.config) |
e56965fb | 84 | } else { |
3bac6e06 | 85 | let path = self.path.join("Cargo.toml"); |
e5a11190 | 86 | let (pkg, _) = ops::read_package(&path, self.source_id, self.config)?; |
3bac6e06 | 87 | Ok(vec![pkg]) |
8d5acdfc AC |
88 | } |
89 | } | |
8a19eb74 AC |
90 | |
91 | /// List all files relevant to building this package inside this source. | |
92 | /// | |
bacb6be3 | 93 | /// This function will use the appropriate methods to determine the |
8a19eb74 AC |
94 | /// set of files underneath this source's directory which are relevant for |
95 | /// building `pkg`. | |
96 | /// | |
97 | /// The basic assumption of this method is that all files in the directory | |
98 | /// are relevant for building this package, but it also contains logic to | |
99 | /// use other methods like .gitignore to filter the list of files. | |
a6dad622 | 100 | pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> { |
ebca5190 | 101 | self._list_files(pkg).with_context(|| { |
9ed56cad EH |
102 | format!( |
103 | "failed to determine list of files in {}", | |
104 | pkg.root().display() | |
105 | ) | |
106 | }) | |
107 | } | |
108 | ||
109 | fn _list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> { | |
a6dad622 | 110 | let root = pkg.root(); |
c072ba42 | 111 | let no_include_option = pkg.manifest().include().is_empty(); |
31fcba37 WL |
112 | let git_repo = if no_include_option { |
113 | self.discover_git_repo(root)? | |
114 | } else { | |
115 | None | |
116 | }; | |
c072ba42 | 117 | |
c072ba42 | 118 | let mut exclude_builder = GitignoreBuilder::new(root); |
31fcba37 WL |
119 | if no_include_option && git_repo.is_none() { |
120 | // no include option and not git repo discovered (see rust-lang/cargo#7183). | |
121 | exclude_builder.add_line(None, ".*")?; | |
122 | } | |
c072ba42 BE |
123 | for rule in pkg.manifest().exclude() { |
124 | exclude_builder.add_line(None, rule)?; | |
125 | } | |
126 | let ignore_exclude = exclude_builder.build()?; | |
127 | ||
128 | let mut include_builder = GitignoreBuilder::new(root); | |
129 | for rule in pkg.manifest().include() { | |
130 | include_builder.add_line(None, rule)?; | |
131 | } | |
132 | let ignore_include = include_builder.build()?; | |
133 | ||
b49fe506 | 134 | let ignore_should_package = |relative_path: &Path, is_dir: bool| { |
f7c91ba6 | 135 | // "Include" and "exclude" options are mutually exclusive. |
c072ba42 | 136 | if no_include_option { |
b49fe506 WL |
137 | !ignore_exclude |
138 | .matched_path_or_any_parents(relative_path, is_dir) | |
139 | .is_ignore() | |
c072ba42 | 140 | } else { |
51e0c71c EH |
141 | if is_dir { |
142 | // Generally, include directives don't list every | |
143 | // directory (nor should they!). Just skip all directory | |
144 | // checks, and only check files. | |
b49fe506 | 145 | return true; |
51e0c71c | 146 | } |
b49fe506 | 147 | ignore_include |
1e682848 | 148 | .matched_path_or_any_parents(relative_path, /* is_dir */ false) |
b49fe506 | 149 | .is_ignore() |
c072ba42 BE |
150 | } |
151 | }; | |
152 | ||
09ff1944 | 153 | let filter = |path: &Path, is_dir: bool| { |
b49fe506 WL |
154 | let relative_path = match path.strip_prefix(root) { |
155 | Ok(p) => p, | |
156 | Err(_) => return false, | |
157 | }; | |
49e37f80 EH |
158 | |
159 | let rel = relative_path.as_os_str(); | |
160 | if rel == "Cargo.lock" { | |
b49fe506 | 161 | return pkg.include_lockfile(); |
49e37f80 | 162 | } else if rel == "Cargo.toml" { |
b49fe506 | 163 | return true; |
49e37f80 EH |
164 | } |
165 | ||
51e0c71c | 166 | ignore_should_package(relative_path, is_dir) |
8dc7e8a4 | 167 | }; |
0e8f8d50 | 168 | |
f7c91ba6 | 169 | // Attempt Git-prepopulate only if no `include` (see rust-lang/cargo#4135). |
c072ba42 | 170 | if no_include_option { |
31fcba37 | 171 | if let Some(repo) = git_repo { |
09ff1944 | 172 | return self.list_files_git(pkg, &repo, &filter); |
6eaa964d | 173 | } |
1c588524 | 174 | } |
09ff1944 | 175 | self.list_files_walk(pkg, &filter) |
1c588524 FKI |
176 | } |
177 | ||
31fcba37 WL |
178 | /// Returns `Some(git2::Repository)` if found sibling `Cargo.toml` and `.git` |
179 | /// directory; otherwise, caller should fall back on full file list. | |
180 | fn discover_git_repo(&self, root: &Path) -> CargoResult<Option<git2::Repository>> { | |
1232ad3c EH |
181 | let repo = match git2::Repository::discover(root) { |
182 | Ok(repo) => repo, | |
3a5af295 EH |
183 | Err(e) => { |
184 | log::debug!( | |
185 | "could not discover git repo at or above {}: {}", | |
186 | root.display(), | |
187 | e | |
188 | ); | |
189 | return Ok(None); | |
9185445a | 190 | } |
1232ad3c | 191 | }; |
3a5af295 EH |
192 | let index = repo |
193 | .index() | |
ebca5190 | 194 | .with_context(|| format!("failed to open git index at {}", repo.path().display()))?; |
3a5af295 EH |
195 | let repo_root = repo.workdir().ok_or_else(|| { |
196 | anyhow::format_err!( | |
197 | "did not expect repo at {} to be bare", | |
198 | repo.path().display() | |
199 | ) | |
200 | })?; | |
5bd74c41 EH |
201 | let repo_relative_path = match paths::strip_prefix_canonical(root, repo_root) { |
202 | Ok(p) => p, | |
203 | Err(e) => { | |
204 | log::warn!( | |
205 | "cannot determine if path `{:?}` is in git repo `{:?}`: {:?}", | |
206 | root, | |
207 | repo_root, | |
208 | e | |
209 | ); | |
210 | return Ok(None); | |
211 | } | |
212 | }; | |
25715e4f EH |
213 | let manifest_path = repo_relative_path.join("Cargo.toml"); |
214 | if index.get_path(&manifest_path, 0).is_some() { | |
31fcba37 | 215 | return Ok(Some(repo)); |
5935ec1d | 216 | } |
1232ad3c | 217 | // Package Cargo.toml is not in git, don't use git to guide our selection. |
3a5af295 | 218 | Ok(None) |
0e8f8d50 AC |
219 | } |
220 | ||
1e682848 AC |
221 | fn list_files_git( |
222 | &self, | |
223 | pkg: &Package, | |
385b54b3 | 224 | repo: &git2::Repository, |
09ff1944 | 225 | filter: &dyn Fn(&Path, bool) -> bool, |
1e682848 | 226 | ) -> CargoResult<Vec<PathBuf>> { |
7a2facba | 227 | warn!("list_files_git {}", pkg.package_id()); |
82655b46 | 228 | let index = repo.index()?; |
e5a11190 E |
229 | let root = repo |
230 | .workdir() | |
0d44a826 | 231 | .ok_or_else(|| anyhow::format_err!("can't list files on a bare repository"))?; |
a6dad622 | 232 | let pkg_path = pkg.root(); |
8b07052b | 233 | |
9185445a | 234 | let mut ret = Vec::<PathBuf>::new(); |
a8e9ce22 | 235 | |
f7c91ba6 | 236 | // We use information from the Git repository to guide us in traversing |
a8e9ce22 | 237 | // its tree. The primary purpose of this is to take advantage of the |
f7c91ba6 | 238 | // `.gitignore` and auto-ignore files that don't matter. |
a8e9ce22 | 239 | // |
bacb6be3 | 240 | // Here we're also careful to look at both tracked and untracked files as |
a8e9ce22 AC |
241 | // the untracked files are often part of a build and may become relevant |
242 | // as part of a future commit. | |
046a6c59 | 243 | let index_files = index.iter().map(|entry| { |
50a24ff2 TW |
244 | use libgit2_sys::{GIT_FILEMODE_COMMIT, GIT_FILEMODE_LINK}; |
245 | // ``is_dir`` is an optimization to avoid calling | |
246 | // ``fs::metadata`` on every file. | |
247 | let is_dir = if entry.mode == GIT_FILEMODE_LINK as u32 { | |
248 | // Let the code below figure out if this symbolic link points | |
249 | // to a directory or not. | |
250 | None | |
251 | } else { | |
252 | Some(entry.mode == GIT_FILEMODE_COMMIT as u32) | |
253 | }; | |
254 | (join(root, &entry.path), is_dir) | |
046a6c59 | 255 | }); |
a8e9ce22 AC |
256 | let mut opts = git2::StatusOptions::new(); |
257 | opts.include_untracked(true); | |
092c88c4 | 258 | if let Ok(suffix) = pkg_path.strip_prefix(root) { |
b6ad6fb4 AC |
259 | opts.pathspec(suffix); |
260 | } | |
82655b46 | 261 | let statuses = repo.statuses(Some(&mut opts))?; |
97a13504 EH |
262 | let mut skip_paths = HashSet::new(); |
263 | let untracked: Vec<_> = statuses | |
264 | .iter() | |
265 | .filter_map(|entry| { | |
266 | match entry.status() { | |
267 | // Don't include Cargo.lock if it is untracked. Packaging will | |
268 | // generate a new one as needed. | |
269 | git2::Status::WT_NEW if entry.path() != Some("Cargo.lock") => { | |
270 | Some(Ok((join(root, entry.path_bytes()), None))) | |
271 | } | |
272 | git2::Status::WT_DELETED => { | |
273 | let path = match join(root, entry.path_bytes()) { | |
274 | Ok(p) => p, | |
275 | Err(e) => return Some(Err(e)), | |
276 | }; | |
277 | skip_paths.insert(path); | |
278 | None | |
279 | } | |
280 | _ => None, | |
281 | } | |
282 | }) | |
283 | .collect::<CargoResult<_>>()?; | |
a8e9ce22 | 284 | |
9185445a AC |
285 | let mut subpackages_found = Vec::new(); |
286 | ||
c5611a32 | 287 | for (file_path, is_dir) in index_files.chain(untracked) { |
82655b46 | 288 | let file_path = file_path?; |
97a13504 EH |
289 | if skip_paths.contains(&file_path) { |
290 | continue; | |
291 | } | |
8b07052b | 292 | |
9185445a | 293 | // Filter out files blatantly outside this package. This is helped a |
7d202307 | 294 | // bit above via the `pathspec` function call, but we need to filter |
9185445a AC |
295 | // the entries in the index as well. |
296 | if !file_path.starts_with(pkg_path) { | |
1e682848 | 297 | continue; |
a8e9ce22 | 298 | } |
8dc7e8a4 | 299 | |
9185445a | 300 | match file_path.file_name().and_then(|s| s.to_str()) { |
7d202307 EH |
301 | // The `target` directory is never included. |
302 | Some("target") => continue, | |
9185445a AC |
303 | |
304 | // Keep track of all sub-packages found and also strip out all | |
305 | // matches we've found so far. Note, though, that if we find | |
f7c91ba6 | 306 | // our own `Cargo.toml`, we keep going. |
9185445a AC |
307 | Some("Cargo.toml") => { |
308 | let path = file_path.parent().unwrap(); | |
309 | if path != pkg_path { | |
310 | warn!("subpackage found: {}", path.display()); | |
311 | ret.retain(|p| !p.starts_with(path)); | |
312 | subpackages_found.push(path.to_path_buf()); | |
1e682848 | 313 | continue; |
9185445a | 314 | } |
8b07052b | 315 | } |
9185445a AC |
316 | |
317 | _ => {} | |
8a19eb74 AC |
318 | } |
319 | ||
9185445a AC |
320 | // If this file is part of any other sub-package we've found so far, |
321 | // skip it. | |
322 | if subpackages_found.iter().any(|p| file_path.starts_with(p)) { | |
1e682848 | 323 | continue; |
9185445a AC |
324 | } |
325 | ||
51e0c71c EH |
326 | // `is_dir` is None for symlinks. The `unwrap` checks if the |
327 | // symlink points to a directory. | |
328 | let is_dir = is_dir.unwrap_or_else(|| file_path.is_dir()); | |
329 | if is_dir { | |
5935ec1d | 330 | warn!(" found submodule {}", file_path.display()); |
092c88c4 | 331 | let rel = file_path.strip_prefix(root)?; |
54c42142 | 332 | let rel = rel.to_str().ok_or_else(|| { |
3a18c89a | 333 | anyhow::format_err!("invalid utf-8 filename: {}", rel.display()) |
54c42142 | 334 | })?; |
1e0b04a0 AC |
335 | // Git submodules are currently only named through `/` path |
336 | // separators, explicitly not `\` which windows uses. Who knew? | |
337 | let rel = rel.replace(r"\", "/"); | |
a8e9ce22 AC |
338 | match repo.find_submodule(&rel).and_then(|s| s.open()) { |
339 | Ok(repo) => { | |
385b54b3 | 340 | let files = self.list_files_git(pkg, &repo, filter)?; |
a8e9ce22 AC |
341 | ret.extend(files.into_iter()); |
342 | } | |
343 | Err(..) => { | |
0113ec46 | 344 | self.walk(&file_path, &mut ret, false, filter)?; |
a8e9ce22 AC |
345 | } |
346 | } | |
b49fe506 | 347 | } else if filter(&file_path, is_dir) { |
51e0c71c | 348 | assert!(!is_dir); |
5935ec1d AC |
349 | // We found a file! |
350 | warn!(" found {}", file_path.display()); | |
351 | ret.push(file_path); | |
352 | } | |
8dc7e8a4 | 353 | } |
a6dad622 AC |
354 | return Ok(ret); |
355 | ||
356 | #[cfg(unix)] | |
357 | fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> { | |
a6dad622 | 358 | use std::ffi::OsStr; |
e5a11190 | 359 | use std::os::unix::prelude::*; |
a6dad622 AC |
360 | Ok(path.join(<OsStr as OsStrExt>::from_bytes(data))) |
361 | } | |
362 | #[cfg(windows)] | |
363 | fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> { | |
364 | use std::str; | |
365 | match str::from_utf8(data) { | |
366 | Ok(s) => Ok(path.join(s)), | |
0d44a826 EH |
367 | Err(e) => Err(anyhow::format_err!( |
368 | "cannot process path in git with a non utf8 filename: {}\n{:?}", | |
369 | e, | |
370 | data | |
1e682848 | 371 | )), |
a6dad622 AC |
372 | } |
373 | } | |
0e8f8d50 AC |
374 | } |
375 | ||
1e682848 AC |
376 | fn list_files_walk( |
377 | &self, | |
378 | pkg: &Package, | |
09ff1944 | 379 | filter: &dyn Fn(&Path, bool) -> bool, |
1e682848 | 380 | ) -> CargoResult<Vec<PathBuf>> { |
0e8f8d50 | 381 | let mut ret = Vec::new(); |
0113ec46 | 382 | self.walk(pkg.root(), &mut ret, true, filter)?; |
3a852a0f | 383 | Ok(ret) |
a8e9ce22 | 384 | } |
8a19eb74 | 385 | |
1e682848 | 386 | fn walk( |
0113ec46 | 387 | &self, |
1e682848 AC |
388 | path: &Path, |
389 | ret: &mut Vec<PathBuf>, | |
390 | is_root: bool, | |
09ff1944 | 391 | filter: &dyn Fn(&Path, bool) -> bool, |
1e682848 | 392 | ) -> CargoResult<()> { |
3591a5bd WL |
393 | let walkdir = WalkDir::new(path) |
394 | .follow_links(true) | |
395 | .into_iter() | |
396 | .filter_entry(|entry| { | |
397 | let path = entry.path(); | |
398 | let at_root = is_root && entry.depth() == 0; | |
399 | let is_dir = entry.file_type().is_dir(); | |
400 | ||
b49fe506 | 401 | if !at_root && !filter(path, is_dir) { |
3591a5bd WL |
402 | return false; |
403 | } | |
c072ba42 | 404 | |
3591a5bd WL |
405 | if !is_dir { |
406 | return true; | |
407 | } | |
408 | ||
409 | // Don't recurse into any sub-packages that we have. | |
410 | if !at_root && path.join("Cargo.toml").exists() { | |
411 | return false; | |
412 | } | |
413 | ||
414 | // Skip root Cargo artifacts. | |
415 | if is_root | |
416 | && entry.depth() == 1 | |
417 | && path.file_name().and_then(|s| s.to_str()) == Some("target") | |
418 | { | |
419 | return false; | |
420 | } | |
421 | ||
422 | true | |
423 | }); | |
424 | for entry in walkdir { | |
0113ec46 WL |
425 | match entry { |
426 | Ok(entry) => { | |
427 | if !entry.file_type().is_dir() { | |
428 | ret.push(entry.into_path()); | |
429 | } | |
430 | } | |
431 | Err(err) if err.loop_ancestor().is_some() => { | |
432 | self.config.shell().warn(err)?; | |
433 | } | |
434 | Err(err) => match err.path() { | |
c0110c63 WL |
435 | // If an error occurs with a path, filter it again. |
436 | // If it is excluded, Just ignore it in this case. | |
437 | // See issue rust-lang/cargo#10917 | |
438 | Some(path) if !filter(path, path.is_dir()) => {} | |
439 | // Otherwise, simply recover from it. | |
0113ec46 WL |
440 | // Don't worry about error skipping here, the callers would |
441 | // still hit the IO error if they do access it thereafter. | |
442 | Some(path) => ret.push(path.to_path_buf()), | |
443 | None => return Err(err.into()), | |
444 | }, | |
a8e9ce22 | 445 | } |
a8e9ce22 | 446 | } |
3591a5bd | 447 | |
3a852a0f | 448 | Ok(()) |
8a19eb74 | 449 | } |
9c1124fb GF |
450 | |
451 | pub fn last_modified_file(&self, pkg: &Package) -> CargoResult<(FileTime, PathBuf)> { | |
452 | if !self.updated { | |
0d44a826 EH |
453 | return Err(internal(format!( |
454 | "BUG: source `{:?}` was not updated", | |
455 | self.path | |
456 | ))); | |
9c1124fb GF |
457 | } |
458 | ||
459 | let mut max = FileTime::zero(); | |
460 | let mut max_path = PathBuf::new(); | |
ebca5190 | 461 | for file in self.list_files(pkg).with_context(|| { |
9ed56cad EH |
462 | format!( |
463 | "failed to determine the most recently modified file in {}", | |
464 | pkg.root().display() | |
465 | ) | |
466 | })? { | |
f7c91ba6 | 467 | // An `fs::stat` error here is either because path is a |
9c1124fb | 468 | // broken symlink, a permissions error, or a race |
f7c91ba6 AR |
469 | // condition where this path was `rm`-ed -- either way, |
470 | // we can ignore the error and treat the path's `mtime` | |
471 | // as `0`. | |
385b54b3 | 472 | let mtime = paths::mtime(&file).unwrap_or_else(|_| FileTime::zero()); |
9c1124fb GF |
473 | if mtime > max { |
474 | max = mtime; | |
475 | max_path = file; | |
476 | } | |
477 | } | |
478 | trace!("last modified file {}: {}", self.path.display(), max); | |
479 | Ok((max, max_path)) | |
480 | } | |
8921abd7 DW |
481 | |
482 | pub fn path(&self) -> &Path { | |
483 | &self.path | |
484 | } | |
f12f0256 AS |
485 | |
486 | pub fn update(&mut self) -> CargoResult<()> { | |
487 | if !self.updated { | |
488 | let packages = self.read_packages()?; | |
489 | self.packages.extend(packages.into_iter()); | |
490 | self.updated = true; | |
491 | } | |
492 | ||
493 | Ok(()) | |
494 | } | |
62bff631 C |
495 | } |
496 | ||
2fe0bf83 | 497 | impl<'cfg> Debug for PathSource<'cfg> { |
b8b7faee | 498 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
cfeabbc2 | 499 | write!(f, "the paths source") |
50f110a4 YK |
500 | } |
501 | } | |
502 | ||
f947326a | 503 | impl<'cfg> Source for PathSource<'cfg> { |
233ecba2 | 504 | fn query( |
82093ad9 | 505 | &mut self, |
233ecba2 | 506 | dep: &Dependency, |
a645c4fe | 507 | kind: QueryKind, |
82093ad9 AS |
508 | f: &mut dyn FnMut(Summary), |
509 | ) -> Poll<CargoResult<()>> { | |
412b6339 | 510 | self.update()?; |
84cc3d8b | 511 | for s in self.packages.iter().map(|p| p.summary()) { |
a645c4fe EP |
512 | let matched = match kind { |
513 | QueryKind::Exact => dep.matches(s), | |
514 | QueryKind::Fuzzy => true, | |
515 | }; | |
516 | if matched { | |
233ecba2 EP |
517 | f(s.clone()) |
518 | } | |
84cc3d8b | 519 | } |
82093ad9 | 520 | Poll::Ready(Ok(())) |
84cc3d8b E |
521 | } |
522 | ||
5b08b8fe AC |
523 | fn supports_checksums(&self) -> bool { |
524 | false | |
525 | } | |
526 | ||
527 | fn requires_precise(&self) -> bool { | |
528 | false | |
529 | } | |
b02023ee | 530 | |
e5a11190 E |
531 | fn source_id(&self) -> SourceId { |
532 | self.source_id | |
53bd095f DW |
533 | } |
534 | ||
dae87a26 | 535 | fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> { |
9cf70517 | 536 | trace!("getting packages; id={}", id); |
412b6339 | 537 | self.update()?; |
9cf70517 | 538 | let pkg = self.packages.iter().find(|pkg| pkg.package_id() == id); |
1e682848 | 539 | pkg.cloned() |
c94804bd | 540 | .map(MaybePackage::Ready) |
1e682848 | 541 | .ok_or_else(|| internal(format!("failed to find {} in path source", id))) |
62bff631 | 542 | } |
e05b4dc8 | 543 | |
dae87a26 | 544 | fn finish_download(&mut self, _id: PackageId, _data: Vec<u8>) -> CargoResult<Package> { |
c94804bd AC |
545 | panic!("no download should have started") |
546 | } | |
547 | ||
8d5acdfc | 548 | fn fingerprint(&self, pkg: &Package) -> CargoResult<String> { |
9c1124fb | 549 | let (max, max_path) = self.last_modified_file(pkg)?; |
64a46826 AC |
550 | // Note that we try to strip the prefix of this package to get a |
551 | // relative path to ensure that the fingerprint remains consistent | |
552 | // across entire project directory renames. | |
553 | let max_path = max_path.strip_prefix(&self.path).unwrap_or(&max_path); | |
c0595fbc | 554 | Ok(format!("{} ({})", max, max_path.display())) |
e05b4dc8 | 555 | } |
20cfb41e AC |
556 | |
557 | fn describe(&self) -> String { | |
558 | match self.source_id.url().to_file_path() { | |
559 | Ok(path) => path.display().to_string(), | |
560 | Err(_) => self.source_id.to_string(), | |
561 | } | |
562 | } | |
4a2f810d AC |
563 | |
564 | fn add_to_yanked_whitelist(&mut self, _pkgs: &[PackageId]) {} | |
5f616eb1 | 565 | |
72ed97bf AS |
566 | fn is_yanked(&mut self, _pkg: PackageId) -> Poll<CargoResult<bool>> { |
567 | Poll::Ready(Ok(false)) | |
5f616eb1 | 568 | } |
82093ad9 AS |
569 | |
570 | fn block_until_ready(&mut self) -> CargoResult<()> { | |
f12f0256 | 571 | self.update() |
82093ad9 | 572 | } |
f12f0256 | 573 | |
6aad5eca AS |
574 | fn invalidate_cache(&mut self) { |
575 | // Path source has no local cache. | |
576 | } | |
62bff631 | 577 | } |