From 19fd5cb6bd08f5d6670c61a128c87d6a09dec506 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 10 Feb 2021 10:58:07 -0800 Subject: [PATCH] Add a schema version to the index. --- crates/cargo-test-support/src/registry.rs | 19 ++++++++++-- crates/crates-io/lib.rs | 2 ++ src/cargo/ops/registry.rs | 1 + src/cargo/sources/registry/index.rs | 37 ++++++++++++++++++++--- src/cargo/sources/registry/mod.rs | 18 +++++++++++ tests/testsuite/registry.rs | 30 ++++++++++++++++++ 6 files changed, 99 insertions(+), 8 deletions(-) diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index 4a5ff4955df..1be91502868 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -327,6 +327,7 @@ pub struct Package { links: Option, rust_version: Option, cargo_features: Vec, + v: Option, } #[derive(Clone)] @@ -401,6 +402,7 @@ impl Package { links: None, rust_version: None, cargo_features: Vec::new(), + v: None, } } @@ -554,6 +556,14 @@ impl Package { self } + /// Sets the index schema version for this package. + /// + /// See [`cargo::sources::registry::RegistryPackage`] for more information. + pub fn schema_version(&mut self, version: u32) -> &mut Package { + self.v = Some(version); + self + } + /// Creates the package and place it in the registry. /// /// This does not actually use Cargo's publishing system, but instead @@ -599,7 +609,7 @@ impl Package { } else { serde_json::json!(self.name) }; - let line = serde_json::json!({ + let mut json = serde_json::json!({ "name": name, "vers": self.vers, "deps": deps, @@ -607,8 +617,11 @@ impl Package { "features": self.features, "yanked": self.yanked, "links": self.links, - }) - .to_string(); + }); + if let Some(v) = self.v { + json["v"] = serde_json::json!(v); + } + let line = json.to_string(); let file = match self.name.len() { 1 => format!("1/{}", self.name), diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 3f76b4b3451..4ae5069de02 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -56,6 +56,8 @@ pub struct NewCrate { pub repository: Option, pub badges: BTreeMap>, pub links: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub v: Option, } #[derive(Serialize)] diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index afb1adbf9d2..7032ae13090 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -305,6 +305,7 @@ fn transmit( license_file: license_file.clone(), badges: badges.clone(), links: links.clone(), + v: None, }, tarball, ); diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index c88726402f5..2489491c974 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -72,7 +72,8 @@ use crate::sources::registry::{RegistryData, RegistryPackage}; use crate::util::interning::InternedString; use crate::util::paths; use crate::util::{internal, CargoResult, Config, Filesystem, ToSemver}; -use log::info; +use anyhow::bail; +use log::{debug, info}; use semver::{Version, VersionReq}; use std::collections::{HashMap, HashSet}; use std::fs; @@ -233,6 +234,8 @@ enum MaybeIndexSummary { pub struct IndexSummary { pub summary: Summary, pub yanked: bool, + /// Schema version, see [`RegistryPackage`]. + v: u32, } /// A representation of the cache on disk that Cargo maintains of summaries. @@ -305,6 +308,7 @@ impl<'cfg> RegistryIndex<'cfg> { // minimize the amount of work being done here and parse as little as // necessary. let raw_data = &summaries.raw_data; + let max_version = 1; Ok(summaries .versions .iter_mut() @@ -318,6 +322,19 @@ impl<'cfg> RegistryIndex<'cfg> { } }, ) + .filter(move |is| { + if is.v > max_version { + debug!( + "unsupported schema version {} ({} {})", + is.v, + is.summary.name(), + is.summary.version() + ); + false + } else { + true + } + }) .filter(move |is| { is.summary .unstable_gate(namespaced_features, weak_dep_features) @@ -578,7 +595,14 @@ impl Summaries { // actually happens to verify that our cache is indeed fresh and // computes exactly the same value as before. if cfg!(debug_assertions) && cache_contents.is_some() { - assert_eq!(cache_bytes, cache_contents); + if cache_bytes != cache_contents { + panic!( + "original cache contents:\n{:?}\n\ + does not equal new cache contents:\n{:?}\n", + cache_contents.as_ref().map(|s| String::from_utf8_lossy(s)), + cache_bytes.as_ref().map(|s| String::from_utf8_lossy(s)), + ); + } } // Once we have our `cache_bytes` which represents the `Summaries` we're @@ -659,19 +683,19 @@ impl<'a> SummariesCache<'a> { .split_first() .ok_or_else(|| anyhow::format_err!("malformed cache"))?; if *first_byte != CURRENT_CACHE_VERSION { - anyhow::bail!("looks like a different Cargo's cache, bailing out"); + bail!("looks like a different Cargo's cache, bailing out"); } let mut iter = split(rest, 0); if let Some(update) = iter.next() { if update != last_index_update.as_bytes() { - anyhow::bail!( + bail!( "cache out of date: current index ({}) != cache ({})", last_index_update, str::from_utf8(update)?, ) } } else { - anyhow::bail!("malformed file"); + bail!("malformed file"); } let mut ret = SummariesCache::default(); while let Some(version) = iter.next() { @@ -749,7 +773,9 @@ impl IndexSummary { features, yanked, links, + v, } = serde_json::from_slice(line)?; + let v = v.unwrap_or(1); log::trace!("json parsed registry {}/{}", name, vers); let pkgid = PackageId::new(name, &vers, source_id)?; let deps = deps @@ -761,6 +787,7 @@ impl IndexSummary { Ok(IndexSummary { summary, yanked: yanked.unwrap_or(false), + v, }) } } diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 495df40bfce..3142e719b45 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -269,6 +269,24 @@ pub struct RegistryPackage<'a> { /// Added early 2018 (see ), /// can be `None` if published before then. links: Option, + /// The schema version for this entry. + /// + /// If this is None, it defaults to version 1. Entries with unknown + /// versions are ignored. + /// + /// This provides a method to safely introduce changes to index entries + /// and allow older versions of cargo to ignore newer entries it doesn't + /// understand. This is honored as of 1.51, so unfortunately older + /// versions will ignore it, and potentially misinterpret version 1 and + /// newer entries. + /// + /// The intent is that versions older than 1.51 will work with a + /// pre-existing `Cargo.lock`, but they may not correctly process `cargo + /// update` or build a lock from scratch. In that case, cargo may + /// incorrectly select a new package that uses a new index format. A + /// workaround is to downgrade any packages that are incompatible with the + /// `--precise` flag of `cargo update`. + v: Option, } #[test] diff --git a/tests/testsuite/registry.rs b/tests/testsuite/registry.rs index acba4a2c413..441e965744a 100644 --- a/tests/testsuite/registry.rs +++ b/tests/testsuite/registry.rs @@ -2170,3 +2170,33 @@ fn package_lock_inside_package_is_overwritten() { assert_eq!(ok.metadata().unwrap().len(), 2); } + +#[cargo_test] +fn ignores_unknown_index_version() { + // If the version field is not understood, it is ignored. + Package::new("bar", "1.0.0").publish(); + Package::new("bar", "1.0.1").schema_version(9999).publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "foo v0.1.0 [..]\n\ + └── bar v1.0.0\n\ + ", + ) + .run(); +}