Add dependency registry to cargo metadata.

This adds the `registry` field for dependencies for alternate registries in
`cargo metadata`.
This commit is contained in:
Eric Huss 2018-12-29 20:46:15 -08:00
parent 15e3b5a3c8
commit 3d84d0ad77
15 changed files with 765 additions and 341 deletions

View file

@ -19,6 +19,7 @@ path = "src/cargo/lib.rs"
[dependencies]
atty = "0.2"
byteorder = "1.2"
bytesize = "1.0"
crates-io = { path = "src/crates-io", version = "0.22" }
crossbeam-utils = "0.6"
@ -57,6 +58,7 @@ tempfile = "3.0"
termcolor = "1.0"
toml = "0.4.2"
url = "1.1"
url_serde = "0.2.0"
clap = "2.31.2"
unicode-width = "0.1.5"
openssl = { version = '0.10.11', optional = true }

View file

@ -7,6 +7,7 @@ use semver::ReqParseError;
use semver::VersionReq;
use serde::ser;
use serde::Serialize;
use url::Url;
use crate::core::interning::InternedString;
use crate::core::{PackageId, SourceId, Summary};
@ -25,6 +26,12 @@ pub struct Dependency {
struct Inner {
name: InternedString,
source_id: SourceId,
/// Source ID for the registry as specified in the manifest.
///
/// This will be None if it is not specified (crates.io dependency).
/// This is different from `source_id` for example when both a `path` and
/// `registry` is specified. Or in the case of a crates.io dependency,
/// `source_id` will be crates.io and this will be None.
registry_id: Option<SourceId>,
req: VersionReq,
specified_req: bool,
@ -59,6 +66,10 @@ struct SerializedDependency<'a> {
uses_default_features: bool,
features: &'a [InternedString],
target: Option<&'a Platform>,
/// The registry URL this dependency is from.
/// If None, then it comes from the default registry (crates.io).
#[serde(with = "url_serde")]
registry: Option<Url>,
}
impl ser::Serialize for Dependency {
@ -76,6 +87,7 @@ impl ser::Serialize for Dependency {
features: self.features(),
target: self.platform(),
rename: self.explicit_name_in_toml().map(|s| s.as_str()),
registry: self.registry_id().map(|sid| sid.url().clone()),
}
.serialize(s)
}

View file

@ -40,6 +40,8 @@ struct SourceIdInner {
// e.g. the exact git revision of the specified branch for a Git Source
precise: Option<String>,
/// Name of the registry source for alternative registries
/// WARNING: This is not always set for alt-registries when the name is
/// not known.
name: Option<String>,
}
@ -247,6 +249,8 @@ impl SourceId {
}
/// Is this source from an alternative registry
/// DEPRECATED: This is not correct if the registry name is not known
/// (for example when loaded from an index).
pub fn is_alt_registry(self) -> bool {
self.is_registry() && self.inner.name.is_some()
}

View file

@ -159,8 +159,10 @@ fn transmit(
// registry in the dependency.
let dep_registry_id = match dep.registry_id() {
Some(id) => id,
None => failure::bail!("dependency missing registry ID"),
None => SourceId::crates_io(config)?,
};
// In the index and Web API, None means "from the same registry"
// whereas in Cargo.toml, it means "from crates.io".
let dep_registry = if dep_registry_id != registry_id {
Some(dep_registry_id.url().to_string())
} else {

View file

@ -299,7 +299,7 @@ impl<'a> RegistryDependency<'a> {
package,
} = self;
let id = if let Some(registry) = registry {
let id = if let Some(registry) = &registry {
SourceId::for_registry(&registry.to_url()?)?
} else {
default
@ -328,6 +328,12 @@ impl<'a> RegistryDependency<'a> {
// out here.
features.retain(|s| !s.is_empty());
// In index, "registry" is null if it is from the same index.
// In Cargo.toml, "registry" is None if it is from the default
if !id.is_default_registry() {
dep.set_registry_id(id);
}
dep.set_optional(optional)
.set_default_features(default_features)
.set_features(features)
@ -486,22 +492,7 @@ impl<'cfg> RegistrySource<'cfg> {
MaybePackage::Ready(pkg) => pkg,
MaybePackage::Download { .. } => unreachable!(),
};
// Unfortunately the index and the actual Cargo.toml in the index can
// differ due to historical Cargo bugs. To paper over these we trash the
// *summary* loaded from the Cargo.toml we just downloaded with the one
// we loaded from the index.
let summaries = self
.index
.summaries(package.name().as_str(), &mut *self.ops)?;
let summary = summaries
.iter()
.map(|s| &s.0)
.find(|s| s.package_id() == package)
.expect("summary not found");
let mut manifest = pkg.manifest().clone();
manifest.set_summary(summary.clone());
Ok(Package::new(manifest, pkg.manifest_path()))
Ok(pkg)
}
}

View file

@ -1307,14 +1307,6 @@ impl DetailedTomlDependency {
}
}
let registry_id = match self.registry {
Some(ref registry) => {
cx.features.require(Feature::alternative_registries())?;
SourceId::alt_registry(cx.config, registry)?
}
None => SourceId::crates_io(cx.config)?,
};
let new_source_id = match (
self.git.as_ref(),
self.path.as_ref(),
@ -1410,8 +1402,13 @@ impl DetailedTomlDependency {
.unwrap_or(true),
)
.set_optional(self.optional.unwrap_or(false))
.set_platform(cx.platform.clone())
.set_registry_id(registry_id);
.set_platform(cx.platform.clone());
if let Some(registry) = &self.registry {
cx.features.require(Feature::alternative_registries())?;
let registry_id = SourceId::alt_registry(cx.config, registry)?;
dep.set_registry_id(registry_id);
}
if let Some(kind) = kind {
dep.set_kind(kind);
}

View file

@ -61,7 +61,9 @@ The output has the following format:
{
/* The name of the dependency. */
"name": "bitflags",
/* The source ID of the dependency. */
/* The source ID of the dependency. May be null, see
description for the package source.
*/
"source": "registry+https://github.com/rust-lang/crates.io-index",
/* The version requirement for the dependency.
Dependencies without a version requirement have a value of "*".
@ -84,7 +86,12 @@ The output has the following format:
/* The target platform for the dependency.
null if not a target dependency.
*/
"target": "cfg(windows)"
"target": "cfg(windows)",
/* A string of the URL of the registry this dependency is from.
If not specified or null, the dependency is from the default
registry (crates.io).
*/
"registry": null
}
],
/* Array of Cargo targets. */

View file

@ -1,4 +1,5 @@
use crate::support::registry::{self, alt_api_path, Package};
use crate::support::publish::validate_alt_upload;
use crate::support::registry::{self, Package};
use crate::support::{basic_manifest, git, paths, project};
use std::fs::{self, File};
use std::io::Write;
@ -107,7 +108,7 @@ fn depend_on_alt_registry_depends_on_same_registry_no_index() {
Package::new("baz", "0.0.1").alternative(true).publish();
Package::new("bar", "0.0.1")
.dep("baz", "0.0.1")
.registry_dep("baz", "0.0.1")
.alternative(true)
.publish();
@ -152,7 +153,7 @@ fn depend_on_alt_registry_depends_on_same_registry() {
Package::new("baz", "0.0.1").alternative(true).publish();
Package::new("bar", "0.0.1")
.registry_dep("baz", "0.0.1", registry::alt_registry().as_str())
.registry_dep("baz", "0.0.1")
.alternative(true)
.publish();
@ -197,7 +198,7 @@ fn depend_on_alt_registry_depends_on_crates_io() {
Package::new("baz", "0.0.1").publish();
Package::new("bar", "0.0.1")
.registry_dep("baz", "0.0.1", registry::registry().as_str())
.dep("baz", "0.0.1")
.alternative(true)
.publish();
@ -210,7 +211,7 @@ fn depend_on_alt_registry_depends_on_crates_io() {
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1 (registry `[ROOT][..]`)
[DOWNLOADED] bar v0.0.1 (registry `[ROOT][..]`)
[COMPILING] baz v0.0.1 (registry `[ROOT][..]`)
[COMPILING] baz v0.0.1
[COMPILING] bar v0.0.1 (registry `[ROOT][..]`)
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
@ -345,6 +346,40 @@ fn publish_with_registry_dependency() {
p.cargo("publish --registry alternative -Zunstable-options")
.masquerade_as_nightly_cargo()
.run();
validate_alt_upload(
r#"{
"authors": [],
"badges": {},
"categories": [],
"deps": [
{
"default_features": true,
"features": [],
"kind": "normal",
"name": "bar",
"optional": false,
"target": null,
"version_req": "^0.0.1"
}
],
"description": null,
"documentation": null,
"features": {},
"homepage": null,
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"name": "foo",
"readme": null,
"readme_file": null,
"repository": null,
"vers": "0.0.1"
}"#,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
);
}
#[test]
@ -440,8 +475,29 @@ fn publish_to_alt_registry() {
.masquerade_as_nightly_cargo()
.run();
// Ensure that the crate is uploaded
assert!(alt_api_path().join("api/v1/crates/new").exists());
validate_alt_upload(
r#"{
"authors": [],
"badges": {},
"categories": [],
"deps": [],
"description": null,
"documentation": null,
"features": {},
"homepage": null,
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"name": "foo",
"readme": null,
"readme_file": null,
"repository": null,
"vers": "0.0.1"
}"#,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
);
}
#[test]
@ -476,6 +532,41 @@ fn publish_with_crates_io_dep() {
p.cargo("publish --registry alternative -Zunstable-options")
.masquerade_as_nightly_cargo()
.run();
validate_alt_upload(
r#"{
"authors": ["me"],
"badges": {},
"categories": [],
"deps": [
{
"default_features": true,
"features": [],
"kind": "normal",
"name": "bar",
"optional": false,
"registry": "https://github.com/rust-lang/crates.io-index",
"target": null,
"version_req": "^0.0.1"
}
],
"description": "foo",
"documentation": null,
"features": {},
"homepage": null,
"keywords": [],
"license": "MIT",
"license_file": null,
"links": null,
"name": "foo",
"readme": null,
"readme_file": null,
"repository": null,
"vers": "0.0.1"
}"#,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
);
}
#[test]
@ -696,3 +787,279 @@ fn no_api() {
.with_status(101)
.run();
}
#[test]
fn alt_reg_metadata() {
// Check for "registry" entries in `cargo metadata` with alternative registries.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["alternative-registries"]
[package]
name = "foo"
version = "0.0.1"
[dependencies]
altdep = { version = "0.0.1", registry = "alternative" }
iodep = { version = "0.0.1" }
"#,
)
.file("src/lib.rs", "")
.build();
Package::new("bar", "0.0.1").publish();
Package::new("altdep", "0.0.1")
.dep("bar", "0.0.1")
.alternative(true)
.publish();
Package::new("altdep2", "0.0.1").alternative(true).publish();
Package::new("iodep", "0.0.1")
.registry_dep("altdep2", "0.0.1")
.publish();
// The important thing to check here is the "registry" value in `deps`.
// They should be:
// foo -> altdep: alternative-registry
// foo -> iodep: null (because it is in crates.io)
// altdep -> bar: null (because it is in crates.io)
// iodep -> altdep2: alternative-registry
p.cargo("metadata --format-version=1 --no-deps")
.masquerade_as_nightly_cargo()
.with_json(
r#"
{
"packages": [
{
"name": "foo",
"version": "0.0.1",
"id": "foo 0.0.1 (path+file:[..]/foo)",
"license": null,
"license_file": null,
"description": null,
"source": null,
"dependencies": [
{
"name": "altdep",
"source": "registry+file:[..]/alternative-registry",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": "file:[..]/alternative-registry"
},
{
"name": "iodep",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": null
}
],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/foo/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
}
],
"workspace_members": [
"foo 0.0.1 (path+file:[..]/foo)"
],
"resolve": null,
"target_directory": "[..]/foo/target",
"version": 1,
"workspace_root": "[..]/foo"
}"#,
)
.run();
// --no-deps uses a different code path, make sure both work.
p.cargo("metadata --format-version=1")
.masquerade_as_nightly_cargo()
.with_json(
r#"
{
"packages": [
{
"name": "altdep2",
"version": "0.0.1",
"id": "altdep2 0.0.1 (registry+file:[..]/alternative-registry)",
"license": null,
"license_file": null,
"description": null,
"source": "registry+file:[..]/alternative-registry",
"dependencies": [],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/altdep2-0.0.1/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
},
{
"name": "altdep",
"version": "0.0.1",
"id": "altdep 0.0.1 (registry+file:[..]/alternative-registry)",
"license": null,
"license_file": null,
"description": null,
"source": "registry+file:[..]/alternative-registry",
"dependencies": [
{
"name": "bar",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": null
}
],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/altdep-0.0.1/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
},
{
"name": "foo",
"version": "0.0.1",
"id": "foo 0.0.1 (path+file:[..]/foo)",
"license": null,
"license_file": null,
"description": null,
"source": null,
"dependencies": [
{
"name": "altdep",
"source": "registry+file:[..]/alternative-registry",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": "file:[..]/alternative-registry"
},
{
"name": "iodep",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": null
}
],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/foo/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
},
{
"name": "iodep",
"version": "0.0.1",
"id": "iodep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"license": null,
"license_file": null,
"description": null,
"source": "registry+https://github.com/rust-lang/crates.io-index",
"dependencies": [
{
"name": "altdep2",
"source": "registry+file:[..]/alternative-registry",
"req": "^0.0.1",
"kind": null,
"rename": null,
"optional": false,
"uses_default_features": true,
"features": [],
"target": null,
"registry": "file:[..]/alternative-registry"
}
],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/iodep-0.0.1/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
},
{
"name": "bar",
"version": "0.0.1",
"id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"license": null,
"license_file": null,
"description": null,
"source": "registry+https://github.com/rust-lang/crates.io-index",
"dependencies": [],
"targets": "{...}",
"features": {},
"manifest_path": "[..]/bar-0.0.1/Cargo.toml",
"metadata": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
}
],
"workspace_members": [
"foo 0.0.1 (path+file:[..]/foo)"
],
"resolve": "{...}",
"target_directory": "[..]/foo/target",
"version": 1,
"workspace_root": "[..]/foo"
}"#,
)
.run();
}

View file

@ -1,10 +1,6 @@
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use crate::support::{cross_compile, project, publish};
use flate2::read::GzDecoder;
use tar::Archive;
#[test]
fn simple_cross_package() {
@ -54,17 +50,12 @@ fn simple_cross_package() {
// Check that the tarball contains the files
let f = File::open(&p.root().join("target/package/foo-0.0.0.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let entries = ar.entries().unwrap();
let entry_paths = entries
.map(|entry| entry.unwrap().path().unwrap().into_owned())
.collect::<Vec<PathBuf>>();
assert!(entry_paths.contains(&PathBuf::from("foo-0.0.0/Cargo.toml")));
assert!(entry_paths.contains(&PathBuf::from("foo-0.0.0/Cargo.toml.orig")));
assert!(entry_paths.contains(&PathBuf::from("foo-0.0.0/src/main.rs")));
publish::validate_crate_contents(
f,
"foo-0.0.0.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
&[],
);
}
#[test]

View file

@ -321,6 +321,7 @@ fn cargo_metadata_with_deps_and_version() {
"kind": null,
"name": "bar",
"optional": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
@ -332,6 +333,7 @@ fn cargo_metadata_with_deps_and_version() {
"kind": "dev",
"name": "foobar",
"optional": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
@ -410,6 +412,7 @@ fn cargo_metadata_with_deps_and_version() {
"kind": null,
"name": "baz",
"optional": false,
"registry": null,
"rename": null,
"req": "^0.0.1",
"source": "registry+https://github.com/rust-lang/crates.io-index",
@ -1417,6 +1420,7 @@ fn rename_dependency() {
"name": "bar",
"optional": false,
"rename": null,
"registry": null,
"req": "^0.1.0",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
@ -1428,6 +1432,7 @@ fn rename_dependency() {
"name": "bar",
"optional": false,
"rename": "baz",
"registry": null,
"req": "^0.2.0",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,

View file

@ -1,14 +1,15 @@
use std;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::path::Path;
use crate::support::registry::Package;
use crate::support::{basic_manifest, git, is_nightly, path2url, paths, project, registry};
use crate::support::{
basic_manifest, git, is_nightly, path2url, paths, project, publish::validate_crate_contents,
registry,
};
use crate::support::{cargo_process, sleep_ms};
use flate2::read::GzDecoder;
use git2;
use tar::Archive;
#[test]
fn simple() {
@ -53,22 +54,12 @@ src/main.rs
p.cargo("package").with_stdout("").run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for f in ar.entries().unwrap() {
let f = f.unwrap();
let fname = f.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
f.header().path()
)
}
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
&[],
);
}
#[test]
@ -168,29 +159,25 @@ See http://doc.crates.io/manifest.html#package-metadata for more info.
.run();
let f = File::open(&repo.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let mut entry = ar
.entries()
.unwrap()
.map(|f| f.unwrap())
.find(|e| e.path().unwrap().ends_with(".cargo_vcs_info.json"))
.unwrap();
let mut contents = String::new();
entry.read_to_string(&mut contents).unwrap();
assert_eq!(
&contents[..],
&*format!(
r#"{{
let vcs_contents = format!(
r#"{{
"git": {{
"sha1": "{}"
}}
}}
"#,
repo.revparse_head()
)
repo.revparse_head()
);
validate_crate_contents(
f,
"foo-0.0.1.crate",
&[
"Cargo.toml",
"Cargo.toml.orig",
"src/main.rs",
".cargo_vcs_info.json",
],
&[(".cargo_vcs_info.json", &vcs_contents)],
);
println!("package sub-repo");
@ -602,22 +589,12 @@ src[..]main.rs
p.cargo("package").with_stdout("").run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for f in ar.entries().unwrap() {
let f = f.unwrap();
let fname = f.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
f.header().path()
)
}
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
&[],
);
}
#[cfg(unix)] // windows doesn't allow these characters in filenames
@ -681,15 +658,12 @@ See [..]
// Check that the tarball contains the added file
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let entries = ar.entries().unwrap();
let entry_paths = entries
.map(|entry| entry.unwrap().path().unwrap().into_owned())
.collect::<Vec<PathBuf>>();
assert!(entry_paths.contains(&PathBuf::from("foo-0.0.1/src/foo.rs")));
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs", "src/foo.rs"],
&[],
);
}
#[test]
@ -827,24 +801,8 @@ fn generated_manifest() {
.run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let mut entry = ar
.entries()
.unwrap()
.map(|f| f.unwrap())
.find(|e| e.path().unwrap().ends_with("Cargo.toml"))
.unwrap();
let mut contents = String::new();
entry.read_to_string(&mut contents).unwrap();
// BTreeMap makes the order of dependencies in the generated file deterministic
// by sorting alphabetically
assert_eq!(
&contents[..],
&*format!(
r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
let rewritten_toml = format!(
r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
@ -881,8 +839,14 @@ registry-index = "{}"
[dependencies.ghi]
version = "1.0"
"#,
registry::alt_registry()
)
registry::alt_registry()
);
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
&[("Cargo.toml", &rewritten_toml)],
);
}
@ -923,21 +887,7 @@ fn ignore_workspace_specifier() {
.run();
let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let mut entry = ar
.entries()
.unwrap()
.map(|f| f.unwrap())
.find(|e| e.path().unwrap().ends_with("Cargo.toml"))
.unwrap();
let mut contents = String::new();
entry.read_to_string(&mut contents).unwrap();
assert_eq!(
&contents[..],
r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
let rewritten_toml = r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
@ -953,7 +903,12 @@ fn ignore_workspace_specifier() {
name = "bar"
version = "0.1.0"
authors = []
"#
"#;
validate_crate_contents(
f,
"bar-0.1.0.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
&[("Cargo.toml", &rewritten_toml)],
);
}
@ -1123,23 +1078,12 @@ src/main.rs
.run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for f in ar.entries().unwrap() {
let f = f.unwrap();
let fname = f.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/Cargo.lock"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
f.header().path()
)
}
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
&[],
);
}
#[test]
@ -1202,15 +1146,12 @@ fn no_lock_file_with_library() {
p.cargo("package").masquerade_as_nightly_cargo().run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for f in ar.entries().unwrap() {
let f = f.unwrap();
let fname = f.header().path().unwrap();
assert!(!fname.ends_with("Cargo.lock"));
}
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
&[],
);
}
#[test]
@ -1246,15 +1187,12 @@ fn lock_file_and_workspace() {
.run();
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let mut rdr = GzDecoder::new(f);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
assert!(ar.entries().unwrap().into_iter().any(|f| {
let f = f.unwrap();
let fname = f.header().path().unwrap();
fname.ends_with("Cargo.lock")
}));
validate_crate_contents(
f,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs", "Cargo.lock"],
&[],
);
}
#[test]

View file

@ -1,12 +1,72 @@
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::SeekFrom;
use crate::support::git::repo;
use crate::support::paths;
use crate::support::{basic_manifest, project, publish};
use flate2::read::GzDecoder;
use tar::Archive;
const CLEAN_FOO_JSON: &str = r#"
{
"authors": [],
"badges": {},
"categories": [],
"deps": [],
"description": "foo",
"documentation": "foo",
"features": {},
"homepage": "foo",
"keywords": [],
"license": "MIT",
"license_file": null,
"links": null,
"name": "foo",
"readme": null,
"readme_file": null,
"repository": "foo",
"vers": "0.0.1"
}
"#;
fn validate_upload_foo() {
publish::validate_upload(
r#"
{
"authors": [],
"badges": {},
"categories": [],
"deps": [],
"description": "foo",
"documentation": null,
"features": {},
"homepage": null,
"keywords": [],
"license": "MIT",
"license_file": null,
"links": null,
"name": "foo",
"readme": null,
"readme_file": null,
"repository": null,
"vers": "0.0.1"
}
"#,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
);
}
fn validate_upload_foo_clean() {
publish::validate_upload(
CLEAN_FOO_JSON,
"foo-0.0.1.crate",
&[
"Cargo.toml",
"Cargo.toml.orig",
"src/main.rs",
".cargo_vcs_info.json",
],
);
}
#[test]
fn simple() {
@ -41,37 +101,7 @@ See [..]
))
.run();
let mut f = File::open(&publish::upload_path().join("api/v1/crates/new")).unwrap();
// Skip the metadata payload and the size of the tarball
let mut sz = [0; 4];
assert_eq!(f.read(&mut sz).unwrap(), 4);
let sz = (u32::from(sz[0]) << 0)
| (u32::from(sz[1]) << 8)
| (u32::from(sz[2]) << 16)
| (u32::from(sz[3]) << 24);
f.seek(SeekFrom::Current(i64::from(sz) + 4)).unwrap();
// Verify the tarball
let mut rdr = GzDecoder::new(f);
assert_eq!(
rdr.header().unwrap().filename().unwrap(),
b"foo-0.0.1.crate"
);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for file in ar.entries().unwrap() {
let file = file.unwrap();
let fname = file.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
file.header().path()
);
}
validate_upload_foo();
}
#[test]
@ -116,37 +146,7 @@ See [..]
))
.run();
let mut f = File::open(&publish::upload_path().join("api/v1/crates/new")).unwrap();
// Skip the metadata payload and the size of the tarball
let mut sz = [0; 4];
assert_eq!(f.read(&mut sz).unwrap(), 4);
let sz = (u32::from(sz[0]) << 0)
| (u32::from(sz[1]) << 8)
| (u32::from(sz[2]) << 16)
| (u32::from(sz[3]) << 24);
f.seek(SeekFrom::Current(i64::from(sz) + 4)).unwrap();
// Verify the tarball
let mut rdr = GzDecoder::new(f);
assert_eq!(
rdr.header().unwrap().filename().unwrap(),
b"foo-0.0.1.crate"
);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for file in ar.entries().unwrap() {
let file = file.unwrap();
let fname = file.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
file.header().path()
);
}
validate_upload_foo();
}
// TODO: Deprecated
@ -193,37 +193,7 @@ See [..]
))
.run();
let mut f = File::open(&publish::upload_path().join("api/v1/crates/new")).unwrap();
// Skip the metadata payload and the size of the tarball
let mut sz = [0; 4];
assert_eq!(f.read(&mut sz).unwrap(), 4);
let sz = (u32::from(sz[0]) << 0)
| (u32::from(sz[1]) << 8)
| (u32::from(sz[2]) << 16)
| (u32::from(sz[3]) << 24);
f.seek(SeekFrom::Current(i64::from(sz) + 4)).unwrap();
// Verify the tarball
let mut rdr = GzDecoder::new(f);
assert_eq!(
rdr.header().unwrap().filename().unwrap(),
b"foo-0.0.1.crate"
);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for file in ar.entries().unwrap() {
let file = file.unwrap();
let fname = file.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
file.header().path()
);
}
validate_upload_foo();
}
// TODO: Deprecated
@ -272,37 +242,7 @@ See [..]
))
.run();
let mut f = File::open(&publish::upload_path().join("api/v1/crates/new")).unwrap();
// Skip the metadata payload and the size of the tarball
let mut sz = [0; 4];
assert_eq!(f.read(&mut sz).unwrap(), 4);
let sz = (u32::from(sz[0]) << 0)
| (u32::from(sz[1]) << 8)
| (u32::from(sz[2]) << 16)
| (u32::from(sz[3]) << 24);
f.seek(SeekFrom::Current(i64::from(sz) + 4)).unwrap();
// Verify the tarball
let mut rdr = GzDecoder::new(f);
assert_eq!(
rdr.header().unwrap().filename().unwrap(),
b"foo-0.0.1.crate"
);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
for file in ar.entries().unwrap() {
let file = file.unwrap();
let fname = file.header().path_bytes();
let fname = &*fname;
assert!(
fname == b"foo-0.0.1/Cargo.toml"
|| fname == b"foo-0.0.1/Cargo.toml.orig"
|| fname == b"foo-0.0.1/src/main.rs",
"unexpected filename: {:?}",
file.header().path()
);
}
validate_upload_foo();
}
#[test]
@ -479,6 +419,8 @@ fn publish_clean() {
p.cargo("publish --index")
.arg(publish::registry().to_string())
.run();
validate_upload_foo_clean();
}
#[test]
@ -510,6 +452,8 @@ fn publish_in_sub_repo() {
.arg("--index")
.arg(publish::registry().to_string())
.run();
validate_upload_foo_clean();
}
#[test]
@ -540,6 +484,18 @@ fn publish_when_ignored() {
p.cargo("publish --index")
.arg(publish::registry().to_string())
.run();
publish::validate_upload(
CLEAN_FOO_JSON,
"foo-0.0.1.crate",
&[
"Cargo.toml",
"Cargo.toml.orig",
"src/main.rs",
".gitignore",
".cargo_vcs_info.json",
],
);
}
#[test]
@ -570,6 +526,12 @@ fn ignore_when_crate_ignored() {
.arg("--index")
.arg(publish::registry().to_string())
.run();
publish::validate_upload(
CLEAN_FOO_JSON,
"foo-0.0.1.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/main.rs", "baz"],
);
}
#[test]
@ -778,6 +740,7 @@ fn publish_allowed_registry() {
description = "foo"
documentation = "foo"
homepage = "foo"
repository = "foo"
publish = ["alternative"]
"#,
)
@ -787,6 +750,8 @@ fn publish_allowed_registry() {
p.cargo("publish --registry alternative -Zunstable-options")
.masquerade_as_nightly_cargo()
.run();
validate_upload_foo_clean();
}
#[test]

View file

@ -1192,16 +1192,7 @@ impl Execs {
Ok(actual) => actual,
};
match find_mismatch(expected, &actual) {
Some((expected_part, actual_part)) => Err(format!(
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
serde_json::to_string_pretty(expected).unwrap(),
serde_json::to_string_pretty(&actual).unwrap(),
serde_json::to_string_pretty(expected_part).unwrap(),
serde_json::to_string_pretty(actual_part).unwrap(),
)),
None => Ok(()),
}
find_json_mismatch(expected, &actual)
}
fn diff_lines<'a>(
@ -1292,12 +1283,28 @@ fn lines_match_works() {
assert!(!lines_match("b", "cb"));
}
// Compares JSON object for approximate equality.
// You can use `[..]` wildcard in strings (useful for OS dependent things such
// as paths). You can use a `"{...}"` string literal as a wildcard for
// arbitrary nested JSON (useful for parts of object emitted by other programs
// (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison.
fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
/// Compares JSON object for approximate equality.
/// You can use `[..]` wildcard in strings (useful for OS dependent things such
/// as paths). You can use a `"{...}"` string literal as a wildcard for
/// arbitrary nested JSON (useful for parts of object emitted by other programs
/// (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison.
pub fn find_json_mismatch(expected: &Value, actual: &Value) -> Result<(), String> {
match find_json_mismatch_r(expected, &actual) {
Some((expected_part, actual_part)) => Err(format!(
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
serde_json::to_string_pretty(expected).unwrap(),
serde_json::to_string_pretty(&actual).unwrap(),
serde_json::to_string_pretty(expected_part).unwrap(),
serde_json::to_string_pretty(actual_part).unwrap(),
)),
None => Ok(()),
}
}
fn find_json_mismatch_r<'a>(
expected: &'a Value,
actual: &'a Value,
) -> Option<(&'a Value, &'a Value)> {
use serde_json::Value::*;
match (expected, actual) {
(&Number(ref l), &Number(ref r)) if l == r => None,
@ -1312,7 +1319,7 @@ fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Valu
let mut r = r.iter().collect::<Vec<_>>();
l.retain(
|l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
|l| match r.iter().position(|r| find_json_mismatch_r(l, r).is_none()) {
Some(i) => {
r.remove(i);
false
@ -1337,7 +1344,7 @@ fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Valu
l.values()
.zip(r.values())
.filter_map(|(l, r)| find_mismatch(l, r))
.filter_map(|(l, r)| find_json_mismatch_r(l, r))
.nth(0)
}
(&Null, &Null) => None,

View file

@ -1,10 +1,15 @@
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::PathBuf;
use std::io::{prelude::*, SeekFrom};
use std::path::{Path, PathBuf};
use crate::support::git::{repo, Repository};
use crate::support::paths;
use crate::support::registry::alt_api_path;
use crate::support::{find_json_mismatch, paths};
use byteorder::{LittleEndian, ReadBytesExt};
use flate2::read::GzDecoder;
use tar::Archive;
use url::Url;
pub fn setup() -> Repository {
@ -61,3 +66,116 @@ pub fn upload_path() -> PathBuf {
fn upload() -> Url {
Url::from_file_path(&*upload_path()).ok().unwrap()
}
/// Check the result of a crate publish.
pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) {
let new_path = upload_path().join("api/v1/crates/new");
_validate_upload(
&new_path,
expected_json,
expected_crate_name,
expected_files,
);
}
/// Check the result of a crate publish to an alternative registry.
pub fn validate_alt_upload(
expected_json: &str,
expected_crate_name: &str,
expected_files: &[&str],
) {
let new_path = alt_api_path().join("api/v1/crates/new");
_validate_upload(
&new_path,
expected_json,
expected_crate_name,
expected_files,
);
}
fn _validate_upload(
new_path: &Path,
expected_json: &str,
expected_crate_name: &str,
expected_files: &[&str],
) {
let mut f = File::open(new_path).unwrap();
// 32-bit little-endian integer of length of JSON data.
let json_sz = f.read_u32::<LittleEndian>().expect("read json length");
let mut json_bytes = vec![0; json_sz as usize];
f.read_exact(&mut json_bytes).expect("read JSON data");
let actual_json = serde_json::from_slice(&json_bytes).expect("uploaded JSON should be valid");
let expected_json = serde_json::from_str(expected_json).expect("expected JSON does not parse");
find_json_mismatch(&expected_json, &actual_json)
.expect("uploaded JSON did not match expected JSON");
// 32-bit little-endian integer of length of crate file.
let crate_sz = f.read_u32::<LittleEndian>().expect("read crate length");
let mut krate_bytes = vec![0; crate_sz as usize];
f.read_exact(&mut krate_bytes).expect("read crate data");
// Check at end.
let current = f.seek(SeekFrom::Current(0)).unwrap();
assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), current);
// Verify the tarball
validate_crate_contents(&krate_bytes[..], expected_crate_name, expected_files, &[]);
}
/// Check the contents of a `.crate` file.
///
/// - `expected_crate_name` should be something like `foo-0.0.1.crate`.
/// - `expected_files` should be a complete list of files in the crate
/// (relative to expected_crate_name).
/// - `expected_contents` should be a list of `(file_name, contents)` tuples
/// to validate the contents of the given file. Only the listed files will
/// be checked (others will be ignored).
pub fn validate_crate_contents(
reader: impl Read,
expected_crate_name: &str,
expected_files: &[&str],
expected_contents: &[(&str, &str)],
) {
let mut rdr = GzDecoder::new(reader);
assert_eq!(
rdr.header().unwrap().filename().unwrap(),
expected_crate_name.as_bytes()
);
let mut contents = Vec::new();
rdr.read_to_end(&mut contents).unwrap();
let mut ar = Archive::new(&contents[..]);
let files: HashMap<PathBuf, String> = ar
.entries()
.unwrap()
.map(|entry| {
let mut entry = entry.unwrap();
let name = entry.path().unwrap().into_owned();
let mut contents = String::new();
entry.read_to_string(&mut contents).unwrap();
(name, contents)
})
.collect();
assert!(expected_crate_name.ends_with(".crate"));
let base_crate_name = Path::new(&expected_crate_name[..expected_crate_name.len() - 6]);
let actual_files: HashSet<PathBuf> = files.keys().cloned().collect();
let expected_files: HashSet<PathBuf> = expected_files
.iter()
.map(|name| base_crate_name.join(name))
.collect();
let missing: Vec<&PathBuf> = expected_files.difference(&actual_files).collect();
let extra: Vec<&PathBuf> = actual_files.difference(&expected_files).collect();
if !missing.is_empty() || !extra.is_empty() {
panic!(
"uploaded archive does not match.\nMissing: {:?}\nExtra: {:?}\n",
missing, extra
);
}
if !expected_contents.is_empty() {
for (e_file_name, e_file_contents) in expected_contents {
let full_e_name = base_crate_name.join(e_file_name);
let actual_contents = files
.get(&full_e_name)
.unwrap_or_else(|| panic!("file `{}` missing in archive", e_file_name));
assert_eq!(actual_contents, e_file_contents);
}
}
}

View file

@ -3,6 +3,7 @@ use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use cargo::sources::CRATES_IO_INDEX;
use cargo::util::Sha256;
use flate2::write::GzEncoder;
use flate2::Compression;
@ -277,10 +278,9 @@ impl Package {
self.add_dep(Dependency::new(name, vers).target(target))
}
/// Add a dependency to an alternative registry.
/// The given registry should be a URI to the alternative registry.
pub fn registry_dep(&mut self, name: &str, vers: &str, registry: &str) -> &mut Package {
self.add_dep(Dependency::new(name, vers).registry(registry))
/// Add a dependency to the alternative registry.
pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
self.add_dep(Dependency::new(name, vers).registry("alternative"))
}
/// Add a dev-dependency. Example:
@ -333,6 +333,16 @@ impl Package {
.deps
.iter()
.map(|dep| {
// In the index, the `registry` is null if it is from the same registry.
// In Cargo.toml, it is None if it is from crates.io.
let registry_url =
match (self.alternative, dep.registry.as_ref().map(|s| s.as_ref())) {
(false, None) => None,
(false, Some("alternative")) => Some(alt_registry().to_string()),
(true, None) => Some(CRATES_IO_INDEX.to_string()),
(true, Some("alternative")) => None,
_ => panic!("registry_dep currently only supports `alternative`"),
};
serde_json::json!({
"name": dep.name,
"req": dep.vers,
@ -341,7 +351,7 @@ impl Package {
"target": dep.target,
"optional": dep.optional,
"kind": dep.kind,
"registry": dep.registry,
"registry": registry_url,
"package": dep.package,
})
})
@ -412,14 +422,19 @@ impl Package {
}
fn make_archive(&self) {
let features = if self.deps.iter().any(|dep| dep.registry.is_some()) {
"cargo-features = [\"alternative-registries\"]\n"
} else {
""
};
let mut manifest = format!(
r#"
[package]
{}[package]
name = "{}"
version = "{}"
authors = []
"#,
self.name, self.vers
features, self.name, self.vers
);
for dep in self.deps.iter() {
let target = match dep.target {
@ -438,6 +453,9 @@ impl Package {
"#,
target, kind, dep.name, dep.vers
));
if let Some(registry) = &dep.registry {
manifest.push_str(&format!("registry = \"{}\"", registry));
}
}
let dst = self.archive_dst();