From 3d84d0ad7708ab88384cbfe4e6a33184d1bac7f7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 29 Dec 2018 20:46:15 -0800 Subject: [PATCH 1/2] Add dependency `registry` to `cargo metadata`. This adds the `registry` field for dependencies for alternate registries in `cargo metadata`. --- Cargo.toml | 2 + src/cargo/core/dependency.rs | 12 + src/cargo/core/source/source_id.rs | 4 + src/cargo/ops/registry.rs | 4 +- src/cargo/sources/registry/mod.rs | 25 +- src/cargo/util/toml/mod.rs | 17 +- src/doc/man/cargo-metadata.adoc | 11 +- tests/testsuite/alt_registry.rs | 381 +++++++++++++++++++++++++++- tests/testsuite/cross_publish.rs | 21 +- tests/testsuite/metadata.rs | 5 + tests/testsuite/package.rs | 206 ++++++--------- tests/testsuite/publish.rs | 219 +++++++--------- tests/testsuite/support/mod.rs | 43 ++-- tests/testsuite/support/publish.rs | 124 ++++++++- tests/testsuite/support/registry.rs | 32 ++- 15 files changed, 765 insertions(+), 341 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32d2007618a..cd7d36898b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index 262c3753874..0dc9d5c394d 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -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, 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, } 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) } diff --git a/src/cargo/core/source/source_id.rs b/src/cargo/core/source/source_id.rs index eee27ed1fa1..19e27de51d5 100644 --- a/src/cargo/core/source/source_id.rs +++ b/src/cargo/core/source/source_id.rs @@ -40,6 +40,8 @@ struct SourceIdInner { // e.g. the exact git revision of the specified branch for a Git Source precise: Option, /// 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, } @@ -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() } diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index dbb95fc5168..04e482fe96d 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -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 { diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 2771a1821a1..e2f756aeec0 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -299,7 +299,7 @@ impl<'a> RegistryDependency<'a> { package, } = self; - let id = if let Some(registry) = registry { + let id = if let Some(registry) = ®istry { SourceId::for_registry(®istry.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) } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 53e0d7ad8b3..602465918b0 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -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); } diff --git a/src/doc/man/cargo-metadata.adoc b/src/doc/man/cargo-metadata.adoc index 35c8f133f3d..35e6926a834 100644 --- a/src/doc/man/cargo-metadata.adoc +++ b/src/doc/man/cargo-metadata.adoc @@ -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. */ diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index 2bdc1b57e96..41ac080b640 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -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(); +} diff --git a/tests/testsuite/cross_publish.rs b/tests/testsuite/cross_publish.rs index 624d323c99c..eb3664675b3 100644 --- a/tests/testsuite/cross_publish.rs +++ b/tests/testsuite/cross_publish.rs @@ -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::>(); - 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] diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index 2b8e1d1abea..aeaad126fa3 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -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, diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 34a707c271f..f14eff20b6d 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -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::>(); - 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] diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 57ddd3428ba..0ce1434a709 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -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] diff --git a/tests/testsuite/support/mod.rs b/tests/testsuite/support/mod.rs index 16537f61577..47e681460fb 100644 --- a/tests/testsuite/support/mod.rs +++ b/tests/testsuite/support/mod.rs @@ -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::>(); 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, diff --git a/tests/testsuite/support/publish.rs b/tests/testsuite/support/publish.rs index afb2e109456..e5a13b002cf 100644 --- a/tests/testsuite/support/publish.rs +++ b/tests/testsuite/support/publish.rs @@ -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::().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::().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 = 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 = files.keys().cloned().collect(); + let expected_files: HashSet = 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); + } + } +} diff --git a/tests/testsuite/support/registry.rs b/tests/testsuite/support/registry.rs index e7cedb7dc9b..82439c15c91 100644 --- a/tests/testsuite/support/registry.rs +++ b/tests/testsuite/support/registry.rs @@ -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(); From 64042d845d874da632848e51b098e6b8a717473d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sun, 30 Dec 2018 16:07:58 -0800 Subject: [PATCH 2/2] Fix registry-index bug. --- src/cargo/util/toml/mod.rs | 12 +++ tests/testsuite/alt_registry.rs | 152 ++++++++++++++++++++++++++++ tests/testsuite/support/registry.rs | 3 +- 3 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 602465918b0..e52aba1ec7e 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -201,6 +201,12 @@ impl<'de> de::Deserialize<'de> for TomlDependency { pub struct DetailedTomlDependency { version: Option, registry: Option, + /// The URL of the `registry` field. + /// This is an internal implementation detail. When Cargo creates a + /// package, it replaces `registry` with `registry-index` so that the + /// manifest contains the correct URL. All users won't have the same + /// registry names configured, so Cargo can't rely on just the name for + /// crates published by other users. registry_index: Option, path: Option, git: Option, @@ -1408,6 +1414,12 @@ impl DetailedTomlDependency { let registry_id = SourceId::alt_registry(cx.config, registry)?; dep.set_registry_id(registry_id); } + if let Some(registry_index) = &self.registry_index { + cx.features.require(Feature::alternative_registries())?; + let url = registry_index.to_url()?; + let registry_id = SourceId::for_registry(&url)?; + dep.set_registry_id(registry_id); + } if let Some(kind) = kind { dep.set_kind(kind); diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index 41ac080b640..f99ab93adc6 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -1063,3 +1063,155 @@ fn alt_reg_metadata() { ) .run(); } + +#[test] +fn unknown_registry() { + // A known registry refers to an unknown registry. + // foo -> bar(crates.io) -> baz(alt) + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + Package::new("baz", "0.0.1") + .alternative(true) + .publish(); + Package::new("bar", "0.0.1") + .registry_dep("baz", "0.0.1") + .publish(); + + // Remove "alternative" from config. + let cfg_path = paths::home().join(".cargo/config"); + let mut config = fs::read_to_string(&cfg_path).unwrap(); + let start = config.find("[registries.alternative]").unwrap(); + config.insert(start, '#'); + let start_index = &config[start..].find("index =").unwrap(); + config.insert(start + start_index, '#'); + fs::write(&cfg_path, config).unwrap(); + + p.cargo("build") + .masquerade_as_nightly_cargo() + .run(); + + // Important parts: + // foo -> bar registry = null + // bar -> baz registry = alternate + p.cargo("metadata --format-version=1") + .masquerade_as_nightly_cargo() + .with_json(r#" + { + "packages": [ + { + "name": "baz", + "version": "0.0.1", + "id": "baz 0.0.1 (registry+file://[..]/alternative-registry)", + "license": null, + "license_file": null, + "description": null, + "source": "registry+file://[..]/alternative-registry", + "dependencies": [], + "targets": "{...}", + "features": {}, + "manifest_path": "[..]", + "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": "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": "[..]/foo/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": [ + { + "name": "baz", + "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": "[..]", + "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(); +} diff --git a/tests/testsuite/support/registry.rs b/tests/testsuite/support/registry.rs index 82439c15c91..76b641c5384 100644 --- a/tests/testsuite/support/registry.rs +++ b/tests/testsuite/support/registry.rs @@ -454,7 +454,8 @@ impl Package { target, kind, dep.name, dep.vers )); if let Some(registry) = &dep.registry { - manifest.push_str(&format!("registry = \"{}\"", registry)); + assert_eq!(registry, "alternative"); + manifest.push_str(&format!("registry-index = \"{}\"", alt_registry())); } }