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