Paths nested in paths
authorYehuda Katz + Carl Lerche <engineering@tilde.io>
Wed, 18 Jun 2014 00:05:29 +0000 (17:05 -0700)
committerTim Carey-Smith <tim@spork.in>
Wed, 18 Jun 2014 00:05:29 +0000 (17:05 -0700)
src/bin/cargo-git-checkout.rs
src/bin/cargo-read-manifest.rs
src/cargo/core/package.rs
src/cargo/core/source.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_read_manifest.rs
src/cargo/sources/git/source.rs
src/cargo/sources/path.rs
src/cargo/util/toml.rs
tests/test_cargo_compile_path_deps.rs [new file with mode: 0644]
tests/tests.rs

index 3a400a9147489fc710b2edf212f0d2a4f0ee2e84..2fcac0cad626f58124dabb741263cd4a64c916ad 100644 (file)
@@ -7,7 +7,7 @@ extern crate url;
 
 use hammer::FlagConfig;
 use cargo::{execute_main_without_stdin,CLIResult,CLIError,ToResult};
-use cargo::core::source::Source;
+use cargo::core::source::{Source,SourceId};
 use cargo::sources::git::{GitSource};
 use cargo::util::{Config,ToCLI};
 use url::Url;
@@ -31,7 +31,9 @@ fn execute(options: Options) -> CLIResult<Option<()>> {
     let url: Url = try!(from_str(url.as_slice()).to_result(|_|
         CLIError::new(format!("The URL `{}` you passed was not a valid URL", url), None::<&str>, 1)));
 
-    let source = GitSource::new(&url, reference.as_slice(), &try!(Config::new().to_cli(1)));
+    let source_id = SourceId::for_git(&url, reference.as_slice());
+
+    let source = GitSource::new(&source_id, &try!(Config::new().to_cli(1)));
 
     try!(source.update().map_err(|e| {
         CLIError::new(format!("Couldn't update {}: {}", source, e), None::<&str>, 1)
index b1a08de0eaf44789782bc777ddf22c84d783c556..3c3b8694d0cb8088b2921db8c32e00134396af41 100644 (file)
@@ -6,7 +6,7 @@ extern crate hammer;
 
 use hammer::FlagConfig;
 use cargo::{execute_main_without_stdin,CLIResult,CLIError};
-use cargo::core::Package;
+use cargo::core::{Package,SourceId};
 use cargo::sources::{PathSource};
 
 #[deriving(PartialEq,Clone,Decodable)]
@@ -21,6 +21,10 @@ fn main() {
 }
 
 fn execute(options: Options) -> CLIResult<Option<Package>> {
-    PathSource::read_package(&Path::new(options.manifest_path.as_slice())).map(|m| Some(m))
+    let source_id = SourceId::for_path(&Path::new(options.manifest_path.as_slice()));
+
+    PathSource::new(&source_id)
+        .get_root_package()
+        .map(|pkg| Some(pkg))
         .map_err(|err| CLIError::new(err.get_desc(), Some(err.get_detail()), 1))
 }
index 86f816c98eeeb57044f09e5e7b1ec73ed53258c1..2502ed3cba17ae5ae611e469099d66c889bb4cc6 100644 (file)
@@ -16,6 +16,7 @@ use util::{CargoResult, graph};
 use serialize::{Encoder,Encodable};
 use core::source::SourceId;
 
+// TODO: Is manifest_path a relic?
 #[deriving(Clone,PartialEq)]
 pub struct Package {
     // The package's manifest
index d093ffc065200766c8fd833f6a25f188e5161585..dec82d5013197cbe002638cb995a27a6ef6c0086 100644 (file)
@@ -66,10 +66,29 @@ impl SourceId {
         SourceId::new(PathKind, url::from_str(format!("file://{}", path.display()).as_slice()).unwrap())
     }
 
+    pub fn for_git(url: &Url, reference: &str) -> SourceId {
+        SourceId::new(GitKind(reference.to_str()), url.clone())
+    }
+
     pub fn for_central() -> SourceId {
         SourceId::new(RegistryKind, url::from_str(format!("https://example.com").as_slice()).unwrap())
     }
 
+    pub fn get_url<'a>(&'a self) -> &'a Url {
+        &self.url
+    }
+
+    pub fn is_path(&self) -> bool {
+        self.kind == PathKind
+    }
+
+    pub fn is_git(&self) -> bool {
+        match self.kind {
+            GitKind(_) => true,
+            _ => false
+        }
+    }
+
     /*
     let git_sources: Vec<Box<Source>> = try!(result::collect(package.get_sources().iter().map(|source_id: &SourceId| {
         match source_id.kind {
@@ -93,9 +112,9 @@ impl SourceId {
     pub fn load(&self, config: &Config) -> Box<Source> {
         match self.kind {
             GitKind(ref reference) => {
-                box GitSource::new(&self.url, reference.as_slice(), config) as Box<Source>
+                box GitSource::new(self, config) as Box<Source>
             },
-            PathKind => box PathSource::new(&Path::new(self.url.path.as_slice())) as Box<Source>,
+            PathKind => box PathSource::new(self) as Box<Source>,
             RegistryKind => unimplemented!()
         }
     }
index b78de842f9103a9d71423efb6ee89e5d17e834eb..fd94204fc76841af9f45dbb650168564dee437eb 100644 (file)
@@ -26,7 +26,7 @@ pub fn compile(manifest_path: &Path) -> CargoResult<()> {
     log!(4, "compile; manifest-path={}", manifest_path.display());
 
     // TODO: Move this into PathSource
-    let package = try!(PathSource::read_package(manifest_path));
+    let package = try!(PathSource::new(&SourceId::for_path(&manifest_path.dir_path())).get_root_package());
     debug!("loaded package; package={}", package);
 
     let override_ids = try!(source_ids_from_config());
index fb4d15f15d944407af17338e54f008ff3b895646..2265e28b4b001343a7132a0e46582276396e08fa 100644 (file)
@@ -1,18 +1,18 @@
 use std::io::File;
 use util;
 use url::Url;
-use core::{Package,Manifest};
+use core::{Package,Manifest,SourceId};
 use util::{CargoResult,io_error};
 
-pub fn read_manifest(contents: &[u8], namespace: &Url) -> CargoResult<Manifest> {
-    util::toml::to_manifest(contents, namespace)
+pub fn read_manifest(contents: &[u8], source_id: &SourceId) -> CargoResult<(Manifest, Vec<Path>)> {
+    util::toml::to_manifest(contents, source_id)
 }
 
-pub fn read_package(path: &Path, namespace: &Url) -> CargoResult<Package> {
-    log!(5, "read_package; path={}; namespace={}", path.display(), namespace);
+pub fn read_package(path: &Path, source_id: &SourceId) -> CargoResult<(Package, Vec<Path>)> {
+    log!(5, "read_package; path={}; source-id={}", path.display(), source_id);
     let mut file = try!(File::open(path).map_err(io_error));
     let data = try!(file.read_to_end().map_err(io_error));
-    let manifest = try!(read_manifest(data.as_slice(), namespace));
+    let (manifest, nested) = try!(read_manifest(data.as_slice(), source_id));
 
-    Ok(Package::new(manifest, path))
+    Ok((Package::new(manifest, path), nested))
 }
index 991dc71eacdb4e1903dd551cd4ca884fdfa1d9d6..b1693326f7da191ec3cec08a71cffeb092649624 100644 (file)
@@ -8,12 +8,13 @@ use url;
 use url::Url;
 
 use ops;
-use core::source::Source;
+use core::source::{Source,SourceId,GitKind};
 use core::{Package,PackageId,Summary};
 use util::{CargoResult,Config};
 use sources::git::utils::{GitReference,GitRemote,Master,Other};
 
 pub struct GitSource {
+    id: SourceId,
     remote: GitRemote,
     reference: GitReference,
     db_path: Path,
@@ -21,19 +22,27 @@ pub struct GitSource {
 }
 
 impl GitSource {
-    pub fn new(url: &Url, reference: &str, config: &Config) -> GitSource {
-        let remote = GitRemote::new(url);
-        let ident = ident(url);
+    pub fn new(source_id: &SourceId, config: &Config) -> GitSource {
+        assert!(source_id.is_git(), "id is not git, id={}", source_id);
+
+        let reference = match source_id.kind {
+            GitKind(ref reference) => reference,
+            _ => fail!("Not a git source; id={}", source_id)
+        };
+
+        let remote = GitRemote::new(source_id.get_url());
+        let ident = ident(&source_id.url);
 
         let db_path = config.git_db_path()
             .join(ident.as_slice());
 
         let checkout_path = config.git_checkout_path()
-            .join(ident.as_slice()).join(reference);
+            .join(ident.as_slice()).join(reference.as_slice());
 
         GitSource {
+            id: source_id.clone(),
             remote: remote,
-            reference: GitReference::for_str(reference),
+            reference: GitReference::for_str(reference.as_slice()),
             db_path: db_path,
             checkout_path: checkout_path
         }
@@ -88,7 +97,7 @@ impl Source for GitSource {
 
     fn list(&self) -> CargoResult<Vec<Summary>> {
         log!(5, "listing summaries in git source `{}`", self.remote);
-        let pkg = try!(read_manifest(&self.checkout_path, self.get_namespace()));
+        let pkg = try!(read_manifest(&self.checkout_path, &self.id));
         Ok(vec!(pkg.get_summary().clone()))
     }
 
@@ -99,7 +108,7 @@ impl Source for GitSource {
     fn get(&self, package_ids: &[PackageId]) -> CargoResult<Vec<Package>> {
         log!(5, "getting packages for package ids `{}` from `{}`", package_ids, self.remote);
         // TODO: Support multiple manifests per repo
-        let pkg = try!(read_manifest(&self.checkout_path, self.remote.get_url()));
+        let pkg = try!(read_manifest(&self.checkout_path, &self.id));
 
         if package_ids.iter().any(|pkg_id| pkg_id == pkg.get_package_id()) {
             Ok(vec!(pkg))
@@ -109,9 +118,11 @@ impl Source for GitSource {
     }
 }
 
-fn read_manifest(path: &Path, url: &url::Url) -> CargoResult<Package> {
+fn read_manifest(path: &Path, source_id: &SourceId) -> CargoResult<Package> {
     let path = path.join("Cargo.toml");
-    ops::read_package(&path, url)
+    // TODO: recurse
+    let (pkg, _) = try!(ops::read_package(&path, source_id));
+    Ok(pkg)
 }
 
 #[cfg(test)]
index 1aa03ddb59b545bd750ea49659349dd14629df02..a4fd3744810a6872e702772d6a22663843f4f379 100644 (file)
@@ -1,20 +1,19 @@
 use std::fmt;
 use std::fmt::{Show,Formatter};
-use core::{Package,PackageId,Summary};
-use core::source::Source;
+use core::{Package,PackageId,Summary,SourceId,Source};
 use ops;
 use url;
 use util::{CargoResult,simple_human,io_error,realpath};
 
-/* 
- * TODO: Consider whether it may be more appropriate for a PathSource to only
- * take in a single path vs. a vec of paths. The pros / cons are unknown at
- * this point.
- */
 pub struct PathSource {
-    path: Path
+    id: SourceId,
+    path: Path,
 }
 
+/**
+ * TODO: Figure out if packages should be discovered in new or self should be
+ * mut and packages are discovered in update
+ */
 impl PathSource {
 
     /**
@@ -22,20 +21,42 @@ impl PathSource {
      * The source will read the manifest and find any other packages contained
      * in the directory structure reachable by the root manifest.
      */
-    pub fn new(path: &Path) -> PathSource {
-        log!(5, "new; path={}", path.display());
-        PathSource { path: path.clone() }
+    pub fn new(id: &SourceId) -> PathSource {
+        log!(5, "new; id={}", id);
+        assert!(id.is_path(), "does not represent a path source; id={}", id);
+
+        let path = Path::new(id.get_url().path.as_slice());
+
+        PathSource {
+            id: id.clone(),
+            path: path
+        }
     }
 
-    pub fn read_package(path: &Path) -> CargoResult<Package> {
-        log!(5, "read_package; path={}", path.display());
+    /*
+    pub fn get_path<'a>(&'a self) -> &'a Path {
+        &self.path
+    }
+    */
+
+    pub fn get_root_package(&self) -> CargoResult<Package> {
+        log!(5, "get_root_package; source={}", self);
+
+        match (try!(self.packages())).as_slice().head() {
+            Some(pkg) => Ok(pkg.clone()),
+            None => Err(simple_human("no package found in source"))
+        }
+    }
 
-        // TODO: Use a realpath fn
-        let dir = path.dir_path();
-        let namespace = try!(namespace(&dir));
+    fn packages(&self) -> CargoResult<Vec<Package>> {
+        find_packages(&self.path, &self.id)
+    }
 
-        ops::read_package(path, &namespace)
+    /*
+    fn get_root_manifest_path(&self) -> Path {
+        self.path.join("Cargo.toml")
     }
+    */
 }
 
 impl Show for PathSource {
@@ -50,15 +71,8 @@ impl Source for PathSource {
     }
 
     fn list(&self) -> CargoResult<Vec<Summary>> {
-        // TODO: Recursively find manifests
-
-        match PathSource::read_package(&self.path.join("Cargo.toml")) {
-            Ok(ref pkg) => Ok(vec!(pkg.get_summary().clone())),
-            Err(e) => {
-                debug!("failed to read manifest; path={}; err={}", self.path.display(), e);
-                Err(e)
-            }
-        }
+        let pkgs = try!(self.packages());
+        Ok(pkgs.iter().map(|p| p.get_summary().clone()).collect())
     }
 
     fn download(&self, _: &[PackageId])  -> CargoResult<()>{
@@ -69,20 +83,26 @@ impl Source for PathSource {
     fn get(&self, ids: &[PackageId]) -> CargoResult<Vec<Package>> {
         log!(5, "getting packages; ids={}", ids);
 
-        PathSource::read_package(&self.path.join("Cargo.toml")).and_then(|pkg| {
-            log!(5, "comparing; pkg={}", pkg);
-
-            if ids.iter().any(|pkg_id| pkg.get_package_id() == pkg_id) {
-                Ok(vec!(pkg))
-            } else {
-                // TODO: Be smarter
-                // Err(simple_human(format!("Couldn't find `{}` in path source", ids)))
-                Ok(vec!())
-            }
-        })
+        let pkgs = try!(self.packages());
+
+        Ok(pkgs.iter()
+           .filter(|pkg| ids.iter().any(|id| pkg.get_package_id() == id))
+           .map(|pkg| pkg.clone())
+           .collect())
     }
 }
 
+fn find_packages(path: &Path, source_id: &SourceId) -> CargoResult<Vec<Package>> {
+    let (pkg, nested) = try!(ops::read_package(&path.join("Cargo.toml"), source_id));
+    let mut ret = vec!(pkg);
+
+    for path in nested.iter() {
+        ret.push_all(try!(find_packages(path, source_id)).as_slice());
+    }
+
+    Ok(ret)
+}
+
 fn namespace(path: &Path) -> CargoResult<url::Url> {
     let real = try!(realpath(path).map_err(io_error));
     url::from_str(format!("file://{}", real.display()).as_slice()).map_err(|err|
index f8c2718ce596e0f7771366fdeae9afa72c571951..0bf40537faa6e5cde8ca82fa35ec9bc270d1227b 100644 (file)
@@ -9,14 +9,14 @@ use core::manifest::{LibKind,Lib};
 use core::{Summary,Manifest,Target,Dependency,PackageId};
 use util::{CargoResult,Require,simple_human,toml_error};
 
-pub fn to_manifest(contents: &[u8], namespace: &Url) -> CargoResult<Manifest> {
+pub fn to_manifest(contents: &[u8], source_id: &SourceId) -> CargoResult<(Manifest, Vec<Path>)> {
     let root = try!(toml::parse_from_bytes(contents).map_err(|_|
         simple_human("Cargo.toml is not valid Toml")));
 
     let toml = try!(toml_to_manifest(root).map_err(|_|
         simple_human("Cargo.toml is not a valid Cargo manifest")));
 
-    toml.to_manifest(namespace)
+    toml.to_manifest(source_id)
 }
 
 fn toml_to_manifest(root: toml::Value) -> CargoResult<TomlManifest> {
@@ -114,8 +114,9 @@ impl TomlProject {
 }
 
 impl TomlManifest {
-    pub fn to_manifest(&self, namespace: &Url) -> CargoResult<Manifest> {
+    pub fn to_manifest(&self, source_id: &SourceId) -> CargoResult<(Manifest, Vec<Path>)> {
         let mut sources = vec!();
+        let mut nested_paths = vec!();
 
         // Get targets
         let targets = normalize(self.lib.as_ref().map(|l| l.as_slice()), self.bin.as_ref().map(|b| b.as_slice()));
@@ -135,7 +136,7 @@ impl TomlManifest {
                             (string.clone(), SourceId::for_central())
                         },
                         DetailedDep(ref details) => {
-                            let source_id = details.other.find_equiv(&"git").map(|git| {
+                            let new_source_id = details.other.find_equiv(&"git").map(|git| {
                                 // TODO: Don't unwrap here
                                 let kind = GitKind("master".to_str());
                                 let url = url::from_str(git.as_slice()).unwrap();
@@ -143,14 +144,14 @@ impl TomlManifest {
                                 // TODO: Don't do this for path
                                 sources.push(source_id.clone());
                                 source_id
-                            });
-
-                            // TODO: Convert relative path dependencies to namespace
-
-                            match source_id {
-                                Some(source_id) => (details.version.clone(), source_id),
-                                None => (details.version.clone(), SourceId::for_central())
-                            }
+                            }).or_else(|| {
+                                details.other.find_equiv(&"path").map(|path| {
+                                    nested_paths.push(Path::new(path.as_slice()));
+                                    source_id.clone()
+                                })
+                            }).unwrap_or(SourceId::for_central());
+
+                            (details.version.clone(), new_source_id)
                         }
                     };
 
@@ -160,11 +161,11 @@ impl TomlManifest {
             None => ()
         }
 
-        Ok(Manifest::new(
-                &Summary::new(&self.project.to_package_id(namespace), deps.as_slice()),
+        Ok((Manifest::new(
+                &Summary::new(&self.project.to_package_id(source_id.get_url()), deps.as_slice()),
                 targets.as_slice(),
                 &Path::new("target"),
-                sources))
+                sources), nested_paths))
     }
 }
 
diff --git a/tests/test_cargo_compile_path_deps.rs b/tests/test_cargo_compile_path_deps.rs
new file mode 100644 (file)
index 0000000..b4481e0
--- /dev/null
@@ -0,0 +1,81 @@
+use support::{ProjectBuilder,ResultTest,project,execs,main_file};
+use hamcrest::{assert_that,existing_file};
+use cargo;
+use cargo::util::{CargoResult,process};
+
+fn setup() {
+}
+
+test!(cargo_compile_with_nested_deps_shorthand {
+    let mut p = project("foo");
+    let bar = p.root().join("bar");
+    let baz = p.root().join("baz");
+
+    p = p
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies.bar]
+
+            version = "0.5.0"
+            path = "bar"
+
+            [[bin]]
+
+            name = "foo"
+        "#)
+        .file("src/foo.rs", main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies.baz]
+
+            version = "0.5.0"
+            path = "baz"
+
+            [[lib]]
+
+            name = "bar"
+        "#)
+        .file("bar/src/bar.rs", r#"
+            extern crate baz;
+
+            pub fn gimme() -> String {
+                baz::gimme()
+            }
+        "#)
+        .file("baz/Cargo.toml", r#"
+            [project]
+
+            name = "baz"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [[lib]]
+
+            name = "baz"
+        "#)
+        .file("baz/src/baz.rs", r#"
+            pub fn gimme() -> String {
+                "test passed".to_str()
+            }
+        "#);
+
+    p.cargo_process("cargo-compile")
+        .exec_with_output()
+        .assert();
+
+    assert_that(&p.root().join("target/foo"), existing_file());
+
+    assert_that(
+      cargo::util::process("foo").extra_path(p.root().join("target")),
+      execs().with_stdout("test passed\n"));
+})
index 3357e0a629cef6496c7feb9afc7ca1719dad89d1..1e99363fc1a0fe5a17f6970dc849f2c509c96ed6 100644 (file)
@@ -22,4 +22,5 @@ macro_rules! test(
 
 mod test_cargo_compile;
 mod test_cargo_compile_git_deps;
+mod test_cargo_compile_path_deps;
 mod test_shell;