Alternate registries.
authorWithout Boats <woboats@gmail.com>
Wed, 30 Aug 2017 05:55:31 +0000 (22:55 -0700)
committerWithout Boats <woboats@gmail.com>
Wed, 4 Oct 2017 07:20:28 +0000 (00:20 -0700)
Allow users to add dependencies which are in alternate registries.
The alternate registries are listed in the .cargo/config, and must
there have a link to a conforming crate index.

13 files changed:
src/bin/login.rs
src/cargo/core/features.rs
src/cargo/core/source.rs [deleted file]
src/cargo/core/source/mod.rs [new file with mode: 0644]
src/cargo/core/source/source_id.rs [new file with mode: 0644]
src/cargo/ops/registry.rs
src/cargo/sources/registry/mod.rs
src/cargo/sources/registry/remote.rs
src/cargo/util/config.rs
src/cargo/util/toml/mod.rs
tests/alt-registry.rs [new file with mode: 0644]
tests/bad-config.rs
tests/cargotest/support/registry.rs

index c41118cc35066d09f7601a51cf47c5c3185aa77e..5e8e0e2db96530f2b07779dcf99d9b02099017c9 100644 (file)
@@ -51,7 +51,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult {
             let mut src = RegistrySource::remote(&src, config);
             src.update()?;
             let config = src.config()?.unwrap();
-            let host = options.flag_host.clone().unwrap_or(config.api);
+            let host = options.flag_host.clone().unwrap_or(config.api.unwrap());
             println!("please visit {}me and paste the API Token below", host);
             let mut line = String::new();
             let input = io::stdin();
index 4bfafc174ddae61618516f266361e6203e4a342b..d4e91c010133511bd54a0c08f969dd090e5e216b 100644 (file)
@@ -31,7 +31,7 @@
 //! })?;
 //! ```
 //!
-//! Notably you'll notice the `require` funciton called with your `Feature`, and
+//! Notably you'll notice the `require` function called with your `Feature`, and
 //! then you use `chain_err` to tack on more context for why the feature was
 //! required when the feature isn't activated.
 //!
@@ -122,6 +122,9 @@ features! {
         // A dummy feature that gates the usage of the `im-a-teapot` manifest
         // entry. This is basically just intended for tests.
         [unstable] test_dummy_unstable: bool,
+
+        // Downloading packages from alternative registry indexes.
+        [unstable] alternative_registries: bool,
     }
 }
 
diff --git a/src/cargo/core/source.rs b/src/cargo/core/source.rs
deleted file mode 100644 (file)
index 01d6599..0000000
+++ /dev/null
@@ -1,652 +0,0 @@
-use std::cmp::{self, Ordering};
-use std::collections::hash_map::{HashMap, Values, IterMut};
-use std::fmt::{self, Formatter};
-use std::hash::{self, Hash};
-use std::path::Path;
-use std::sync::Arc;
-use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT};
-use std::sync::atomic::Ordering::SeqCst;
-
-use serde::ser;
-use serde::de;
-use url::Url;
-
-use core::{Package, PackageId, Registry};
-use ops;
-use sources::git;
-use sources::{PathSource, GitSource, RegistrySource, CRATES_IO};
-use sources::DirectorySource;
-use util::{Config, CargoResult, ToUrl};
-
-/// A Source finds and downloads remote packages based on names and
-/// versions.
-pub trait Source: Registry {
-    /// Returns the `SourceId` corresponding to this source
-    fn source_id(&self) -> &SourceId;
-
-    /// The update method performs any network operations required to
-    /// get the entire list of all names, versions and dependencies of
-    /// packages managed by the Source.
-    fn update(&mut self) -> CargoResult<()>;
-
-    /// The download method fetches the full package for each name and
-    /// version specified.
-    fn download(&mut self, package: &PackageId) -> CargoResult<Package>;
-
-    /// Generates a unique string which represents the fingerprint of the
-    /// current state of the source.
-    ///
-    /// This fingerprint is used to determine the "fresheness" of the source
-    /// later on. It must be guaranteed that the fingerprint of a source is
-    /// constant if and only if the output product will remain constant.
-    ///
-    /// The `pkg` argument is the package which this fingerprint should only be
-    /// interested in for when this source may contain multiple packages.
-    fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
-
-    /// If this source supports it, verifies the source of the package
-    /// specified.
-    ///
-    /// Note that the source may also have performed other checksum-based
-    /// verification during the `download` step, but this is intended to be run
-    /// just before a crate is compiled so it may perform more expensive checks
-    /// which may not be cacheable.
-    fn verify(&self, _pkg: &PackageId) -> CargoResult<()> {
-        Ok(())
-    }
-}
-
-impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
-    /// Forwards to `Source::source_id`
-    fn source_id(&self) -> &SourceId {
-        (**self).source_id()
-    }
-
-    /// Forwards to `Source::update`
-    fn update(&mut self) -> CargoResult<()> {
-        (**self).update()
-    }
-
-    /// Forwards to `Source::download`
-    fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
-        (**self).download(id)
-    }
-
-    /// Forwards to `Source::fingerprint`
-    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
-        (**self).fingerprint(pkg)
-    }
-
-    /// Forwards to `Source::verify`
-    fn verify(&self, pkg: &PackageId) -> CargoResult<()> {
-        (**self).verify(pkg)
-    }
-}
-
-/// The possible kinds of code source. Along with a URL, this fully defines the
-/// source
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-enum Kind {
-    /// Kind::Git(<git reference>) represents a git repository
-    Git(GitReference),
-    /// represents a local path
-    Path,
-    /// represents the central registry
-    Registry,
-    /// represents a local filesystem-based registry
-    LocalRegistry,
-    /// represents a directory-based registry
-    Directory,
-}
-
-/// Information to find a specific commit in a git repository
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum GitReference {
-    /// from a tag
-    Tag(String),
-    /// from the HEAD of a branch
-    Branch(String),
-    /// from a specific revision
-    Rev(String),
-}
-
-/// Unique identifier for a source of packages.
-#[derive(Clone, Eq, Debug)]
-pub struct SourceId {
-    inner: Arc<SourceIdInner>,
-}
-
-/// Unique identifier for a source of packages.
-#[derive(Eq, Clone, Debug)]
-struct SourceIdInner {
-    /// The source URL
-    url: Url,
-    /// `git::canonicalize_url(url)` for the url field
-    canonical_url: Url,
-    /// The source kind
-    kind: Kind,
-    // e.g. the exact git revision of the specified branch for a Git Source
-    precise: Option<String>,
-}
-
-impl SourceId {
-    /// Create a SourceId object from the kind and url.
-    ///
-    /// The canonical url will be calculated, but the precise field will not
-    fn new(kind: Kind, url: Url) -> CargoResult<SourceId> {
-        let source_id = SourceId {
-            inner: Arc::new(SourceIdInner {
-                kind: kind,
-                canonical_url: git::canonicalize_url(&url)?,
-                url: url,
-                precise: None,
-            }),
-        };
-        Ok(source_id)
-    }
-
-    /// Parses a source URL and returns the corresponding ID.
-    ///
-    /// ## Example
-    ///
-    /// ```
-    /// use cargo::core::SourceId;
-    /// SourceId::from_url("git+https://github.com/alexcrichton/\
-    ///                     libssh2-static-sys#80e71a3021618eb05\
-    ///                     656c58fb7c5ef5f12bc747f");
-    /// ```
-    pub fn from_url(string: &str) -> CargoResult<SourceId> {
-        let mut parts = string.splitn(2, '+');
-        let kind = parts.next().unwrap();
-        let url = parts.next().ok_or_else(|| format!("invalid source `{}`", string))?;
-
-        match kind {
-            "git" => {
-                let mut url = url.to_url()?;
-                let mut reference = GitReference::Branch("master".to_string());
-                for (k, v) in url.query_pairs() {
-                    match &k[..] {
-                        // map older 'ref' to branch
-                        "branch" |
-                        "ref" => reference = GitReference::Branch(v.into_owned()),
-
-                        "rev" => reference = GitReference::Rev(v.into_owned()),
-                        "tag" => reference = GitReference::Tag(v.into_owned()),
-                        _ => {}
-                    }
-                }
-                let precise = url.fragment().map(|s| s.to_owned());
-                url.set_fragment(None);
-                url.set_query(None);
-                Ok(SourceId::for_git(&url, reference)?.with_precise(precise))
-            },
-            "registry" => {
-                let url = url.to_url()?;
-                Ok(SourceId::new(Kind::Registry, url)?
-                            .with_precise(Some("locked".to_string())))
-            }
-            "path" => {
-                let url = url.to_url()?;
-                SourceId::new(Kind::Path, url)
-            }
-            kind => Err(format!("unsupported source protocol: {}", kind).into())
-        }
-    }
-
-    /// A view of the `SourceId` that can be `Display`ed as a URL
-    pub fn to_url(&self) -> SourceIdToUrl {
-        SourceIdToUrl { inner: &*self.inner }
-    }
-
-    /// Create a SourceId from a filesystem path.
-    ///
-    /// Pass absolute path
-    pub fn for_path(path: &Path) -> CargoResult<SourceId> {
-        let url = path.to_url()?;
-        SourceId::new(Kind::Path, url)
-    }
-
-    /// Crate a SourceId from a git reference
-    pub fn for_git(url: &Url, reference: GitReference) -> CargoResult<SourceId> {
-        SourceId::new(Kind::Git(reference), url.clone())
-    }
-
-    /// Create a SourceId from a registry url
-    pub fn for_registry(url: &Url) -> CargoResult<SourceId> {
-        SourceId::new(Kind::Registry, url.clone())
-    }
-
-    /// Create a SourceId from a local registry path
-    pub fn for_local_registry(path: &Path) -> CargoResult<SourceId> {
-        let url = path.to_url()?;
-        SourceId::new(Kind::LocalRegistry, url)
-    }
-
-    /// Create a SourceId from a directory path
-    pub fn for_directory(path: &Path) -> CargoResult<SourceId> {
-        let url = path.to_url()?;
-        SourceId::new(Kind::Directory, url)
-    }
-
-    /// Returns the `SourceId` corresponding to the main repository.
-    ///
-    /// This is the main cargo registry by default, but it can be overridden in
-    /// a `.cargo/config`.
-    pub fn crates_io(config: &Config) -> CargoResult<SourceId> {
-        let cfg = ops::registry_configuration(config)?;
-        let url = if let Some(ref index) = cfg.index {
-            static WARNED: AtomicBool = ATOMIC_BOOL_INIT;
-            if !WARNED.swap(true, SeqCst) {
-                config.shell().warn("custom registry support via \
-                                     the `registry.index` configuration is \
-                                     being removed, this functionality \
-                                     will not work in the future")?;
-            }
-            &index[..]
-        } else {
-            CRATES_IO
-        };
-        let url = url.to_url()?;
-        SourceId::for_registry(&url)
-    }
-
-    /// Get this source URL
-    pub fn url(&self) -> &Url {
-        &self.inner.url
-    }
-
-    /// Is this source from a filesystem path
-    pub fn is_path(&self) -> bool {
-        self.inner.kind == Kind::Path
-    }
-
-    /// Is this source from a registry (either local or not)
-    pub fn is_registry(&self) -> bool {
-        self.inner.kind == Kind::Registry || self.inner.kind == Kind::LocalRegistry
-    }
-
-    /// Is this source from a git repository
-    pub fn is_git(&self) -> bool {
-        match self.inner.kind {
-            Kind::Git(_) => true,
-            _ => false,
-        }
-    }
-
-    /// Creates an implementation of `Source` corresponding to this ID.
-    pub fn load<'a>(&self, config: &'a Config) -> CargoResult<Box<Source + 'a>> {
-        trace!("loading SourceId; {}", self);
-        match self.inner.kind {
-            Kind::Git(..) => Ok(Box::new(GitSource::new(self, config)?)),
-            Kind::Path => {
-                let path = match self.inner.url.to_file_path() {
-                    Ok(p) => p,
-                    Err(()) => panic!("path sources cannot be remote"),
-                };
-                Ok(Box::new(PathSource::new(&path, self, config)))
-            }
-            Kind::Registry => Ok(Box::new(RegistrySource::remote(self, config))),
-            Kind::LocalRegistry => {
-                let path = match self.inner.url.to_file_path() {
-                    Ok(p) => p,
-                    Err(()) => panic!("path sources cannot be remote"),
-                };
-                Ok(Box::new(RegistrySource::local(self, &path, config)))
-            }
-            Kind::Directory => {
-                let path = match self.inner.url.to_file_path() {
-                    Ok(p) => p,
-                    Err(()) => panic!("path sources cannot be remote"),
-                };
-                Ok(Box::new(DirectorySource::new(&path, self, config)))
-            }
-        }
-    }
-
-    /// Get the value of the precise field
-    pub fn precise(&self) -> Option<&str> {
-        self.inner.precise.as_ref().map(|s| &s[..])
-    }
-
-    /// Get the git reference if this is a git source, otherwise None.
-    pub fn git_reference(&self) -> Option<&GitReference> {
-        match self.inner.kind {
-            Kind::Git(ref s) => Some(s),
-            _ => None,
-        }
-    }
-
-    /// Create a new SourceId from this source with the given `precise`
-    pub fn with_precise(&self, v: Option<String>) -> SourceId {
-        SourceId {
-            inner: Arc::new(SourceIdInner {
-                precise: v,
-                ..(*self.inner).clone()
-            })
-        }
-    }
-
-    /// Whether the remote registry is the standard https://crates.io
-    pub fn is_default_registry(&self) -> bool {
-        match self.inner.kind {
-            Kind::Registry => {}
-            _ => return false,
-        }
-        self.inner.url.to_string() == CRATES_IO
-    }
-
-    /// Hash `self`
-    ///
-    /// For paths, remove the workspace prefix so the same source will give the
-    /// same hash in different locations.
-    pub fn stable_hash<S: hash::Hasher>(&self, workspace: &Path, into: &mut S) {
-        if self.is_path() {
-            if let Ok(p) = self.inner.url.to_file_path().unwrap().strip_prefix(workspace) {
-                self.inner.kind.hash(into);
-                p.to_str().unwrap().hash(into);
-                return
-            }
-        }
-        self.hash(into)
-    }
-}
-
-impl PartialEq for SourceId {
-    fn eq(&self, other: &SourceId) -> bool {
-        (*self.inner).eq(&*other.inner)
-    }
-}
-
-impl PartialOrd for SourceId {
-    fn partial_cmp(&self, other: &SourceId) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for SourceId {
-    fn cmp(&self, other: &SourceId) -> Ordering {
-        self.inner.cmp(&other.inner)
-    }
-}
-
-impl ser::Serialize for SourceId {
-    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
-        where S: ser::Serializer,
-    {
-        if self.is_path() {
-            None::<String>.serialize(s)
-        } else {
-            s.collect_str(&self.to_url())
-        }
-    }
-}
-
-impl<'de> de::Deserialize<'de> for SourceId {
-    fn deserialize<D>(d: D) -> Result<SourceId, D::Error>
-        where D: de::Deserializer<'de>,
-    {
-        let string = String::deserialize(d)?;
-        SourceId::from_url(&string).map_err(de::Error::custom)
-    }
-}
-
-impl fmt::Display for SourceId {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        match *self.inner {
-            SourceIdInner { kind: Kind::Path, ref url, .. } => {
-                fmt::Display::fmt(url, f)
-            }
-            SourceIdInner { kind: Kind::Git(ref reference), ref url,
-                            ref precise, .. } => {
-                write!(f, "{}", url)?;
-                if let Some(pretty) = reference.pretty_ref() {
-                    write!(f, "?{}", pretty)?;
-                }
-
-                if let Some(ref s) = *precise {
-                    let len = cmp::min(s.len(), 8);
-                    write!(f, "#{}", &s[..len])?;
-                }
-                Ok(())
-            }
-            SourceIdInner { kind: Kind::Registry, ref url, .. } |
-            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
-                write!(f, "registry {}", url)
-            }
-            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
-                write!(f, "dir {}", url)
-            }
-        }
-    }
-}
-
-// This custom implementation handles situations such as when two git sources
-// point at *almost* the same URL, but not quite, even when they actually point
-// to the same repository.
-/// This method tests for self and other values to be equal, and is used by ==.
-///
-/// For git repositories, the canonical url is checked.
-impl PartialEq for SourceIdInner {
-    fn eq(&self, other: &SourceIdInner) -> bool {
-        if self.kind != other.kind {
-            return false;
-        }
-        if self.url == other.url {
-            return true;
-        }
-
-        match (&self.kind, &other.kind) {
-            (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => {
-                ref1 == ref2 && self.canonical_url == other.canonical_url
-            }
-            _ => false,
-        }
-    }
-}
-
-impl PartialOrd for SourceIdInner {
-    fn partial_cmp(&self, other: &SourceIdInner) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for SourceIdInner {
-    fn cmp(&self, other: &SourceIdInner) -> Ordering {
-        match self.kind.cmp(&other.kind) {
-            Ordering::Equal => {}
-            ord => return ord,
-        }
-        match self.url.cmp(&other.url) {
-            Ordering::Equal => {}
-            ord => return ord,
-        }
-        match (&self.kind, &other.kind) {
-            (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => {
-                (ref1, &self.canonical_url).cmp(&(ref2, &other.canonical_url))
-            }
-            _ => self.kind.cmp(&other.kind),
-        }
-    }
-}
-
-// The hash of SourceId is used in the name of some Cargo folders, so shouldn't
-// vary. `as_str` gives the serialisation of a url (which has a spec) and so
-// insulates against possible changes in how the url crate does hashing.
-impl Hash for SourceId {
-    fn hash<S: hash::Hasher>(&self, into: &mut S) {
-        self.inner.kind.hash(into);
-        match *self.inner {
-            SourceIdInner { kind: Kind::Git(..), ref canonical_url, .. } => {
-                canonical_url.as_str().hash(into)
-            }
-            _ => self.inner.url.as_str().hash(into),
-        }
-    }
-}
-
-/// A `Display`able view into a SourceId that will write it as a url
-pub struct SourceIdToUrl<'a> {
-    inner: &'a SourceIdInner,
-}
-
-impl<'a> fmt::Display for SourceIdToUrl<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self.inner {
-            SourceIdInner { kind: Kind::Path, ref url, .. } => {
-                write!(f, "path+{}", url)
-            }
-            SourceIdInner {
-                kind: Kind::Git(ref reference), ref url, ref precise, ..
-            } => {
-                write!(f, "git+{}", url)?;
-                if let Some(pretty) = reference.pretty_ref() {
-                    write!(f, "?{}", pretty)?;
-                }
-                if let Some(precise) = precise.as_ref() {
-                    write!(f, "#{}", precise)?;
-                }
-                Ok(())
-            }
-            SourceIdInner { kind: Kind::Registry, ref url, .. } => {
-                write!(f, "registry+{}", url)
-            }
-            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
-                write!(f, "local-registry+{}", url)
-            }
-            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
-                write!(f, "directory+{}", url)
-            }
-        }
-    }
-}
-
-impl GitReference {
-    /// Returns a `Display`able view of this git reference, or None if using
-    /// the head of the "master" branch
-    pub fn pretty_ref(&self) -> Option<PrettyRef> {
-        match *self {
-            GitReference::Branch(ref s) if *s == "master" => None,
-            _ => Some(PrettyRef { inner: self }),
-        }
-    }
-}
-
-/// A git reference that can be `Display`ed
-pub struct PrettyRef<'a> {
-    inner: &'a GitReference,
-}
-
-impl<'a> fmt::Display for PrettyRef<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self.inner {
-            GitReference::Branch(ref b) => write!(f, "branch={}", b),
-            GitReference::Tag(ref s) => write!(f, "tag={}", s),
-            GitReference::Rev(ref s) => write!(f, "rev={}", s),
-        }
-    }
-}
-
-/// A `HashMap` of `SourceId` -> `Box<Source>`
-#[derive(Default)]
-pub struct SourceMap<'src> {
-    map: HashMap<SourceId, Box<Source + 'src>>,
-}
-
-/// A `std::collection::hash_map::Values` for `SourceMap`
-pub type Sources<'a, 'src> = Values<'a, SourceId, Box<Source + 'src>>;
-
-/// A `std::collection::hash_map::IterMut` for `SourceMap`
-pub struct SourcesMut<'a, 'src: 'a> {
-    inner: IterMut<'a, SourceId, Box<Source + 'src>>,
-}
-
-impl<'src> SourceMap<'src> {
-    /// Create an empty map
-    pub fn new() -> SourceMap<'src> {
-        SourceMap { map: HashMap::new() }
-    }
-
-    /// Like `HashMap::contains_key`
-    pub fn contains(&self, id: &SourceId) -> bool {
-        self.map.contains_key(id)
-    }
-
-    /// Like `HashMap::get`
-    pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> {
-        let source = self.map.get(id);
-
-        source.map(|s| {
-            let s: &(Source + 'src) = &**s;
-            s
-        })
-    }
-
-    /// Like `HashMap::get_mut`
-    pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> {
-        self.map.get_mut(id).map(|s| {
-            let s: &mut (Source + 'src) = &mut **s;
-            s
-        })
-    }
-
-    /// Like `HashMap::get`, but first calculates the `SourceId` from a
-    /// `PackageId`
-    pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> {
-        self.get(pkg_id.source_id())
-    }
-
-    /// Like `HashMap::insert`, but derives the SourceId key from the Source
-    pub fn insert(&mut self, source: Box<Source + 'src>) {
-        let id = source.source_id().clone();
-        self.map.insert(id, source);
-    }
-
-    /// Like `HashMap::is_empty`
-    pub fn is_empty(&self) -> bool {
-        self.map.is_empty()
-    }
-
-    /// Like `HashMap::len`
-    pub fn len(&self) -> usize {
-        self.map.len()
-    }
-
-    /// Like `HashMap::values`
-    pub fn sources<'a>(&'a self) -> Sources<'a, 'src> {
-        self.map.values()
-    }
-
-    /// Like `HashMap::iter_mut`
-    pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> {
-        SourcesMut { inner: self.map.iter_mut() }
-    }
-}
-
-impl<'a, 'src> Iterator for SourcesMut<'a, 'src> {
-    type Item = (&'a SourceId, &'a mut (Source + 'src));
-    fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> {
-        self.inner.next().map(|(a, b)| (a, &mut **b))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::{SourceId, Kind, GitReference};
-    use util::ToUrl;
-
-    #[test]
-    fn github_sources_equal() {
-        let loc = "https://github.com/foo/bar".to_url().unwrap();
-        let master = Kind::Git(GitReference::Branch("master".to_string()));
-        let s1 = SourceId::new(master.clone(), loc).unwrap();
-
-        let loc = "git://github.com/foo/bar".to_url().unwrap();
-        let s2 = SourceId::new(master, loc.clone()).unwrap();
-
-        assert_eq!(s1, s2);
-
-        let foo = Kind::Git(GitReference::Branch("foo".to_string()));
-        let s3 = SourceId::new(foo, loc).unwrap();
-        assert!(s1 != s3);
-    }
-}
diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs
new file mode 100644 (file)
index 0000000..bc54fa1
--- /dev/null
@@ -0,0 +1,157 @@
+use std::collections::hash_map::{HashMap, Values, IterMut};
+
+use core::{Package, PackageId, Registry};
+use util::CargoResult;
+
+mod source_id;
+
+pub use self::source_id::{SourceId, GitReference};
+
+/// A Source finds and downloads remote packages based on names and
+/// versions.
+pub trait Source: Registry {
+    /// Returns the `SourceId` corresponding to this source
+    fn source_id(&self) -> &SourceId;
+
+    /// The update method performs any network operations required to
+    /// get the entire list of all names, versions and dependencies of
+    /// packages managed by the Source.
+    fn update(&mut self) -> CargoResult<()>;
+
+    /// The download method fetches the full package for each name and
+    /// version specified.
+    fn download(&mut self, package: &PackageId) -> CargoResult<Package>;
+
+    /// Generates a unique string which represents the fingerprint of the
+    /// current state of the source.
+    ///
+    /// This fingerprint is used to determine the "fresheness" of the source
+    /// later on. It must be guaranteed that the fingerprint of a source is
+    /// constant if and only if the output product will remain constant.
+    ///
+    /// The `pkg` argument is the package which this fingerprint should only be
+    /// interested in for when this source may contain multiple packages.
+    fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
+
+    /// If this source supports it, verifies the source of the package
+    /// specified.
+    ///
+    /// Note that the source may also have performed other checksum-based
+    /// verification during the `download` step, but this is intended to be run
+    /// just before a crate is compiled so it may perform more expensive checks
+    /// which may not be cacheable.
+    fn verify(&self, _pkg: &PackageId) -> CargoResult<()> {
+        Ok(())
+    }
+}
+
+impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
+    /// Forwards to `Source::source_id`
+    fn source_id(&self) -> &SourceId {
+        (**self).source_id()
+    }
+
+    /// Forwards to `Source::update`
+    fn update(&mut self) -> CargoResult<()> {
+        (**self).update()
+    }
+
+    /// Forwards to `Source::download`
+    fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
+        (**self).download(id)
+    }
+
+    /// Forwards to `Source::fingerprint`
+    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
+        (**self).fingerprint(pkg)
+    }
+
+    /// Forwards to `Source::verify`
+    fn verify(&self, pkg: &PackageId) -> CargoResult<()> {
+        (**self).verify(pkg)
+    }
+}
+
+/// A `HashMap` of `SourceId` -> `Box<Source>`
+#[derive(Default)]
+pub struct SourceMap<'src> {
+    map: HashMap<SourceId, Box<Source + 'src>>,
+}
+
+/// A `std::collection::hash_map::Values` for `SourceMap`
+pub type Sources<'a, 'src> = Values<'a, SourceId, Box<Source + 'src>>;
+
+/// A `std::collection::hash_map::IterMut` for `SourceMap`
+pub struct SourcesMut<'a, 'src: 'a> {
+    inner: IterMut<'a, SourceId, Box<Source + 'src>>,
+}
+
+impl<'src> SourceMap<'src> {
+    /// Create an empty map
+    pub fn new() -> SourceMap<'src> {
+        SourceMap { map: HashMap::new() }
+    }
+
+    /// Like `HashMap::contains_key`
+    pub fn contains(&self, id: &SourceId) -> bool {
+        self.map.contains_key(id)
+    }
+
+    /// Like `HashMap::get`
+    pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> {
+        let source = self.map.get(id);
+
+        source.map(|s| {
+            let s: &(Source + 'src) = &**s;
+            s
+        })
+    }
+
+    /// Like `HashMap::get_mut`
+    pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> {
+        self.map.get_mut(id).map(|s| {
+            let s: &mut (Source + 'src) = &mut **s;
+            s
+        })
+    }
+
+    /// Like `HashMap::get`, but first calculates the `SourceId` from a
+    /// `PackageId`
+    pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> {
+        self.get(pkg_id.source_id())
+    }
+
+    /// Like `HashMap::insert`, but derives the SourceId key from the Source
+    pub fn insert(&mut self, source: Box<Source + 'src>) {
+        let id = source.source_id().clone();
+        self.map.insert(id, source);
+    }
+
+    /// Like `HashMap::is_empty`
+    pub fn is_empty(&self) -> bool {
+        self.map.is_empty()
+    }
+
+    /// Like `HashMap::len`
+    pub fn len(&self) -> usize {
+        self.map.len()
+    }
+
+    /// Like `HashMap::values`
+    pub fn sources<'a>(&'a self) -> Sources<'a, 'src> {
+        self.map.values()
+    }
+
+    /// Like `HashMap::iter_mut`
+    pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> {
+        SourcesMut { inner: self.map.iter_mut() }
+    }
+}
+
+impl<'a, 'src> Iterator for SourcesMut<'a, 'src> {
+    type Item = (&'a SourceId, &'a mut (Source + 'src));
+    fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> {
+        self.inner.next().map(|(a, b)| (a, &mut **b))
+    }
+}
+
diff --git a/src/cargo/core/source/source_id.rs b/src/cargo/core/source/source_id.rs
new file mode 100644 (file)
index 0000000..1c37d31
--- /dev/null
@@ -0,0 +1,537 @@
+use std::cmp::{self, Ordering};
+use std::fmt::{self, Formatter};
+use std::hash::{self, Hash};
+use std::path::Path;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT};
+use std::sync::atomic::Ordering::SeqCst;
+
+use serde::ser;
+use serde::de;
+use url::Url;
+
+use ops;
+use sources::git;
+use sources::{PathSource, GitSource, RegistrySource, CRATES_IO};
+use sources::DirectorySource;
+use util::{Config, ConfigValue as CV, CargoResult, ToUrl};
+
+/// Unique identifier for a source of packages.
+#[derive(Clone, Eq, Debug)]
+pub struct SourceId {
+    inner: Arc<SourceIdInner>,
+}
+
+#[derive(Eq, Clone, Debug)]
+struct SourceIdInner {
+    /// The source URL
+    url: Url,
+    /// `git::canonicalize_url(url)` for the url field
+    canonical_url: Url,
+    /// The source kind
+    kind: Kind,
+    // e.g. the exact git revision of the specified branch for a Git Source
+    precise: Option<String>,
+}
+
+/// The possible kinds of code source. Along with a URL, this fully defines the
+/// source
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum Kind {
+    /// Kind::Git(<git reference>) represents a git repository
+    Git(GitReference),
+    /// represents a local path
+    Path,
+    /// represents a remote registry
+    Registry,
+    /// represents a local filesystem-based registry
+    LocalRegistry,
+    /// represents a directory-based registry
+    Directory,
+}
+
+/// Information to find a specific commit in a git repository
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum GitReference {
+    /// from a tag
+    Tag(String),
+    /// from the HEAD of a branch
+    Branch(String),
+    /// from a specific revision
+    Rev(String),
+}
+
+impl SourceId {
+    /// Create a SourceId object from the kind and url.
+    ///
+    /// The canonical url will be calculated, but the precise field will not
+    fn new(kind: Kind, url: Url) -> CargoResult<SourceId> {
+        let source_id = SourceId {
+            inner: Arc::new(SourceIdInner {
+                kind: kind,
+                canonical_url: git::canonicalize_url(&url)?,
+                url: url,
+                precise: None,
+            }),
+        };
+        Ok(source_id)
+    }
+
+    /// Parses a source URL and returns the corresponding ID.
+    ///
+    /// ## Example
+    ///
+    /// ```
+    /// use cargo::core::SourceId;
+    /// SourceId::from_url("git+https://github.com/alexcrichton/\
+    ///                     libssh2-static-sys#80e71a3021618eb05\
+    ///                     656c58fb7c5ef5f12bc747f");
+    /// ```
+    pub fn from_url(string: &str) -> CargoResult<SourceId> {
+        let mut parts = string.splitn(2, '+');
+        let kind = parts.next().unwrap();
+        let url = parts.next().ok_or_else(|| format!("invalid source `{}`", string))?;
+
+        match kind {
+            "git" => {
+                let mut url = url.to_url()?;
+                let mut reference = GitReference::Branch("master".to_string());
+                for (k, v) in url.query_pairs() {
+                    match &k[..] {
+                        // map older 'ref' to branch
+                        "branch" |
+                        "ref" => reference = GitReference::Branch(v.into_owned()),
+
+                        "rev" => reference = GitReference::Rev(v.into_owned()),
+                        "tag" => reference = GitReference::Tag(v.into_owned()),
+                        _ => {}
+                    }
+                }
+                let precise = url.fragment().map(|s| s.to_owned());
+                url.set_fragment(None);
+                url.set_query(None);
+                Ok(SourceId::for_git(&url, reference)?.with_precise(precise))
+            },
+            "registry" => {
+                let url = url.to_url()?;
+                Ok(SourceId::new(Kind::Registry, url)?
+                            .with_precise(Some("locked".to_string())))
+            }
+            "path" => {
+                let url = url.to_url()?;
+                SourceId::new(Kind::Path, url)
+            }
+            kind => Err(format!("unsupported source protocol: {}", kind).into())
+        }
+    }
+
+    /// A view of the `SourceId` that can be `Display`ed as a URL
+    pub fn to_url(&self) -> SourceIdToUrl {
+        SourceIdToUrl { inner: &*self.inner }
+    }
+
+    /// Create a SourceId from a filesystem path.
+    ///
+    /// Pass absolute path
+    pub fn for_path(path: &Path) -> CargoResult<SourceId> {
+        let url = path.to_url()?;
+        SourceId::new(Kind::Path, url)
+    }
+
+    /// Crate a SourceId from a git reference
+    pub fn for_git(url: &Url, reference: GitReference) -> CargoResult<SourceId> {
+        SourceId::new(Kind::Git(reference), url.clone())
+    }
+
+    /// Create a SourceId from a registry url
+    pub fn for_registry(url: &Url) -> CargoResult<SourceId> {
+        SourceId::new(Kind::Registry, url.clone())
+    }
+
+    /// Create a SourceId from a local registry path
+    pub fn for_local_registry(path: &Path) -> CargoResult<SourceId> {
+        let url = path.to_url()?;
+        SourceId::new(Kind::LocalRegistry, url)
+    }
+
+    /// Create a SourceId from a directory path
+    pub fn for_directory(path: &Path) -> CargoResult<SourceId> {
+        let url = path.to_url()?;
+        SourceId::new(Kind::Directory, url)
+    }
+
+    /// Returns the `SourceId` corresponding to the main repository.
+    ///
+    /// This is the main cargo registry by default, but it can be overridden in
+    /// a `.cargo/config`.
+    pub fn crates_io(config: &Config) -> CargoResult<SourceId> {
+        let cfg = ops::registry_configuration(config)?;
+        let url = if let Some(ref index) = cfg.index {
+            static WARNED: AtomicBool = ATOMIC_BOOL_INIT;
+            if !WARNED.swap(true, SeqCst) {
+                config.shell().warn("custom registry support via \
+                                     the `registry.index` configuration is \
+                                     being removed, this functionality \
+                                     will not work in the future")?;
+            }
+            &index[..]
+        } else {
+            CRATES_IO
+        };
+        let url = url.to_url()?;
+        SourceId::for_registry(&url)
+    }
+
+    pub fn alt_registry(config: &Config, key: &str) -> CargoResult<SourceId> {
+        let registries = config.get_table("registries")?;
+        match registries.as_ref().and_then(|registries| registries.val.get(key)) {
+            Some(registry)  => {
+                let index = match *registry {
+                    CV::Table(ref registry, _)  => {
+                        match registry.get("index") {
+                            Some(index) => index.string(&format!("registries.{}", key))?.0,
+                            None => return Err(format!("No index for registry `{}`", key).into()),
+                        }
+                    }
+                    _ => registry.expected("table", &format!("registries.{}", key))?
+                };
+
+                let url = index.to_url()?;
+
+                Ok(SourceId {
+                    inner: Arc::new(SourceIdInner {
+                        kind: Kind::Registry,
+                        canonical_url: git::canonicalize_url(&url)?,
+                        url: url,
+                        precise: None,
+                    }),
+                })
+            }
+            None => Err(format!("Required unknown registry source: `{}`", key).into())
+        }
+    }
+
+    /// Get this source URL
+    pub fn url(&self) -> &Url {
+        &self.inner.url
+    }
+
+    pub fn display_registry(&self) -> String {
+        format!("registry `{}`", self.url())
+    }
+
+    /// Is this source from a filesystem path
+    pub fn is_path(&self) -> bool {
+        self.inner.kind == Kind::Path
+    }
+
+    /// Is this source from a registry (either local or not)
+    pub fn is_registry(&self) -> bool {
+        match self.inner.kind {
+            Kind::Registry | Kind::LocalRegistry    => true,
+            _                                       => false,
+        }
+    }
+
+    /// Is this source from a git repository
+    pub fn is_git(&self) -> bool {
+        match self.inner.kind {
+            Kind::Git(_) => true,
+            _ => false,
+        }
+    }
+
+    /// Creates an implementation of `Source` corresponding to this ID.
+    pub fn load<'a>(&self, config: &'a Config) -> CargoResult<Box<super::Source + 'a>> {
+        trace!("loading SourceId; {}", self);
+        match self.inner.kind {
+            Kind::Git(..) => Ok(Box::new(GitSource::new(self, config)?)),
+            Kind::Path => {
+                let path = match self.inner.url.to_file_path() {
+                    Ok(p) => p,
+                    Err(()) => panic!("path sources cannot be remote"),
+                };
+                Ok(Box::new(PathSource::new(&path, self, config)))
+            }
+            Kind::Registry => Ok(Box::new(RegistrySource::remote(self, config))),
+            Kind::LocalRegistry => {
+                let path = match self.inner.url.to_file_path() {
+                    Ok(p) => p,
+                    Err(()) => panic!("path sources cannot be remote"),
+                };
+                Ok(Box::new(RegistrySource::local(self, &path, config)))
+            }
+            Kind::Directory => {
+                let path = match self.inner.url.to_file_path() {
+                    Ok(p) => p,
+                    Err(()) => panic!("path sources cannot be remote"),
+                };
+                Ok(Box::new(DirectorySource::new(&path, self, config)))
+            }
+        }
+    }
+
+    /// Get the value of the precise field
+    pub fn precise(&self) -> Option<&str> {
+        self.inner.precise.as_ref().map(|s| &s[..])
+    }
+
+    /// Get the git reference if this is a git source, otherwise None.
+    pub fn git_reference(&self) -> Option<&GitReference> {
+        match self.inner.kind {
+            Kind::Git(ref s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Create a new SourceId from this source with the given `precise`
+    pub fn with_precise(&self, v: Option<String>) -> SourceId {
+        SourceId {
+            inner: Arc::new(SourceIdInner {
+                precise: v,
+                ..(*self.inner).clone()
+            })
+        }
+    }
+
+    /// Whether the remote registry is the standard https://crates.io
+    pub fn is_default_registry(&self) -> bool {
+        match self.inner.kind {
+            Kind::Registry => {}
+            _ => return false,
+        }
+        self.inner.url.to_string() == CRATES_IO
+    }
+
+    /// Hash `self`
+    ///
+    /// For paths, remove the workspace prefix so the same source will give the
+    /// same hash in different locations.
+    pub fn stable_hash<S: hash::Hasher>(&self, workspace: &Path, into: &mut S) {
+        if self.is_path() {
+            if let Ok(p) = self.inner.url.to_file_path().unwrap().strip_prefix(workspace) {
+                self.inner.kind.hash(into);
+                p.to_str().unwrap().hash(into);
+                return
+            }
+        }
+        self.hash(into)
+    }
+}
+
+impl PartialEq for SourceId {
+    fn eq(&self, other: &SourceId) -> bool {
+        (*self.inner).eq(&*other.inner)
+    }
+}
+
+impl PartialOrd for SourceId {
+    fn partial_cmp(&self, other: &SourceId) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SourceId {
+    fn cmp(&self, other: &SourceId) -> Ordering {
+        self.inner.cmp(&other.inner)
+    }
+}
+
+impl ser::Serialize for SourceId {
+    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
+        where S: ser::Serializer,
+    {
+        if self.is_path() {
+            None::<String>.serialize(s)
+        } else {
+            s.collect_str(&self.to_url())
+        }
+    }
+}
+
+impl<'de> de::Deserialize<'de> for SourceId {
+    fn deserialize<D>(d: D) -> Result<SourceId, D::Error>
+        where D: de::Deserializer<'de>,
+    {
+        let string = String::deserialize(d)?;
+        SourceId::from_url(&string).map_err(de::Error::custom)
+    }
+}
+
+impl fmt::Display for SourceId {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match *self.inner {
+            SourceIdInner { kind: Kind::Path, ref url, .. } => {
+                fmt::Display::fmt(url, f)
+            }
+            SourceIdInner { kind: Kind::Git(ref reference), ref url,
+                            ref precise, .. } => {
+                write!(f, "{}", url)?;
+                if let Some(pretty) = reference.pretty_ref() {
+                    write!(f, "?{}", pretty)?;
+                }
+
+                if let Some(ref s) = *precise {
+                    let len = cmp::min(s.len(), 8);
+                    write!(f, "#{}", &s[..len])?;
+                }
+                Ok(())
+            }
+            SourceIdInner { kind: Kind::Registry, ref url, .. } |
+            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
+                write!(f, "registry `{}`", url)
+            }
+            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+                write!(f, "dir {}", url)
+            }
+        }
+    }
+}
+
+// This custom implementation handles situations such as when two git sources
+// point at *almost* the same URL, but not quite, even when they actually point
+// to the same repository.
+/// This method tests for self and other values to be equal, and is used by ==.
+///
+/// For git repositories, the canonical url is checked.
+impl PartialEq for SourceIdInner {
+    fn eq(&self, other: &SourceIdInner) -> bool {
+        if self.kind != other.kind {
+            return false;
+        }
+        if self.url == other.url {
+            return true;
+        }
+
+        match (&self.kind, &other.kind) {
+            (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => {
+                ref1 == ref2 && self.canonical_url == other.canonical_url
+            }
+            _ => false,
+        }
+    }
+}
+
+impl PartialOrd for SourceIdInner {
+    fn partial_cmp(&self, other: &SourceIdInner) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SourceIdInner {
+    fn cmp(&self, other: &SourceIdInner) -> Ordering {
+        match self.kind.cmp(&other.kind) {
+            Ordering::Equal => {}
+            ord => return ord,
+        }
+        match self.url.cmp(&other.url) {
+            Ordering::Equal => {}
+            ord => return ord,
+        }
+        match (&self.kind, &other.kind) {
+            (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => {
+                (ref1, &self.canonical_url).cmp(&(ref2, &other.canonical_url))
+            }
+            _ => self.kind.cmp(&other.kind),
+        }
+    }
+}
+
+// The hash of SourceId is used in the name of some Cargo folders, so shouldn't
+// vary. `as_str` gives the serialisation of a url (which has a spec) and so
+// insulates against possible changes in how the url crate does hashing.
+impl Hash for SourceId {
+    fn hash<S: hash::Hasher>(&self, into: &mut S) {
+        self.inner.kind.hash(into);
+        match *self.inner {
+            SourceIdInner { kind: Kind::Git(..), ref canonical_url, .. } => {
+                canonical_url.as_str().hash(into)
+            }
+            _ => self.inner.url.as_str().hash(into),
+        }
+    }
+}
+
+/// A `Display`able view into a SourceId that will write it as a url
+pub struct SourceIdToUrl<'a> {
+    inner: &'a SourceIdInner,
+}
+
+impl<'a> fmt::Display for SourceIdToUrl<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self.inner {
+            SourceIdInner { kind: Kind::Path, ref url, .. } => {
+                write!(f, "path+{}", url)
+            }
+            SourceIdInner {
+                kind: Kind::Git(ref reference), ref url, ref precise, ..
+            } => {
+                write!(f, "git+{}", url)?;
+                if let Some(pretty) = reference.pretty_ref() {
+                    write!(f, "?{}", pretty)?;
+                }
+                if let Some(precise) = precise.as_ref() {
+                    write!(f, "#{}", precise)?;
+                }
+                Ok(())
+            }
+            SourceIdInner { kind: Kind::Registry, ref url, .. } => {
+                write!(f, "registry+{}", url)
+            }
+            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
+                write!(f, "local-registry+{}", url)
+            }
+            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+                write!(f, "directory+{}", url)
+            }
+        }
+    }
+}
+
+impl GitReference {
+    /// Returns a `Display`able view of this git reference, or None if using
+    /// the head of the "master" branch
+    pub fn pretty_ref(&self) -> Option<PrettyRef> {
+        match *self {
+            GitReference::Branch(ref s) if *s == "master" => None,
+            _ => Some(PrettyRef { inner: self }),
+        }
+    }
+}
+
+/// A git reference that can be `Display`ed
+pub struct PrettyRef<'a> {
+    inner: &'a GitReference,
+}
+
+impl<'a> fmt::Display for PrettyRef<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self.inner {
+            GitReference::Branch(ref b) => write!(f, "branch={}", b),
+            GitReference::Tag(ref s) => write!(f, "tag={}", s),
+            GitReference::Rev(ref s) => write!(f, "rev={}", s),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{SourceId, Kind, GitReference};
+    use util::ToUrl;
+
+    #[test]
+    fn github_sources_equal() {
+        let loc = "https://github.com/foo/bar".to_url().unwrap();
+        let master = Kind::Git(GitReference::Branch("master".to_string()));
+        let s1 = SourceId::new(master.clone(), loc).unwrap();
+
+        let loc = "git://github.com/foo/bar".to_url().unwrap();
+        let s2 = SourceId::new(master, loc.clone()).unwrap();
+
+        assert_eq!(s1, s2);
+
+        let foo = Kind::Git(GitReference::Branch("foo".to_string()));
+        let s3 = SourceId::new(foo, loc).unwrap();
+        assert!(s1 != s3);
+    }
+}
index 42ff2f872076205f06e18edbc5d054265d20145b..4d21be92260eb30b9a93f1c655e2372a179d994c 100644 (file)
@@ -83,11 +83,18 @@ fn verify_dependencies(pkg: &Package, registry_src: &SourceId)
                        a version", dep.name())
             }
         } else if dep.source_id() != registry_src {
-            bail!("crates cannot be published to crates.io with dependencies sourced from \
-                   a repository\neither publish `{}` as its own crate on crates.io and \
-                   specify a crates.io version as a dependency or pull it into this \
-                   repository and specify it with a path and version\n(crate `{}` has \
-                   repository path `{}`)", dep.name(), dep.name(),  dep.source_id());
+            if dep.source_id().is_registry() {
+                bail!("crates cannot be published to crates.io with dependencies sourced from other\n\
+                       registries either publish `{}` on crates.io or pull it into this repository\n\
+                       and specify it with a path and version\n\
+                       (crate `{}` is pulled from {}", dep.name(), dep.name(), dep.source_id());
+            } else {
+                bail!("crates cannot be published to crates.io with dependencies sourced from \
+                       a repository\neither publish `{}` as its own crate on crates.io and \
+                       specify a crates.io version as a dependency or pull it into this \
+                       repository and specify it with a path and version\n(crate `{}` has \
+                       repository path `{}`)", dep.name(), dep.name(),  dep.source_id());
+            }
         }
     }
     Ok(())
@@ -205,7 +212,7 @@ pub fn registry(config: &Config,
         src.update().chain_err(|| {
             format!("failed to update {}", sid)
         })?;
-        (src.config()?).unwrap().api
+        (src.config()?).unwrap().api.unwrap()
     };
     let handle = http_handle(config)?;
     Ok((Registry::new_handle(api_host, token, handle), sid))
index c967e2ebcd3a824fa8a9834af3628361b0dd061f..8b907f7d33ccc2bfc304a4fe6deb9851aedd9caf 100644 (file)
@@ -198,7 +198,7 @@ pub struct RegistryConfig {
 
     /// API endpoint for the registry. This is what's actually hit to perform
     /// operations like yanks, owner modifications, publish new crates, etc.
-    pub api: String,
+    pub api: Option<String>,
 }
 
 #[derive(Deserialize)]
index 6704282b897d8138e7f1875e3f9d63dc724bf663..846226107147fa91d5492ac406bc66d7b92d36d4 100644 (file)
@@ -166,8 +166,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
         let _lock = self.index_path.open_rw(Path::new(INDEX_LOCK),
                                             self.config,
                                             "the registry index")?;
-        self.config.shell().status("Updating",
-             format!("registry `{}`", self.source_id.url()))?;
+        self.config.shell().status("Updating", self.source_id.display_registry())?;
 
         // git fetch origin master
         let url = self.source_id.url();
index 38e2e303fab8bb2a578fddae8bd51cc01cf0b935..252d8cb01a29ab8f89338719f3b9869e4ab70b85 100644 (file)
@@ -762,7 +762,7 @@ impl ConfigValue {
         }
     }
 
-    fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
+    pub fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
         Err(format!("expected a {}, but found a {} for `{}` in {}",
                     wanted, self.desc(), key,
                     self.definition_path().display()).into())
index c2220ac2d6e7254c219739276b9d474769d8cb3f..487d46f93c974e50a6dda4956dc8b031d32a9f34 100644 (file)
@@ -14,7 +14,7 @@ use url::Url;
 
 use core::{SourceId, Profiles, PackageIdSpec, GitReference, WorkspaceConfig};
 use core::{Summary, Manifest, Target, Dependency, PackageId};
-use core::{EitherManifest, VirtualManifest, Features};
+use core::{EitherManifest, VirtualManifest, Features, Feature};
 use core::dependency::{Kind, Platform};
 use core::manifest::{LibKind, Profile, ManifestMetadata};
 use sources::CRATES_IO;
@@ -184,6 +184,7 @@ impl<'de> de::Deserialize<'de> for TomlDependency {
 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
 pub struct DetailedTomlDependency {
     version: Option<String>,
+    registry: Option<String>,
     path: Option<String>,
     git: Option<String>,
     branch: Option<String>,
@@ -429,6 +430,7 @@ struct Context<'a, 'b> {
     warnings: &'a mut Vec<String>,
     platform: Option<Platform>,
     root: &'a Path,
+    features: &'a Features,
 }
 
 impl TomlManifest {
@@ -511,6 +513,11 @@ impl TomlManifest {
         let mut warnings = vec![];
         let mut errors = vec![];
 
+        // Parse features first so they will be available when parsing other parts of the toml
+        let empty = Vec::new();
+        let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
+        let features = Features::new(&cargo_features, &mut warnings)?;
+
         let project = me.project.as_ref().or_else(|| me.package.as_ref());
         let project = project.ok_or_else(|| {
             CargoError::from("no `package` or `project` section found.")
@@ -551,6 +558,7 @@ impl TomlManifest {
                 nested_paths: &mut nested_paths,
                 config: config,
                 warnings: &mut warnings,
+                features: &features,
                 platform: None,
                 root: package_root,
             };
@@ -649,9 +657,6 @@ impl TomlManifest {
         };
         let profiles = build_profiles(&me.profile);
         let publish = project.publish.unwrap_or(true);
-        let empty = Vec::new();
-        let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
-        let features = Features::new(cargo_features, &mut warnings)?;
         let mut manifest = Manifest::new(summary,
                                          targets,
                                          exclude,
@@ -721,6 +726,7 @@ impl TomlManifest {
                 config: config,
                 warnings: &mut warnings,
                 platform: None,
+                features: &Features::default(), // @alex: is this right?
                 root: root
             };
             (me.replace(&mut cx)?, me.patch(&mut cx)?)
@@ -867,8 +873,12 @@ impl TomlDependency {
             }
         }
 
-        let new_source_id = match (details.git.as_ref(), details.path.as_ref()) {
-            (Some(git), maybe_path) => {
+        let new_source_id = match (details.git.as_ref(), details.path.as_ref(), details.registry.as_ref()) {
+            (Some(_), _, Some(_)) => bail!("dependency ({}) specification is ambiguous. \
+                                            Only one of `git` or `registry` is allowed.", name),
+            (_, Some(_), Some(_)) => bail!("dependency ({}) specification is ambiguous. \
+                                            Only one of `path` or `registry` is allowed.", name),
+            (Some(git), maybe_path, _) => {
                 if maybe_path.is_some() {
                     let msg = format!("dependency ({}) specification is ambiguous. \
                                        Only one of `git` or `path` is allowed. \
@@ -895,7 +905,7 @@ impl TomlDependency {
                 let loc = git.to_url()?;
                 SourceId::for_git(&loc, reference)?
             },
-            (None, Some(path)) => {
+            (None, 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
@@ -913,7 +923,11 @@ impl TomlDependency {
                     cx.source_id.clone()
                 }
             },
-            (None, None) => SourceId::crates_io(cx.config)?,
+            (None, None, Some(registry)) => {
+                cx.features.require(Feature::alternative_registries())?;
+                SourceId::alt_registry(cx.config, registry)?
+            }
+            (None, None, None) => SourceId::crates_io(cx.config)?,
         };
 
         let version = details.version.as_ref().map(|v| &v[..]);
diff --git a/tests/alt-registry.rs b/tests/alt-registry.rs
new file mode 100644 (file)
index 0000000..9d701ba
--- /dev/null
@@ -0,0 +1,145 @@
+extern crate cargotest;
+extern crate hamcrest;
+
+use cargotest::ChannelChanger;
+use cargotest::support::registry::{self, Package};
+use cargotest::support::{project, execs};
+use hamcrest::assert_that;
+
+#[test]
+fn is_feature_gated() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            version = "0.0.1"
+            registry = "alternative"
+        "#)
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    Package::new("bar", "0.0.1").alternative(true).publish();
+
+    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
+                execs().with_status(101)
+                .with_stderr_contains("  feature `alternative-registries` is required"));
+}
+
+#[test]
+fn depend_on_alt_registry() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            cargo-features = ["alternative-registries"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            version = "0.0.1"
+            registry = "alternative"
+        "#)
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    Package::new("bar", "0.0.1").alternative(true).publish();
+
+    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
+                execs().with_status(0).with_stderr(&format!("\
+[UPDATING] registry `{reg}`
+[DOWNLOADING] bar v0.0.1 (registry `file://[..]`)
+[COMPILING] bar v0.0.1 (registry `file://[..]`)
+[COMPILING] foo v0.0.1 ({dir})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
+",
+        dir = p.url(),
+        reg = registry::alt_registry())));
+
+    assert_that(p.cargo("clean").masquerade_as_nightly_cargo(), execs().with_status(0));
+
+    // Don't download a second time
+    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
+                execs().with_status(0).with_stderr(&format!("\
+[COMPILING] bar v0.0.1 (registry `file://[..]`)
+[COMPILING] foo v0.0.1 ({dir})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
+",
+        dir = p.url())));
+}
+
+#[test]
+fn registry_incompatible_with_path() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            cargo-features = ["alternative-registries"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            path = ""
+            registry = "alternative"
+        "#)
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
+                execs().with_status(101)
+                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `path` or `registry` is allowed."));
+}
+
+#[test]
+fn registry_incompatible_with_git() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            cargo-features = ["alternative-registries"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            git = ""
+            registry = "alternative"
+        "#)
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
+                execs().with_status(101)
+                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `git` or `registry` is allowed."));
+}
+
+
+#[test]
+fn cannot_publish_with_registry_dependency() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            cargo-features = ["alternative-registries"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            version = "0.0.1"
+            registry = "alternative"
+        "#)
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    Package::new("bar", "0.0.1").alternative(true).publish();
+
+    assert_that(p.cargo("publish").masquerade_as_nightly_cargo()
+                 .arg("--index").arg(registry::alt_registry().to_string()),
+                execs().with_status(101));
+}
index cff576dbb4722866e3960ef76d26563f8f9f5e90..30746f0c917d0aa9703f0faa6751f1062a296b94 100644 (file)
@@ -794,7 +794,7 @@ fn bad_source_config2() {
 error: failed to load source for a dependency on `bar`
 
 Caused by:
-  Unable to update registry https://[..]
+  Unable to update registry `https://[..]`
 
 Caused by:
   could not find a configured source with the name `bar` \
@@ -826,7 +826,7 @@ fn bad_source_config3() {
 error: failed to load source for a dependency on `bar`
 
 Caused by:
-  Unable to update registry https://[..]
+  Unable to update registry `https://[..]`
 
 Caused by:
   detected a cycle of `replace-with` sources, [..]
@@ -861,7 +861,7 @@ fn bad_source_config4() {
 error: failed to load source for a dependency on `bar`
 
 Caused by:
-  Unable to update registry https://[..]
+  Unable to update registry `https://[..]`
 
 Caused by:
   detected a cycle of `replace-with` sources, the source `crates-io` is \
index 3046d2e20c5e0f1f76687b6fc0dea5fb4ea11130..1e0b28cd0252e3afb92e605f8cafb6998bfbeb17 100644 (file)
@@ -18,6 +18,8 @@ pub fn registry_path() -> PathBuf { paths::root().join("registry") }
 pub fn registry() -> Url { Url::from_file_path(&*registry_path()).ok().unwrap() }
 pub fn dl_path() -> PathBuf { paths::root().join("dl") }
 pub fn dl_url() -> Url { Url::from_file_path(&*dl_path()).ok().unwrap() }
+pub fn alt_registry_path() -> PathBuf { paths::root().join("alternative-registry") }
+pub fn alt_registry() -> Url { Url::from_file_path(&*alt_registry_path()).ok().unwrap() }
 
 pub struct Package {
     name: String,
@@ -28,6 +30,7 @@ pub struct Package {
     yanked: bool,
     features: HashMap<String, Vec<String>>,
     local: bool,
+    alternative: bool,
 }
 
 struct Dependency {
@@ -54,7 +57,10 @@ pub fn init() {
 
         [source.dummy-registry]
         registry = '{reg}'
-    "#, reg = registry()).as_bytes()));
+
+        [registries.alternative]
+        index = '{alt}'
+    "#, reg = registry(), alt = alt_registry()).as_bytes()));
 
     // Init a new registry
     repo(&registry_path())
@@ -63,6 +69,14 @@ pub fn init() {
         "#, dl_url()))
         .build();
     fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap();
+
+    // Init an alt registry
+    repo(&alt_registry_path())
+        .file("config.json", &format!(r#"
+            {{"dl":"{0}","api":"{0}"}}
+        "#, dl_url()))
+        .build();
+    fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap();
 }
 
 impl Package {
@@ -77,6 +91,7 @@ impl Package {
             yanked: false,
             features: HashMap::new(),
             local: false,
+            alternative: false,
         }
     }
 
@@ -85,6 +100,11 @@ impl Package {
         self
     }
 
+    pub fn alternative(&mut self, alternative: bool) -> &mut Package {
+        self.alternative = alternative;
+        self
+    }
+
     pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
         self.files.push((name.to_string(), contents.to_string()));
         self
@@ -174,11 +194,13 @@ impl Package {
             _ => format!("{}/{}/{}", &self.name[0..2], &self.name[2..4], self.name),
         };
 
+        let registry_path = if self.alternative { alt_registry_path() } else { registry_path() };
+
         // Write file/line in the index
         let dst = if self.local {
-            registry_path().join("index").join(&file)
+            registry_path.join("index").join(&file)
         } else {
-            registry_path().join(&file)
+            registry_path.join(&file)
         };
         let mut prev = String::new();
         let _ = File::open(&dst).and_then(|mut f| f.read_to_string(&mut prev));
@@ -188,7 +210,7 @@ impl Package {
 
         // Add the new file to the index
         if !self.local {
-            let repo = t!(git2::Repository::open(&registry_path()));
+            let repo = t!(git2::Repository::open(&registry_path));
             let mut index = t!(repo.index());
             t!(index.add_path(Path::new(&file)));
             t!(index.write());