]> git.proxmox.com Git - cargo.git/blame - src/cargo/sources/path.rs
Rename resolve_dependencies -> resolve_ws_precisely
[cargo.git] / src / cargo / sources / path.rs
CommitLineData
213afc02 1use std::fmt::{self, Debug, Formatter};
a6dad622 2use std::fs;
a6dad622
AC
3use std::path::{Path, PathBuf};
4
7d49029a 5use filetime::FileTime;
8dc7e8a4 6use git2;
046a6c59 7use glob::Pattern;
e05b4dc8 8
3b77b2c7 9use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
12f49111 10use ops;
9c445b92 11use util::{self, CargoResult, internal, internal_error, human, ChainError};
7d49029a 12use util::Config;
62bff631 13
2fe0bf83 14pub struct PathSource<'cfg> {
9224a5ae 15 id: SourceId,
a6dad622 16 path: PathBuf,
21b7418a 17 updated: bool,
5d0cb3f2 18 packages: Vec<Package>,
2fe0bf83 19 config: &'cfg Config,
e56965fb 20 recursive: bool,
98322afd 21}
62bff631 22
2fe0bf83 23impl<'cfg> PathSource<'cfg> {
64ff29ff 24 /// Invoked with an absolute path to a directory that contains a Cargo.toml.
e56965fb
AC
25 ///
26 /// This source will only return the package at precisely the `path`
27 /// specified, and it will be an error if there's not a package at `path`.
2fe0bf83
AC
28 pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
29 -> PathSource<'cfg> {
9224a5ae
YKCL
30 PathSource {
31 id: id.clone(),
a6dad622 32 path: path.to_path_buf(),
21b7418a 33 updated: false,
5d0cb3f2
AC
34 packages: Vec::new(),
35 config: config,
e56965fb
AC
36 recursive: false,
37 }
38 }
39
40 /// Creates a new source which is walked recursively to discover packages.
41 ///
42 /// This is similar to the `new` method except that instead of requiring a
43 /// valid package to be present at `root` the folder is walked entirely to
44 /// crawl for packages.
45 ///
46 /// Note that this should be used with care and likely shouldn't be chosen
47 /// by default!
48 pub fn new_recursive(root: &Path, id: &SourceId, config: &'cfg Config)
49 -> PathSource<'cfg> {
50 PathSource {
51 recursive: true,
52 .. PathSource::new(root, id, config)
9224a5ae 53 }
62bff631 54 }
c57fef45 55
339146de 56 pub fn root_package(&mut self) -> CargoResult<Package> {
7a2facba 57 trace!("root_package; source={:?}", self);
9224a5ae 58
82655b46 59 self.update()?;
21b7418a 60
a6dad622 61 match self.packages.iter().find(|p| p.root() == &*self.path) {
9224a5ae 62 Some(pkg) => Ok(pkg.clone()),
af0f2733 63 None => Err(internal("no package found in source"))
9224a5ae
YKCL
64 }
65 }
8d5acdfc 66
bc60f64b 67 pub fn read_packages(&self) -> CargoResult<Vec<Package>> {
8d5acdfc
AC
68 if self.updated {
69 Ok(self.packages.clone())
e56965fb
AC
70 } else if self.recursive {
71 ops::read_packages(&self.path, &self.id, self.config)
72 } else {
3bac6e06 73 let path = self.path.join("Cargo.toml");
82655b46
SG
74 let (pkg, _) = ops::read_package(&path, &self.id,
75 self.config)?;
3bac6e06 76 Ok(vec![pkg])
8d5acdfc
AC
77 }
78 }
8a19eb74
AC
79
80 /// List all files relevant to building this package inside this source.
81 ///
bacb6be3 82 /// This function will use the appropriate methods to determine the
8a19eb74
AC
83 /// set of files underneath this source's directory which are relevant for
84 /// building `pkg`.
85 ///
86 /// The basic assumption of this method is that all files in the directory
87 /// are relevant for building this package, but it also contains logic to
88 /// use other methods like .gitignore to filter the list of files.
a6dad622
AC
89 pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> {
90 let root = pkg.root();
8b07052b 91
a1faa7cb 92 let parse = |p: &String| {
25e537aa 93 Pattern::new(p).map_err(|e| {
d73a110d
AC
94 human(format!("could not parse pattern `{}`: {}", p, e))
95 })
96 };
ed61ba20
SG
97
98 let exclude = pkg.manifest()
99 .exclude()
100 .iter()
101 .map(|p| parse(p))
102 .collect::<Result<Vec<_>, _>>()?;
103
104 let include = pkg.manifest()
105 .include()
106 .iter()
107 .map(|p| parse(p))
108 .collect::<Result<Vec<_>, _>>()?;
8b07052b 109
f2aa5b4c 110 let mut filter = |p: &Path| {
80fe0e6d 111 let relative_path = util::without_prefix(p, &root).unwrap();
5935ec1d 112 include.iter().any(|p| p.matches_path(&relative_path)) || {
8628dbeb 113 include.is_empty() &&
5935ec1d 114 !exclude.iter().any(|p| p.matches_path(&relative_path))
8b07052b 115 }
8dc7e8a4 116 };
0e8f8d50 117
9185445a
AC
118 // If this package is in a git repository, then we really do want to
119 // query the git repository as it takes into account items such as
120 // .gitignore. We're not quite sure where the git repository is,
121 // however, so we do a bit of a probe.
5935ec1d 122 //
9185445a
AC
123 // We walk this package's path upwards and look for a sibling
124 // Cargo.toml and .git folder. If we find one then we assume that we're
125 // part of that repository.
126 let mut cur = root;
127 loop {
128 if cur.join("Cargo.toml").is_file() {
129 // If we find a git repository next to this Cargo.toml, we still
130 // check to see if we are indeed part of the index. If not, then
131 // this is likely an unrelated git repo, so keep going.
132 if let Ok(repo) = git2::Repository::open(cur) {
82655b46 133 let index = repo.index()?;
9185445a
AC
134 let path = util::without_prefix(root, cur)
135 .unwrap().join("Cargo.toml");
136 if index.get_path(&path, 0).is_some() {
137 return self.list_files_git(pkg, repo, &mut filter);
138 }
139 }
140 }
141 // don't cross submodule boundaries
142 if cur.join(".git").is_dir() {
143 break
144 }
145 match cur.parent() {
146 Some(parent) => cur = parent,
147 None => break,
148 }
5935ec1d 149 }
9185445a 150 self.list_files_walk(pkg, &mut filter)
0e8f8d50
AC
151 }
152
a8e9ce22
AC
153 fn list_files_git(&self, pkg: &Package, repo: git2::Repository,
154 filter: &mut FnMut(&Path) -> bool)
155 -> CargoResult<Vec<PathBuf>> {
7a2facba 156 warn!("list_files_git {}", pkg.package_id());
82655b46
SG
157 let index = repo.index()?;
158 let root = repo.workdir().chain_error(|| {
a8e9ce22 159 internal_error("Can't list files on a bare repository.", "")
82655b46 160 })?;
a6dad622 161 let pkg_path = pkg.root();
8b07052b 162
9185445a 163 let mut ret = Vec::<PathBuf>::new();
a8e9ce22 164
bacb6be3 165 // We use information from the git repository to guide us in traversing
a8e9ce22
AC
166 // its tree. The primary purpose of this is to take advantage of the
167 // .gitignore and auto-ignore files that don't matter.
168 //
bacb6be3 169 // Here we're also careful to look at both tracked and untracked files as
a8e9ce22
AC
170 // the untracked files are often part of a build and may become relevant
171 // as part of a future commit.
046a6c59 172 let index_files = index.iter().map(|entry| {
e0dff0f2 173 use libgit2_sys::GIT_FILEMODE_COMMIT;
632780eb 174 let is_dir = entry.mode == GIT_FILEMODE_COMMIT as u32;
046a6c59
AC
175 (join(&root, &entry.path), Some(is_dir))
176 });
a8e9ce22
AC
177 let mut opts = git2::StatusOptions::new();
178 opts.include_untracked(true);
b6ad6fb4
AC
179 if let Some(suffix) = util::without_prefix(pkg_path, &root) {
180 opts.pathspec(suffix);
181 }
82655b46 182 let statuses = repo.statuses(Some(&mut opts))?;
e2cd4cdb
FC
183 let untracked = statuses.iter().filter_map(|entry| {
184 match entry.status() {
185 git2::STATUS_WT_NEW => Some((join(&root, entry.path_bytes()), None)),
186 _ => None
187 }
a8e9ce22
AC
188 });
189
9185445a
AC
190 let mut subpackages_found = Vec::new();
191
046a6c59 192 'outer: for (file_path, is_dir) in index_files.chain(untracked) {
82655b46 193 let file_path = file_path?;
8b07052b 194
9185445a
AC
195 // Filter out files blatantly outside this package. This is helped a
196 // bit obove via the `pathspec` function call, but we need to filter
197 // the entries in the index as well.
198 if !file_path.starts_with(pkg_path) {
199 continue
a8e9ce22 200 }
8dc7e8a4 201
9185445a
AC
202 match file_path.file_name().and_then(|s| s.to_str()) {
203 // Filter out Cargo.lock and target always, we don't want to
204 // package a lock file no one will ever read and we also avoid
205 // build artifacts
206 Some("Cargo.lock") |
207 Some("target") => continue,
208
209 // Keep track of all sub-packages found and also strip out all
210 // matches we've found so far. Note, though, that if we find
211 // our own `Cargo.toml` we keep going.
212 Some("Cargo.toml") => {
213 let path = file_path.parent().unwrap();
214 if path != pkg_path {
215 warn!("subpackage found: {}", path.display());
216 ret.retain(|p| !p.starts_with(path));
217 subpackages_found.push(path.to_path_buf());
218 continue
219 }
8b07052b 220 }
9185445a
AC
221
222 _ => {}
8a19eb74
AC
223 }
224
9185445a
AC
225 // If this file is part of any other sub-package we've found so far,
226 // skip it.
227 if subpackages_found.iter().any(|p| file_path.starts_with(p)) {
228 continue
229 }
230
231 if is_dir.unwrap_or_else(|| file_path.is_dir()) {
5935ec1d 232 warn!(" found submodule {}", file_path.display());
80fe0e6d 233 let rel = util::without_prefix(&file_path, &root).unwrap();
82655b46 234 let rel = rel.to_str().chain_error(|| {
5935ec1d 235 human(format!("invalid utf-8 filename: {}", rel.display()))
82655b46 236 })?;
1e0b04a0
AC
237 // Git submodules are currently only named through `/` path
238 // separators, explicitly not `\` which windows uses. Who knew?
239 let rel = rel.replace(r"\", "/");
a8e9ce22
AC
240 match repo.find_submodule(&rel).and_then(|s| s.open()) {
241 Ok(repo) => {
82655b46 242 let files = self.list_files_git(pkg, repo, filter)?;
a8e9ce22
AC
243 ret.extend(files.into_iter());
244 }
245 Err(..) => {
82655b46
SG
246 PathSource::walk(&file_path, &mut ret, false,
247 filter)?;
a8e9ce22
AC
248 }
249 }
5935ec1d
AC
250 } else if (*filter)(&file_path) {
251 // We found a file!
252 warn!(" found {}", file_path.display());
253 ret.push(file_path);
254 }
8dc7e8a4 255 }
a6dad622
AC
256 return Ok(ret);
257
258 #[cfg(unix)]
259 fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> {
260 use std::os::unix::prelude::*;
261 use std::ffi::OsStr;
262 Ok(path.join(<OsStr as OsStrExt>::from_bytes(data)))
263 }
264 #[cfg(windows)]
265 fn join(path: &Path, data: &[u8]) -> CargoResult<PathBuf> {
266 use std::str;
267 match str::from_utf8(data) {
268 Ok(s) => Ok(path.join(s)),
269 Err(..) => Err(internal("cannot process path in git with a non \
270 unicode filename")),
271 }
272 }
0e8f8d50
AC
273 }
274
a8e9ce22
AC
275 fn list_files_walk(&self, pkg: &Package, filter: &mut FnMut(&Path) -> bool)
276 -> CargoResult<Vec<PathBuf>> {
0e8f8d50 277 let mut ret = Vec::new();
82655b46 278 PathSource::walk(pkg.root(), &mut ret, true, filter)?;
3a852a0f 279 Ok(ret)
a8e9ce22 280 }
8a19eb74 281
a8e9ce22
AC
282 fn walk(path: &Path, ret: &mut Vec<PathBuf>,
283 is_root: bool, filter: &mut FnMut(&Path) -> bool) -> CargoResult<()>
284 {
285 if !fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false) {
286 if (*filter)(path) {
287 ret.push(path.to_path_buf());
8a19eb74 288 }
0e8f8d50 289 return Ok(())
8a19eb74 290 }
a8e9ce22
AC
291 // Don't recurse into any sub-packages that we have
292 if !is_root && fs::metadata(&path.join("Cargo.toml")).is_ok() {
293 return Ok(())
294 }
82655b46
SG
295 for dir in fs::read_dir(path)? {
296 let dir = dir?.path();
11144645
LB
297 let name = dir.file_name().and_then(|s| s.to_str());
298 // Skip dotfile directories
4673f0a6 299 if name.map(|s| s.starts_with('.')) == Some(true) {
11144645
LB
300 continue
301 } else if is_root {
302 // Skip cargo artifacts
303 match name {
304 Some("target") | Some("Cargo.lock") => continue,
305 _ => {}
306 }
a8e9ce22 307 }
82655b46 308 PathSource::walk(&dir, ret, false, filter)?;
a8e9ce22 309 }
3a852a0f 310 Ok(())
8a19eb74 311 }
62bff631
C
312}
313
2fe0bf83 314impl<'cfg> Debug for PathSource<'cfg> {
50f110a4 315 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
cfeabbc2 316 write!(f, "the paths source")
50f110a4
YK
317 }
318}
319
2fe0bf83 320impl<'cfg> Registry for PathSource<'cfg> {
3b77b2c7 321 fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
348c7389 322 self.packages.query(dep)
3b77b2c7
AC
323 }
324}
325
2fe0bf83 326impl<'cfg> Source for PathSource<'cfg> {
c665938d 327 fn update(&mut self) -> CargoResult<()> {
21b7418a 328 if !self.updated {
82655b46 329 let packages = self.read_packages()?;
3cdca46b 330 self.packages.extend(packages.into_iter());
8d5acdfc 331 self.updated = true;
21b7418a
CL
332 }
333
98322afd
YKCL
334 Ok(())
335 }
62bff631 336
9cf70517
AC
337 fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
338 trace!("getting packages; id={}", id);
c57fef45 339
9cf70517
AC
340 let pkg = self.packages.iter().find(|pkg| pkg.package_id() == id);
341 pkg.cloned().ok_or_else(|| {
342 internal(format!("failed to find {} in path source", id))
343 })
62bff631 344 }
e05b4dc8 345
8d5acdfc 346 fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
5451f95d
CL
347 if !self.updated {
348 return Err(internal_error("BUG: source was not updated", ""));
349 }
350
7d49029a 351 let mut max = FileTime::zero();
c0595fbc 352 let mut max_path = PathBuf::from("");
82655b46 353 for file in self.list_files(pkg)? {
8a19eb74
AC
354 // An fs::stat error here is either because path is a
355 // broken symlink, a permissions error, or a race
356 // condition where this path was rm'ed - either way,
357 // we can ignore the error and treat the path's mtime
358 // as 0.
c0595fbc 359 let mtime = fs::metadata(&file).map(|meta| {
7d49029a
AC
360 FileTime::from_last_modification_time(&meta)
361 }).unwrap_or(FileTime::zero());
a6dad622 362 warn!("{} {}", mtime, file.display());
c0595fbc
AC
363 if mtime > max {
364 max = mtime;
365 max_path = file;
366 }
e05b4dc8 367 }
98854f6f 368 trace!("fingerprint {}: {}", self.path.display(), max);
c0595fbc 369 Ok(format!("{} ({})", max, max_path.display()))
e05b4dc8 370 }
62bff631 371}