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