use ops;
use sources::git;
use sources::{PathSource, GitSource, RegistrySource, CRATES_IO};
+use sources::DirectorySource;
use util::{human, Config, CargoResult, ToUrl};
/// A Source finds and downloads remote packages based on names and
/// 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> {
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
(**self).fingerprint(pkg)
}
+
+ fn verify(&self, pkg: &PackageId) -> CargoResult<()> {
+ (**self).verify(pkg)
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Registry,
/// represents a local filesystem-based registry
LocalRegistry,
+ /// represents a directory-based registry
+ Directory,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
format!("local-registry+{}", url)
}
+ SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+ format!("directory+{}", url)
+ }
}
}
Ok(SourceId::new(Kind::LocalRegistry, url))
}
+ pub fn for_directory(path: &Path) -> CargoResult<SourceId> {
+ let url = try!(path.to_url());
+ Ok(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
};
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"),
+ };
+ Box::new(DirectorySource::new(&path, self, config))
+ }
}
}
SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
write!(f, "registry {}", url)
}
+ SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+ write!(f, "dir {}", url)
+ }
}
}
}
let compare = compare_old_fingerprint(&loc, &*fingerprint);
log_compare(unit, &compare);
+ if compare.is_err() {
+ let source_id = unit.pkg.package_id().source_id();
+ let sources = cx.packages.sources();
+ let source = try!(sources.get(source_id).chain_error(|| {
+ internal("missing package source")
+ }));
+ try!(source.verify(unit.pkg.package_id()));
+ }
+
let root = cx.out_dir(unit);
let mut missing_outputs = false;
if unit.profile.doc {
path.push(s);
srcs.push(try!(SourceId::for_local_registry(&path)));
}
+ if let Some(val) = table.get("directory") {
+ let (s, path) = try!(val.string(&format!("source.{}.directory",
+ name)));
+ let mut path = path.to_path_buf();
+ path.pop();
+ path.pop();
+ path.push(s);
+ srcs.push(try!(SourceId::for_directory(&path)));
+ }
let mut srcs = srcs.into_iter();
let src = try!(srcs.next().chain_error(|| {
--- /dev/null
+use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+use std::fs::File;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+
+use rustc_serialize::hex::ToHex;
+use rustc_serialize::json;
+
+use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
+use sources::PathSource;
+use util::{CargoResult, human, ChainError, Config, Sha256};
+use util::paths;
+
+pub struct DirectorySource<'cfg> {
+ id: SourceId,
+ root: PathBuf,
+ packages: HashMap<PackageId, (Package, Checksum)>,
+ config: &'cfg Config,
+}
+
+#[derive(RustcDecodable)]
+struct Checksum {
+ package: String,
+ files: HashMap<String, String>,
+}
+
+impl<'cfg> DirectorySource<'cfg> {
+ pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
+ -> DirectorySource<'cfg> {
+ DirectorySource {
+ id: id.clone(),
+ root: path.to_path_buf(),
+ config: config,
+ packages: HashMap::new(),
+ }
+ }
+}
+
+impl<'cfg> Debug for DirectorySource<'cfg> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "DirectorySource {{ root: {:?} }}", self.root)
+ }
+}
+
+impl<'cfg> Registry for DirectorySource<'cfg> {
+ fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
+ let packages = self.packages.values().map(|p| &p.0);
+ let matches = packages.filter(|pkg| dep.matches(pkg.summary()));
+ let summaries = matches.map(|pkg| pkg.summary().clone());
+ Ok(summaries.collect())
+ }
+
+ fn supports_checksums(&self) -> bool {
+ true
+ }
+}
+
+impl<'cfg> Source for DirectorySource<'cfg> {
+ fn update(&mut self) -> CargoResult<()> {
+ self.packages.clear();
+ let entries = try!(self.root.read_dir().chain_error(|| {
+ human(format!("failed to read root of directory source: {}",
+ self.root.display()))
+ }));
+
+ for entry in entries {
+ let entry = try!(entry);
+ let path = entry.path();
+ let mut src = PathSource::new(&path,
+ &self.id,
+ self.config);
+ try!(src.update());
+ let pkg = try!(src.root_package());
+
+ let cksum_file = path.join(".cargo-checksum.json");
+ let cksum = try!(paths::read(&path.join(cksum_file)).chain_error(|| {
+ human(format!("failed to load checksum `.cargo-checksum.json` \
+ of {} v{}",
+ pkg.package_id().name(),
+ pkg.package_id().version()))
+
+ }));
+ let cksum: Checksum = try!(json::decode(&cksum).chain_error(|| {
+ human(format!("failed to decode `.cargo-checksum.json` of \
+ {} v{}",
+ pkg.package_id().name(),
+ pkg.package_id().version()))
+ }));
+
+ let mut manifest = pkg.manifest().clone();
+ let summary = manifest.summary().clone();
+ manifest.set_summary(summary.set_checksum(cksum.package.clone()));
+ let pkg = Package::new(manifest, pkg.manifest_path());
+ self.packages.insert(pkg.package_id().clone(), (pkg, cksum));
+ }
+
+ Ok(())
+ }
+
+ fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
+ self.packages.get(id).map(|p| &p.0).cloned().chain_error(|| {
+ human(format!("failed to find package with id: {}", id))
+ })
+ }
+
+ fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
+ Ok(pkg.package_id().version().to_string())
+ }
+
+ fn verify(&self, id: &PackageId) -> CargoResult<()> {
+ let (pkg, cksum) = match self.packages.get(id) {
+ Some(&(ref pkg, ref cksum)) => (pkg, cksum),
+ None => bail!("failed to find entry for `{}` in directory source",
+ id),
+ };
+
+ let mut buf = [0; 16 * 1024];
+ for (file, cksum) in cksum.files.iter() {
+ let mut h = Sha256::new();
+ let file = pkg.root().join(file);
+
+ try!((|| -> CargoResult<()> {
+ let mut f = try!(File::open(&file));
+ loop {
+ match try!(f.read(&mut buf)) {
+ 0 => return Ok(()),
+ n => h.update(&buf[..n]),
+ }
+ }
+ }).chain_error(|| {
+ human(format!("failed to calculate checksum of: {}",
+ file.display()))
+ }));
+
+ let actual = h.finish().to_hex();
+ if &*actual != cksum {
+ bail!("\
+ the listed checksum of `{}` has changed:\n\
+ expected: {}\n\
+ actual: {}\n\
+ \n\
+ directory sources are not intended to be edited, if \
+ modifications are required then it is recommended \
+ that [replace] is used with a forked copy of the \
+ source\
+ ", file.display(), cksum, actual);
+ }
+ }
+
+ Ok(())
+ }
+}
-pub use self::path::PathSource;
+pub use self::config::SourceConfigMap;
+pub use self::directory::DirectorySource;
pub use self::git::GitSource;
+pub use self::path::PathSource;
pub use self::registry::{RegistrySource, CRATES_IO};
pub use self::replaced::ReplacedSource;
-pub use self::config::SourceConfigMap;
-pub mod path;
+pub mod config;
+pub mod directory;
pub mod git;
+pub mod path;
pub mod registry;
-pub mod config;
pub mod replaced;
}
fn fingerprint(&self, id: &Package) -> CargoResult<String> {
- self.inner.fingerprint(id)
+ self.inner.fingerprint(&id)
+ }
+
+ fn verify(&self, id: &PackageId) -> CargoResult<()> {
+ let id = id.with_source_id(&self.replace_with);
+ self.inner.verify(&id)
}
}
self
}
- pub fn publish(&self) {
+ pub fn publish(&self) -> String {
self.make_archive();
// Figure out what we're going to write into the index
"Another commit", &tree,
&[&parent]));
}
+
+ return cksum
}
fn make_archive(&self) {
}
}
-fn cksum(s: &[u8]) -> String {
+pub fn cksum(s: &[u8]) -> String {
let mut sha = Sha256::new();
sha.update(s);
sha.finish().to_hex()
[UPDATING] registry [..]
[DOWNLOADING] [..]
[DOWNLOADING] [..]
-[COMPILING] foo v0.1.0 ([..])
-[COMPILING] bar v0.1.0 ([..])
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0
[COMPILING] a v0.0.1 ([..])
[FINISHED] debug [unoptimized + debuginfo] target(s) in [..]
"));
--- /dev/null
+#[macro_use]
+extern crate cargotest;
+extern crate hamcrest;
+extern crate rustc_serialize;
+
+use std::collections::HashMap;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::str;
+
+use rustc_serialize::json;
+
+use cargotest::support::{project, execs, ProjectBuilder};
+use cargotest::support::paths;
+use cargotest::support::registry::{Package, cksum};
+use hamcrest::assert_that;
+
+fn setup() {
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(t!(File::create(root.join(".cargo/config"))).write_all(br#"
+ [source.crates-io]
+ registry = 'https://wut'
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ directory = 'index'
+ "#));
+}
+
+struct VendorPackage {
+ p: Option<ProjectBuilder>,
+ cksum: Checksum,
+}
+
+#[derive(RustcEncodable)]
+struct Checksum {
+ package: String,
+ files: HashMap<String, String>,
+}
+
+impl VendorPackage {
+ fn new(name: &str) -> VendorPackage {
+ VendorPackage {
+ p: Some(project(&format!("index/{}", name))),
+ cksum: Checksum {
+ package: String::new(),
+ files: HashMap::new(),
+ },
+ }
+ }
+
+ fn file(&mut self, name: &str, contents: &str) -> &mut VendorPackage {
+ self.p = Some(self.p.take().unwrap().file(name, contents));
+ self.cksum.files.insert(name.to_string(), cksum(contents.as_bytes()));
+ self
+ }
+
+ fn build(&mut self) {
+ let p = self.p.take().unwrap();
+ let json = json::encode(&self.cksum).unwrap();
+ let p = p.file(".cargo-checksum.json", &json);
+ p.build();
+ }
+}
+
+#[test]
+fn simple() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate foo;
+
+ pub fn bar() {
+ foo::foo();
+ }
+ "#);
+ p.build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn not_there() {
+ setup();
+
+ project("index").build();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate foo;
+
+ pub fn bar() {
+ foo::foo();
+ }
+ "#);
+ p.build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(101).with_stderr("\
+error: no matching package named `foo` found (required by `bar`)
+location searched: [..]
+version required: ^0.1.0
+"));
+}
+
+#[test]
+fn multiple() {
+ setup();
+
+ VendorPackage::new("foo-0.1.0")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(".cargo-checksum", "")
+ .build();
+
+ VendorPackage::new("foo-0.2.0")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(".cargo-checksum", "")
+ .build();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate foo;
+
+ pub fn bar() {
+ foo::foo();
+ }
+ "#);
+ p.build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn crates_io_then_directory() {
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate foo;
+
+ pub fn bar() {
+ foo::foo();
+ }
+ "#);
+ p.build();
+
+ let cksum = Package::new("foo", "0.1.0")
+ .file("src/lib.rs", "pub fn foo() -> u32 { 0 }")
+ .publish();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `[..]`
+[DOWNLOADING] foo v0.1.0 ([..])
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+
+ setup();
+
+ let mut v = VendorPackage::new("foo");
+ v.file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#);
+ v.file("src/lib.rs", "pub fn foo() -> u32 { 1 }");
+ v.cksum.package = cksum;
+ v.build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn crates_io_then_bad_checksum() {
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", "");
+ p.build();
+
+ Package::new("foo", "0.1.0").publish();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0));
+ setup();
+
+ VendorPackage::new("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(101).with_stderr("\
+error: checksum for `foo v0.1.0` changed between lock files
+
+this could be indicative of a few possible errors:
+
+ * the lock file is corrupt
+ * a replacement source in use (e.g. a mirror) returned a different checksum
+ * the source itself may be corrupt in one way or another
+
+unable to verify that `foo v0.1.0` was the same as before in any situation
+
+"));
+}
+
+#[test]
+fn bad_file_checksum() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+
+ let mut f = t!(File::create(paths::root().join("index/foo/src/lib.rs")));
+ t!(f.write_all(b"fn foo() -> u32 { 0 }"));
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", "");
+ p.build();
+
+ assert_that(p.cargo("build"),
+ execs().with_status(101).with_stderr("\
+error: the listed checksum of `[..]lib.rs` has changed:
+expected: [..]
+actual: [..]
+
+directory sources are not intended to be edited, if modifications are \
+required then it is recommended that [replace] is used with a forked copy of \
+the source
+"));
+}
extern crate {};
fn main() {{}}
", name))
- .publish()
+ .publish();
}
#[test]
assert_that(cargo_process("install").arg("foo"),
execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `[..]`
-[DOWNLOADING] foo v0.0.2 (registry file://[..])
-[COMPILING] foo v0.0.2 (registry file://[..])
[DOWNLOADING] foo v0.0.2 (registry [..])
[COMPILING] foo v0.0.2
[FINISHED] release [optimized] target(s) in [..]
[UNPACKING] foo v0.0.1 ([..])
[COMPILING] foo v0.0.1
[COMPILING] bar v0.0.1 ({dir})
+[FINISHED] [..]
",
dir = p.url())));
- assert_that(p.cargo("build"), execs().with_status(0).with_stderr(""));
+ assert_that(p.cargo("build"), execs().with_status(0).with_stderr("\
+[FINISHED] [..]
+"));
assert_that(p.cargo("test"), execs().with_status(0));
}
[UNPACKING] foo v0.1.0 ([..])
[COMPILING] foo v0.1.0
[COMPILING] bar v0.0.1 ({dir})
+[FINISHED] [..]
",
dir = p.url())));
[COMPILING] [..]
[COMPILING] [..]
[COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
",
dir = p.url())));
}
[COMPILING] foo v0.0.1
[COMPILING] bar v0.1.0
[COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
",
dir = p.url())));
}
[COMPILING] foo v0.0.1
[COMPILING] bar v0.1.0
[COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
",
dir = p.url())));
}
assert_that(p.cargo_process("publish").arg("--dry-run")
.arg("--host").arg(registry().to_string()),
execs().with_status(0).with_stderr(&format!("\
-[UPDATING] registry `{reg}`
+[UPDATING] registry `[..]`
[WARNING] manifest has no documentation, [..]
[PACKAGING] foo v0.0.1 ({dir})
[VERIFYING] foo v0.0.1 ({dir})
[UPLOADING] foo v0.0.1 ({dir})
[WARNING] aborting upload due to dry run
",
- dir = p.url(),
- reg = registry())));
+ dir = p.url())));
// Ensure the API request wasn't actually made
assert!(!upload_path().join("api/v1/crates/new").exists());