]>
Commit | Line | Data |
---|---|---|
213afc02 | 1 | use std::fmt::{self, Debug, Formatter}; |
a6dad622 | 2 | use std::fs; |
a6dad622 AC |
3 | use std::path::{Path, PathBuf}; |
4 | ||
7d49029a | 5 | use filetime::FileTime; |
8dc7e8a4 | 6 | use git2; |
046a6c59 | 7 | use glob::Pattern; |
c072ba42 BE |
8 | use ignore::Match; |
9 | use ignore::gitignore::GitignoreBuilder; | |
e05b4dc8 | 10 | |
29f738fe | 11 | use core::{Dependency, Package, PackageId, Source, SourceId, Summary}; |
12f49111 | 12 | use ops; |
1e682848 | 13 | use util::{self, internal, CargoResult}; |
8c5c9e39 | 14 | use util::paths; |
7d49029a | 15 | use util::Config; |
62bff631 | 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> { |
64ff29ff | 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`. | |
1e682848 | 31 | pub fn new(path: &Path, id: &SourceId, config: &'cfg Config) -> PathSource<'cfg> { |
9224a5ae | 32 | PathSource { |
b02023ee | 33 | source_id: id.clone(), |
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! | |
1e682848 | 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()), |
1e682848 | 72 | None => Err(internal("no package found in source")), |
9224a5ae YKCL |
73 | } |
74 | } | |
8d5acdfc | 75 | |
bc60f64b | 76 | pub fn read_packages(&self) -> CargoResult<Vec<Package>> { |
8d5acdfc AC |
77 | if self.updated { |
78 | Ok(self.packages.clone()) | |
e56965fb | 79 | } else if self.recursive { |
b02023ee | 80 | ops::read_packages(&self.path, &self.source_id, self.config) |
e56965fb | 81 | } else { |
3bac6e06 | 82 | let path = self.path.join("Cargo.toml"); |
b02023ee | 83 | let (pkg, _) = ops::read_package(&path, &self.source_id, self.config)?; |
3bac6e06 | 84 | Ok(vec![pkg]) |
8d5acdfc AC |
85 | } |
86 | } | |
8a19eb74 AC |
87 | |
88 | /// List all files relevant to building this package inside this source. | |
89 | /// | |
bacb6be3 | 90 | /// This function will use the appropriate methods to determine the |
8a19eb74 AC |
91 | /// set of files underneath this source's directory which are relevant for |
92 | /// building `pkg`. | |
93 | /// | |
94 | /// The basic assumption of this method is that all files in the directory | |
95 | /// are relevant for building this package, but it also contains logic to | |
96 | /// use other methods like .gitignore to filter the list of files. | |
c072ba42 BE |
97 | /// |
98 | /// ## Pattern matching strategy | |
99 | /// | |
100 | /// Migrating from a glob-like pattern matching (using `glob` crate) to a | |
101 | /// gitignore-like pattern matching (using `ignore` crate). The migration | |
102 | /// stages are: | |
103 | /// | |
104 | /// 1) Only warn users about the future change iff their matching rules are | |
105 | /// affected. (CURRENT STAGE) | |
106 | /// | |
670a3df4 | 107 | /// 2) Switch to the new strategy and update documents. Still keep warning |
c072ba42 BE |
108 | /// affected users. |
109 | /// | |
670a3df4 | 110 | /// 3) Drop the old strategy and no more warnings. |
c072ba42 BE |
111 | /// |
112 | /// See <https://github.com/rust-lang/cargo/issues/4268> for more info. | |
a6dad622 AC |
113 | pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> { |
114 | let root = pkg.root(); | |
c072ba42 BE |
115 | let no_include_option = pkg.manifest().include().is_empty(); |
116 | ||
117 | // glob-like matching rules | |
8b07052b | 118 | |
c072ba42 | 119 | let glob_parse = |p: &String| { |
5641cbde BE |
120 | let pattern: &str = if p.starts_with('/') { |
121 | &p[1..p.len()] | |
122 | } else { | |
23591fe5 | 123 | p |
5641cbde | 124 | }; |
1e682848 AC |
125 | Pattern::new(pattern) |
126 | .map_err(|e| format_err!("could not parse glob pattern `{}`: {}", p, e)) | |
d73a110d | 127 | }; |
ed61ba20 | 128 | |
c072ba42 BE |
129 | let glob_exclude = pkg.manifest() |
130 | .exclude() | |
131 | .iter() | |
132 | .map(|p| glob_parse(p)) | |
133 | .collect::<Result<Vec<_>, _>>()?; | |
134 | ||
135 | let glob_include = pkg.manifest() | |
136 | .include() | |
137 | .iter() | |
138 | .map(|p| glob_parse(p)) | |
139 | .collect::<Result<Vec<_>, _>>()?; | |
140 | ||
141 | let glob_should_package = |relative_path: &Path| -> bool { | |
5641cbde | 142 | fn glob_match(patterns: &Vec<Pattern>, relative_path: &Path) -> bool { |
1e682848 AC |
143 | patterns |
144 | .iter() | |
145 | .any(|pattern| pattern.matches_path(relative_path)) | |
5641cbde BE |
146 | } |
147 | ||
c072ba42 BE |
148 | // include and exclude options are mutually exclusive. |
149 | if no_include_option { | |
5641cbde | 150 | !glob_match(&glob_exclude, relative_path) |
c072ba42 | 151 | } else { |
5641cbde | 152 | glob_match(&glob_include, relative_path) |
c072ba42 BE |
153 | } |
154 | }; | |
155 | ||
156 | // ignore-like matching rules | |
157 | ||
158 | let mut exclude_builder = GitignoreBuilder::new(root); | |
159 | for rule in pkg.manifest().exclude() { | |
160 | exclude_builder.add_line(None, rule)?; | |
161 | } | |
162 | let ignore_exclude = exclude_builder.build()?; | |
163 | ||
164 | let mut include_builder = GitignoreBuilder::new(root); | |
165 | for rule in pkg.manifest().include() { | |
166 | include_builder.add_line(None, rule)?; | |
167 | } | |
168 | let ignore_include = include_builder.build()?; | |
169 | ||
170 | let ignore_should_package = |relative_path: &Path| -> CargoResult<bool> { | |
171 | // include and exclude options are mutually exclusive. | |
172 | if no_include_option { | |
1e682848 AC |
173 | match ignore_exclude |
174 | .matched_path_or_any_parents(relative_path, /* is_dir */ false) | |
175 | { | |
c072ba42 BE |
176 | Match::None => Ok(true), |
177 | Match::Ignore(_) => Ok(false), | |
37cffbe0 | 178 | Match::Whitelist(pattern) => Err(format_err!( |
c072ba42 BE |
179 | "exclude rules cannot start with `!`: {}", |
180 | pattern.original() | |
37cffbe0 | 181 | )), |
c072ba42 BE |
182 | } |
183 | } else { | |
1e682848 AC |
184 | match ignore_include |
185 | .matched_path_or_any_parents(relative_path, /* is_dir */ false) | |
186 | { | |
c072ba42 BE |
187 | Match::None => Ok(false), |
188 | Match::Ignore(_) => Ok(true), | |
37cffbe0 | 189 | Match::Whitelist(pattern) => Err(format_err!( |
c072ba42 BE |
190 | "include rules cannot start with `!`: {}", |
191 | pattern.original() | |
37cffbe0 | 192 | )), |
c072ba42 BE |
193 | } |
194 | } | |
195 | }; | |
196 | ||
197 | // matching to paths | |
198 | ||
199 | let mut filter = |path: &Path| -> CargoResult<bool> { | |
200 | let relative_path = util::without_prefix(path, root).unwrap(); | |
201 | let glob_should_package = glob_should_package(relative_path); | |
202 | let ignore_should_package = ignore_should_package(relative_path)?; | |
203 | ||
204 | if glob_should_package != ignore_should_package { | |
205 | if glob_should_package { | |
206 | if no_include_option { | |
1e682848 | 207 | self.config.shell().warn(format!( |
24ba7c80 | 208 | "Pattern matching for Cargo's include/exclude fields is changing and \ |
1e682848 AC |
209 | file `{}` WILL be excluded in a future Cargo version.\n\ |
210 | See https://github.com/rust-lang/cargo/issues/4268 for more info", | |
24ba7c80 LL |
211 | relative_path.display() |
212 | ))?; | |
1e682848 AC |
213 | } else { |
214 | self.config.shell().warn(format!( | |
23591fe5 | 215 | "Pattern matching for Cargo's include/exclude fields is changing and \ |
1e682848 AC |
216 | file `{}` WILL NOT be included in a future Cargo version.\n\ |
217 | See https://github.com/rust-lang/cargo/issues/4268 for more info", | |
23591fe5 LL |
218 | relative_path.display() |
219 | ))?; | |
1e682848 AC |
220 | } |
221 | } else if no_include_option { | |
222 | self.config.shell().warn(format!( | |
223 | "Pattern matching for Cargo's include/exclude fields is changing and \ | |
224 | file `{}` WILL NOT be excluded in a future Cargo version.\n\ | |
225 | See https://github.com/rust-lang/cargo/issues/4268 for more info", | |
226 | relative_path.display() | |
227 | ))?; | |
228 | } else { | |
229 | self.config.shell().warn(format!( | |
230 | "Pattern matching for Cargo's include/exclude fields is changing and \ | |
231 | file `{}` WILL be included in a future Cargo version.\n\ | |
232 | See https://github.com/rust-lang/cargo/issues/4268 for more info", | |
233 | relative_path.display() | |
234 | ))?; | |
c072ba42 | 235 | } |
8b07052b | 236 | } |
c072ba42 BE |
237 | |
238 | // Update to ignore_should_package for Stage 2 | |
239 | Ok(glob_should_package) | |
8dc7e8a4 | 240 | }; |
0e8f8d50 | 241 | |
6eaa964d | 242 | // attempt git-prepopulate only if no `include` (rust-lang/cargo#4135) |
c072ba42 | 243 | if no_include_option { |
6eaa964d FKI |
244 | if let Some(result) = self.discover_git_and_list_files(pkg, root, &mut filter) { |
245 | return result; | |
246 | } | |
1c588524 | 247 | } |
1c588524 FKI |
248 | self.list_files_walk(pkg, &mut filter) |
249 | } | |
250 | ||
251 | // Returns Some(_) if found sibling Cargo.toml and .git folder; | |
252 | // otherwise caller should fall back on full file list. | |
1e682848 AC |
253 | fn discover_git_and_list_files( |
254 | &self, | |
255 | pkg: &Package, | |
256 | root: &Path, | |
257 | filter: &mut FnMut(&Path) -> CargoResult<bool>, | |
258 | ) -> Option<CargoResult<Vec<PathBuf>>> { | |
9185445a AC |
259 | // If this package is in a git repository, then we really do want to |
260 | // query the git repository as it takes into account items such as | |
261 | // .gitignore. We're not quite sure where the git repository is, | |
262 | // however, so we do a bit of a probe. | |
5935ec1d | 263 | // |
9185445a AC |
264 | // We walk this package's path upwards and look for a sibling |
265 | // Cargo.toml and .git folder. If we find one then we assume that we're | |
266 | // part of that repository. | |
267 | let mut cur = root; | |
268 | loop { | |
269 | if cur.join("Cargo.toml").is_file() { | |
270 | // If we find a git repository next to this Cargo.toml, we still | |
271 | // check to see if we are indeed part of the index. If not, then | |
272 | // this is likely an unrelated git repo, so keep going. | |
273 | if let Ok(repo) = git2::Repository::open(cur) { | |
1c588524 FKI |
274 | let index = match repo.index() { |
275 | Ok(index) => index, | |
276 | Err(err) => return Some(Err(err.into())), | |
277 | }; | |
1e682848 | 278 | let path = util::without_prefix(root, cur).unwrap().join("Cargo.toml"); |
9185445a | 279 | if index.get_path(&path, 0).is_some() { |
1c588524 | 280 | return Some(self.list_files_git(pkg, repo, filter)); |
9185445a AC |
281 | } |
282 | } | |
283 | } | |
284 | // don't cross submodule boundaries | |
285 | if cur.join(".git").is_dir() { | |
1e682848 | 286 | break; |
9185445a AC |
287 | } |
288 | match cur.parent() { | |
289 | Some(parent) => cur = parent, | |
290 | None => break, | |
291 | } | |
5935ec1d | 292 | } |
23591fe5 | 293 | None |
0e8f8d50 AC |
294 | } |
295 | ||
1e682848 AC |
296 | fn list_files_git( |
297 | &self, | |
298 | pkg: &Package, | |
299 | repo: git2::Repository, | |
300 | filter: &mut FnMut(&Path) -> CargoResult<bool>, | |
301 | ) -> CargoResult<Vec<PathBuf>> { | |
7a2facba | 302 | warn!("list_files_git {}", pkg.package_id()); |
82655b46 | 303 | let index = repo.index()?; |
1e682848 AC |
304 | let root = repo.workdir() |
305 | .ok_or_else(|| internal("Can't list files on a bare repository."))?; | |
a6dad622 | 306 | let pkg_path = pkg.root(); |
8b07052b | 307 | |
9185445a | 308 | let mut ret = Vec::<PathBuf>::new(); |
a8e9ce22 | 309 | |
bacb6be3 | 310 | // We use information from the git repository to guide us in traversing |
a8e9ce22 AC |
311 | // its tree. The primary purpose of this is to take advantage of the |
312 | // .gitignore and auto-ignore files that don't matter. | |
313 | // | |
bacb6be3 | 314 | // Here we're also careful to look at both tracked and untracked files as |
a8e9ce22 AC |
315 | // the untracked files are often part of a build and may become relevant |
316 | // as part of a future commit. | |
046a6c59 | 317 | let index_files = index.iter().map(|entry| { |
e0dff0f2 | 318 | use libgit2_sys::GIT_FILEMODE_COMMIT; |
632780eb | 319 | let is_dir = entry.mode == GIT_FILEMODE_COMMIT as u32; |
c5611a32 | 320 | (join(root, &entry.path), Some(is_dir)) |
046a6c59 | 321 | }); |
a8e9ce22 AC |
322 | let mut opts = git2::StatusOptions::new(); |
323 | opts.include_untracked(true); | |
c5611a32 | 324 | if let Some(suffix) = util::without_prefix(pkg_path, root) { |
b6ad6fb4 AC |
325 | opts.pathspec(suffix); |
326 | } | |
82655b46 | 327 | let statuses = repo.statuses(Some(&mut opts))?; |
1e682848 AC |
328 | let untracked = statuses.iter().filter_map(|entry| match entry.status() { |
329 | git2::Status::WT_NEW => Some((join(root, entry.path_bytes()), None)), | |
330 | _ => None, | |
a8e9ce22 AC |
331 | }); |
332 | ||
9185445a AC |
333 | let mut subpackages_found = Vec::new(); |
334 | ||
c5611a32 | 335 | for (file_path, is_dir) in index_files.chain(untracked) { |
82655b46 | 336 | let file_path = file_path?; |
8b07052b | 337 | |
9185445a AC |
338 | // Filter out files blatantly outside this package. This is helped a |
339 | // bit obove via the `pathspec` function call, but we need to filter | |
340 | // the entries in the index as well. | |
341 | if !file_path.starts_with(pkg_path) { | |
1e682848 | 342 | continue; |
a8e9ce22 | 343 | } |
8dc7e8a4 | 344 | |
9185445a AC |
345 | match file_path.file_name().and_then(|s| s.to_str()) { |
346 | // Filter out Cargo.lock and target always, we don't want to | |
347 | // package a lock file no one will ever read and we also avoid | |
348 | // build artifacts | |
1e682848 | 349 | Some("Cargo.lock") | Some("target") => continue, |
9185445a AC |
350 | |
351 | // Keep track of all sub-packages found and also strip out all | |
352 | // matches we've found so far. Note, though, that if we find | |
353 | // our own `Cargo.toml` we keep going. | |
354 | Some("Cargo.toml") => { | |
355 | let path = file_path.parent().unwrap(); | |
356 | if path != pkg_path { | |
357 | warn!("subpackage found: {}", path.display()); | |
358 | ret.retain(|p| !p.starts_with(path)); | |
359 | subpackages_found.push(path.to_path_buf()); | |
1e682848 | 360 | continue; |
9185445a | 361 | } |
8b07052b | 362 | } |
9185445a AC |
363 | |
364 | _ => {} | |
8a19eb74 AC |
365 | } |
366 | ||
9185445a AC |
367 | // If this file is part of any other sub-package we've found so far, |
368 | // skip it. | |
369 | if subpackages_found.iter().any(|p| file_path.starts_with(p)) { | |
1e682848 | 370 | continue; |
9185445a AC |
371 | } |
372 | ||
373 | if is_dir.unwrap_or_else(|| file_path.is_dir()) { | |
5935ec1d | 374 | warn!(" found submodule {}", file_path.display()); |
c5611a32 | 375 | let rel = util::without_prefix(&file_path, root).unwrap(); |
1e682848 AC |
376 | let rel = rel.to_str() |
377 | .ok_or_else(|| format_err!("invalid utf-8 filename: {}", rel.display()))?; | |
1e0b04a0 AC |
378 | // Git submodules are currently only named through `/` path |
379 | // separators, explicitly not `\` which windows uses. Who knew? | |
380 | let rel = rel.replace(r"\", "/"); | |
a8e9ce22 AC |
381 | match repo.find_submodule(&rel).and_then(|s| s.open()) { |
382 | Ok(repo) => { | |
82655b46 | 383 | let files = self.list_files_git(pkg, repo, filter)?; |
a8e9ce22 AC |
384 | ret.extend(files.into_iter()); |
385 | } | |
386 | Err(..) => { | |
c072ba42 | 387 | PathSource::walk(&file_path, &mut ret, false, filter)?; |
a8e9ce22 AC |
388 | } |
389 | } | |
c072ba42 | 390 | } else if (*filter)(&file_path)? { |
5935ec1d AC |
391 | // We found a file! |
392 | warn!(" found {}", file_path.display()); | |
393 | ret.push(file_path); | |
394 | } | |
8dc7e8a4 | 395 | } |
a6dad622 AC |
396 | return Ok(ret); |
397 | ||
398 | #[cfg(unix)] | |
399 | fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> { | |
400 | use std::os::unix::prelude::*; | |
401 | use std::ffi::OsStr; | |
402 | Ok(path.join(<OsStr as OsStrExt>::from_bytes(data))) | |
403 | } | |
404 | #[cfg(windows)] | |
405 | fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> { | |
406 | use std::str; | |
407 | match str::from_utf8(data) { | |
408 | Ok(s) => Ok(path.join(s)), | |
1e682848 AC |
409 | Err(..) => Err(internal( |
410 | "cannot process path in git with a non \ | |
411 | unicode filename", | |
412 | )), | |
a6dad622 AC |
413 | } |
414 | } | |
0e8f8d50 AC |
415 | } |
416 | ||
1e682848 AC |
417 | fn list_files_walk( |
418 | &self, | |
419 | pkg: &Package, | |
420 | filter: &mut FnMut(&Path) -> CargoResult<bool>, | |
421 | ) -> CargoResult<Vec<PathBuf>> { | |
0e8f8d50 | 422 | let mut ret = Vec::new(); |
82655b46 | 423 | PathSource::walk(pkg.root(), &mut ret, true, filter)?; |
3a852a0f | 424 | Ok(ret) |
a8e9ce22 | 425 | } |
8a19eb74 | 426 | |
1e682848 AC |
427 | fn walk( |
428 | path: &Path, | |
429 | ret: &mut Vec<PathBuf>, | |
430 | is_root: bool, | |
431 | filter: &mut FnMut(&Path) -> CargoResult<bool>, | |
432 | ) -> CargoResult<()> { | |
a8e9ce22 | 433 | if !fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false) { |
c072ba42 | 434 | if (*filter)(path)? { |
a8e9ce22 | 435 | ret.push(path.to_path_buf()); |
8a19eb74 | 436 | } |
1e682848 | 437 | return Ok(()); |
8a19eb74 | 438 | } |
a8e9ce22 AC |
439 | // Don't recurse into any sub-packages that we have |
440 | if !is_root && fs::metadata(&path.join("Cargo.toml")).is_ok() { | |
1e682848 | 441 | return Ok(()); |
a8e9ce22 | 442 | } |
c072ba42 BE |
443 | |
444 | // For package integration tests, we need to sort the paths in a deterministic order to | |
445 | // be able to match stdout warnings in the same order. | |
446 | // | |
57a7e126 | 447 | // TODO: Drop collect and sort after transition period and dropping warning tests. |
c072ba42 BE |
448 | // See <https://github.com/rust-lang/cargo/issues/4268> |
449 | // and <https://github.com/rust-lang/cargo/pull/4270> | |
c74aa949 E |
450 | let mut entries: Vec<PathBuf> = fs::read_dir(path)?.map(|e| e.unwrap().path()).collect(); |
451 | entries.sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str())); | |
452 | for path in entries { | |
c072ba42 | 453 | let name = path.file_name().and_then(|s| s.to_str()); |
11144645 | 454 | // Skip dotfile directories |
4673f0a6 | 455 | if name.map(|s| s.starts_with('.')) == Some(true) { |
1e682848 | 456 | continue; |
676edacf E |
457 | } |
458 | if is_root { | |
11144645 LB |
459 | // Skip cargo artifacts |
460 | match name { | |
461 | Some("target") | Some("Cargo.lock") => continue, | |
462 | _ => {} | |
463 | } | |
a8e9ce22 | 464 | } |
c072ba42 | 465 | PathSource::walk(&path, ret, false, filter)?; |
a8e9ce22 | 466 | } |
3a852a0f | 467 | Ok(()) |
8a19eb74 | 468 | } |
9c1124fb GF |
469 | |
470 | pub fn last_modified_file(&self, pkg: &Package) -> CargoResult<(FileTime, PathBuf)> { | |
471 | if !self.updated { | |
472 | return Err(internal("BUG: source was not updated")); | |
473 | } | |
474 | ||
475 | let mut max = FileTime::zero(); | |
476 | let mut max_path = PathBuf::new(); | |
477 | for file in self.list_files(pkg)? { | |
478 | // An fs::stat error here is either because path is a | |
479 | // broken symlink, a permissions error, or a race | |
480 | // condition where this path was rm'ed - either way, | |
481 | // we can ignore the error and treat the path's mtime | |
482 | // as 0. | |
483 | let mtime = paths::mtime(&file).unwrap_or(FileTime::zero()); | |
484 | if mtime > max { | |
485 | max = mtime; | |
486 | max_path = file; | |
487 | } | |
488 | } | |
489 | trace!("last modified file {}: {}", self.path.display(), max); | |
490 | Ok((max, max_path)) | |
491 | } | |
62bff631 C |
492 | } |
493 | ||
2fe0bf83 | 494 | impl<'cfg> Debug for PathSource<'cfg> { |
50f110a4 | 495 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
cfeabbc2 | 496 | write!(f, "the paths source") |
50f110a4 YK |
497 | } |
498 | } | |
499 | ||
f947326a | 500 | impl<'cfg> Source for PathSource<'cfg> { |
6acedd8a DW |
501 | fn query(&mut self, dep: &Dependency, f: &mut FnMut(Summary)) -> CargoResult<()> { |
502 | for s in self.packages.iter().map(|p| p.summary()) { | |
503 | if dep.matches(s) { | |
504 | f(s.clone()) | |
505 | } | |
506 | } | |
507 | Ok(()) | |
f947326a | 508 | } |
5b08b8fe | 509 | |
84cc3d8b E |
510 | fn fuzzy_query(&mut self, _dep: &Dependency, f: &mut FnMut(Summary)) -> CargoResult<()> { |
511 | for s in self.packages.iter().map(|p| p.summary()) { | |
512 | f(s.clone()) | |
513 | } | |
514 | Ok(()) | |
515 | } | |
516 | ||
5b08b8fe AC |
517 | fn supports_checksums(&self) -> bool { |
518 | false | |
519 | } | |
520 | ||
521 | fn requires_precise(&self) -> bool { | |
522 | false | |
523 | } | |
b02023ee | 524 | |
6acedd8a DW |
525 | fn source_id(&self) -> &SourceId { |
526 | &self.source_id | |
53bd095f DW |
527 | } |
528 | ||
c665938d | 529 | fn update(&mut self) -> CargoResult<()> { |
21b7418a | 530 | if !self.updated { |
82655b46 | 531 | let packages = self.read_packages()?; |
3cdca46b | 532 | self.packages.extend(packages.into_iter()); |
8d5acdfc | 533 | self.updated = true; |
21b7418a CL |
534 | } |
535 | ||
98322afd YKCL |
536 | Ok(()) |
537 | } | |
62bff631 | 538 | |
9cf70517 AC |
539 | fn download(&mut self, id: &PackageId) -> CargoResult<Package> { |
540 | trace!("getting packages; id={}", id); | |
c57fef45 | 541 | |
9cf70517 | 542 | let pkg = self.packages.iter().find(|pkg| pkg.package_id() == id); |
1e682848 AC |
543 | pkg.cloned() |
544 | .ok_or_else(|| internal(format!("failed to find {} in path source", id))) | |
62bff631 | 545 | } |
e05b4dc8 | 546 | |
8d5acdfc | 547 | fn fingerprint(&self, pkg: &Package) -> CargoResult<String> { |
9c1124fb | 548 | let (max, max_path) = self.last_modified_file(pkg)?; |
c0595fbc | 549 | Ok(format!("{} ({})", max, max_path.display())) |
e05b4dc8 | 550 | } |
62bff631 | 551 | } |