Remove hacks when hashing package ids
authorAlex Crichton <alex@alexcrichton.com>
Tue, 1 Mar 2016 06:17:28 +0000 (22:17 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 3 Mar 2016 21:35:46 +0000 (13:35 -0800)
Right now there's a few hacks here and there to "correctly" hash package ids by
taking a package's root path into account instead of the path store in the
package id. The purpose of this was to solve issues where the same package
referenced from two locations ended up having two different hashes.

This hack leaked, however, into the implementation of fingerprints which in
turned ended up causing spurious rebuilds. Fix this problem once and for all by
just defining hashing on package ids the natural and expected way.

src/cargo/core/package.rs
src/cargo/core/package_id.rs
src/cargo/sources/git/source.rs
src/cargo/sources/path.rs
src/cargo/util/toml.rs

index c3ec0dd61835a73dfc6b73aea16fca8b986a7329..c8362ffa286bfb32417e7d68590f08b63ebe39c1 100644 (file)
@@ -87,7 +87,7 @@ impl Package {
     }
 
     pub fn generate_metadata(&self) -> Metadata {
-        self.package_id().generate_metadata(self.root())
+        self.package_id().generate_metadata()
     }
 }
 
@@ -107,15 +107,7 @@ impl Eq for Package {}
 
 impl hash::Hash for Package {
     fn hash<H: hash::Hasher>(&self, into: &mut H) {
-        // We want to be sure that a path-based package showing up at the same
-        // location always has the same hash. To that effect we don't hash the
-        // vanilla package ID if we're a path, but instead feed in our own root
-        // path.
-        if self.package_id().source_id().is_path() {
-            (0, self.root(), self.name(), self.package_id().version()).hash(into)
-        } else {
-            (1, self.package_id()).hash(into)
-        }
+        self.package_id().hash(into)
     }
 }
 
index 25bfc2796ad61a01525f62d2eea9f133229da4f3..0d968aeeef032a22f7023c43c8867f4f36591e66 100644 (file)
@@ -3,7 +3,6 @@ use std::error::Error;
 use std::fmt::{self, Formatter};
 use std::hash::Hash;
 use std::hash;
-use std::path::Path;
 use std::sync::Arc;
 
 use regex::Regex;
@@ -136,13 +135,8 @@ impl PackageId {
     pub fn version(&self) -> &semver::Version { &self.inner.version }
     pub fn source_id(&self) -> &SourceId { &self.inner.source_id }
 
-    pub fn generate_metadata(&self, source_root: &Path) -> Metadata {
-        // See comments in Package::hash for why we have this test
-        let metadata = if self.inner.source_id.is_path() {
-            short_hash(&(0, &self.inner.name, &self.inner.version, source_root))
-        } else {
-            short_hash(&(1, self))
-        };
+    pub fn generate_metadata(&self) -> Metadata {
+        let metadata = short_hash(self);
         let extra_filename = format!("-{}", metadata);
 
         Metadata { metadata: metadata, extra_filename: extra_filename }
index 0cdb02f25d33ca882c728f8b705ba4b776500a9a..95004aa7e5d315826dfc67e240b136835004425c 100644 (file)
@@ -179,8 +179,9 @@ impl<'cfg> Source for GitSource<'cfg> {
         try!(repo.copy_to(actual_rev.clone(), &self.checkout_path));
 
         let source_id = self.source_id.with_precise(Some(actual_rev.to_string()));
-        let path_source = PathSource::new(&self.checkout_path, &source_id,
-                                          self.config);
+        let path_source = PathSource::new_recursive(&self.checkout_path,
+                                                    &source_id,
+                                                    self.config);
 
         self.path_source = Some(path_source);
         self.rev = Some(actual_rev);
index 26373c7ba7e530b76e81616521721d378465ec66..e354f7a06ced82ca6edad4797370609780db91f6 100644 (file)
@@ -18,30 +18,39 @@ pub struct PathSource<'cfg> {
     updated: bool,
     packages: Vec<Package>,
     config: &'cfg Config,
+    recursive: bool,
 }
 
-// TODO: Figure out if packages should be discovered in new or self should be
-// mut and packages are discovered in update
 impl<'cfg> PathSource<'cfg> {
-    pub fn for_path(path: &Path, config: &'cfg Config)
-                    -> CargoResult<PathSource<'cfg>> {
-        trace!("PathSource::for_path; path={}", path.display());
-        Ok(PathSource::new(path, &try!(SourceId::for_path(path)), config))
-    }
-
     /// Invoked with an absolute path to a directory that contains a Cargo.toml.
-    /// The source will read the manifest and find any other packages contained
-    /// in the directory structure reachable by the root manifest.
+    ///
+    /// This source will only return the package at precisely the `path`
+    /// specified, and it will be an error if there's not a package at `path`.
     pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
                -> PathSource<'cfg> {
-        trace!("new; id={}", id);
-
         PathSource {
             id: id.clone(),
             path: path.to_path_buf(),
             updated: false,
             packages: Vec::new(),
             config: config,
+            recursive: false,
+        }
+    }
+
+    /// Creates a new source which is walked recursively to discover packages.
+    ///
+    /// This is similar to the `new` method except that instead of requiring a
+    /// valid package to be present at `root` the folder is walked entirely to
+    /// crawl for packages.
+    ///
+    /// Note that this should be used with care and likely shouldn't be chosen
+    /// by default!
+    pub fn new_recursive(root: &Path, id: &SourceId, config: &'cfg Config)
+                         -> PathSource<'cfg> {
+        PathSource {
+            recursive: true,
+            .. PathSource::new(root, id, config)
         }
     }
 
@@ -59,25 +68,13 @@ impl<'cfg> PathSource<'cfg> {
     pub fn read_packages(&self) -> CargoResult<Vec<Package>> {
         if self.updated {
             Ok(self.packages.clone())
-        } else if (self.id.is_path() && self.id.precise().is_some()) ||
-                  self.id.is_registry() {
-            // If our source id is a path and it's listed with a precise
-            // version, then it means that we're not allowed to have nested
-            // dependencies (they've been rewritten to crates.io dependencies).
-            //
-            // If our source id is a registry dependency then crates are
-            // published one at a time so we don't recurse as well. Note that
-            // cargo by default doesn't package up nested dependencies but it
-            // may do so for custom-crafted tarballs.
-            //
-            // In these cases we specifically read just one package, not a list
-            // of packages.
+        } else if self.recursive {
+            ops::read_packages(&self.path, &self.id, self.config)
+        } else {
             let path = self.path.join("Cargo.toml");
             let (pkg, _) = try!(ops::read_package(&path, &self.id,
                                                   self.config));
             Ok(vec![pkg])
-        } else {
-            ops::read_packages(&self.path, &self.id, self.config)
         }
     }
 
index 7ff7251b2e6964804fdf2e2f3b1ed15fdc2de5c2..ccbb814ab4191340eaade4acc8a41481811908da 100644 (file)
@@ -295,6 +295,7 @@ struct Context<'a, 'b> {
     config: &'b Config,
     warnings: &'a mut Vec<String>,
     platform: Option<Platform>,
+    layout: &'a Layout,
 }
 
 // These functions produce the equivalent of specific manifest entries. One
@@ -385,7 +386,7 @@ impl TomlManifest {
         }
 
         let pkgid = try!(project.to_package_id(source_id));
-        let metadata = pkgid.generate_metadata(&layout.root);
+        let metadata = pkgid.generate_metadata();
 
         // If we have no lib at all, use the inferred lib if available
         // If we have a lib with a path, we're done
@@ -516,6 +517,7 @@ impl TomlManifest {
                 config: config,
                 warnings: &mut warnings,
                 platform: None,
+                layout: &layout,
             };
 
             // Collect the deps
@@ -702,10 +704,27 @@ fn process_dependencies(cx: &mut Context,
                 Some(SourceId::for_git(&loc, reference))
             }
             None => {
-                details.path.as_ref().map(|path| {
-                    cx.nested_paths.push(PathBuf::from(path));
-                    cx.source_id.clone()
-                })
+                match details.path.as_ref() {
+                    Some(path) => {
+                        cx.nested_paths.push(PathBuf::from(path));
+                        // If the source id for the package we're parsing is a
+                        // path source, then we normalize the path here to get
+                        // rid of components like `..`.
+                        //
+                        // The purpose of this is to get a canonical id for the
+                        // package that we're depending on to ensure that builds
+                        // of this package always end up hashing to the same
+                        // value no matter where it's built from.
+                        if cx.source_id.is_path() {
+                            let path = cx.layout.root.join(path);
+                            let path = util::normalize_path(&path);
+                            Some(try!(SourceId::for_path(&path)))
+                        } else {
+                            Some(cx.source_id.clone())
+                        }
+                    }
+                    None => None,
+                }
             }
         }.unwrap_or(try!(SourceId::for_central(cx.config)));