From a7fcef21feb4d835d1fee83b3f93b4aef86d5545 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Wed, 23 Jul 2025 22:09:13 +0200 Subject: [PATCH 1/2] refactor: move `IndexPackage` and `RegistryDependency` to `cargo-util-schemas` for better modularity Signed-off-by: 0xPoe --- crates/cargo-util-schemas/Cargo.toml | 1 + crates/cargo-util-schemas/src/index.rs | 145 +++++++++++ crates/cargo-util-schemas/src/lib.rs | 1 + src/cargo/ops/cargo_package/mod.rs | 2 +- src/cargo/sources/registry/index/mod.rs | 327 +++++++----------------- src/cargo/sources/registry/mod.rs | 5 +- 6 files changed, 243 insertions(+), 238 deletions(-) create mode 100644 crates/cargo-util-schemas/src/index.rs diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 2682b65e1de..da4699f98cf 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -25,6 +25,7 @@ workspace = true [dev-dependencies] snapbox.workspace = true +serde_json.workspace = true [features] unstable-schema = ["dep:schemars", "dep:serde_json"] diff --git a/crates/cargo-util-schemas/src/index.rs b/crates/cargo-util-schemas/src/index.rs new file mode 100644 index 00000000000..489d110b23f --- /dev/null +++ b/crates/cargo-util-schemas/src/index.rs @@ -0,0 +1,145 @@ +use crate::manifest::RustVersion; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, collections::BTreeMap}; + +/// A single line in the index representing a single version of a package. +#[derive(Deserialize, Serialize)] +pub struct IndexPackage<'a> { + /// Name of the package. + #[serde(borrow)] + pub name: Cow<'a, str>, + /// The version of this dependency. + pub vers: Version, + /// All kinds of direct dependencies of the package, including dev and + /// build dependencies. + #[serde(borrow)] + pub deps: Vec>, + /// Set of features defined for the package, i.e., `[features]` table. + #[serde(default)] + pub features: BTreeMap, Vec>>, + /// This field contains features with new, extended syntax. Specifically, + /// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`). + /// + /// This is separated from `features` because versions older than 1.19 + /// will fail to load due to not being able to parse the new syntax, even + /// with a `Cargo.lock` file. + pub features2: Option, Vec>>>, + /// Checksum for verifying the integrity of the corresponding downloaded package. + pub cksum: String, + /// If `true`, Cargo will skip this version when resolving. + /// + /// This was added in 2014. Everything in the crates.io index has this set + /// now, so this probably doesn't need to be an option anymore. + pub yanked: Option, + /// Native library name this package links to. + /// + /// Added early 2018 (see ), + /// can be `None` if published before then. + pub links: Option>, + /// Required version of rust + /// + /// Corresponds to `package.rust-version`. + /// + /// Added in 2023 (see ), + /// can be `None` if published before then or if not set in the manifest. + pub rust_version: Option, + /// The schema version for this entry. + /// + /// If this is None, it defaults to version `1`. Entries with unknown + /// versions are ignored. + /// + /// Version `2` schema adds the `features2` field. + /// + /// Version `3` schema adds `artifact`, `bindep_targes`, and `lib` for + /// artifact dependencies support. + /// + /// 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 2 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 schema. A + /// workaround is to downgrade any packages that are incompatible with the + /// `--precise` flag of `cargo update`. + pub v: Option, +} + +/// A dependency as encoded in the [`IndexPackage`] index JSON. +#[derive(Deserialize, Serialize, Clone)] +pub struct RegistryDependency<'a> { + /// Name of the dependency. If the dependency is renamed, the original + /// would be stored in [`RegistryDependency::package`]. + #[serde(borrow)] + pub name: Cow<'a, str>, + /// The SemVer requirement for this dependency. + #[serde(borrow)] + pub req: Cow<'a, str>, + /// Set of features enabled for this dependency. + #[serde(default)] + pub features: Vec>, + /// Whether or not this is an optional dependency. + #[serde(default)] + pub optional: bool, + /// Whether or not default features are enabled. + #[serde(default = "default_true")] + pub default_features: bool, + /// The target platform for this dependency. + pub target: Option>, + /// The dependency kind. "dev", "build", and "normal". + pub kind: Option>, + // The URL of the index of the registry where this dependency is from. + // `None` if it is from the same index. + pub registry: Option>, + /// The original name if the dependency is renamed. + pub package: Option>, + /// Whether or not this is a public dependency. Unstable. See [RFC 1977]. + /// + /// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html + pub public: Option, + pub artifact: Option>>, + pub bindep_target: Option>, + #[serde(default)] + pub lib: bool, +} + +fn default_true() -> bool { + true +} + +#[test] +fn escaped_char_in_index_json_blob() { + let _: IndexPackage<'_> = serde_json::from_str( + r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{}}"#, + ) + .unwrap(); + let _: IndexPackage<'_> = serde_json::from_str( + r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{"test":["k","q"]},"links":"a-sys"}"# + ).unwrap(); + + // Now we add escaped cher all the places they can go + // these are not valid, but it should error later than json parsing + let _: IndexPackage<'_> = serde_json::from_str( + r#"{ + "name":"This name has a escaped cher in it \n\t\" ", + "vers":"0.0.1", + "deps":[{ + "name": " \n\t\" ", + "req": " \n\t\" ", + "features": [" \n\t\" "], + "optional": true, + "default_features": true, + "target": " \n\t\" ", + "kind": " \n\t\" ", + "registry": " \n\t\" " + }], + "cksum":"bae3", + "features":{"test \n\t\" ":["k \n\t\" ","q \n\t\" "]}, + "links":" \n\t\" "}"#, + ) + .unwrap(); +} diff --git a/crates/cargo-util-schemas/src/lib.rs b/crates/cargo-util-schemas/src/lib.rs index a850c894a8b..9324480f14d 100644 --- a/crates/cargo-util-schemas/src/lib.rs +++ b/crates/cargo-util-schemas/src/lib.rs @@ -9,6 +9,7 @@ //! > ecosystem. This crate follows semver compatibility for its APIs. pub mod core; +pub mod index; pub mod manifest; pub mod messages; #[cfg(feature = "unstable-schema")] diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 3aa0babbd1c..3cc656dc009 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -19,7 +19,6 @@ use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::ops::lockfile::LOCKFILE_NAME; use crate::ops::registry::{RegistryOrIndex, infer_registry}; use crate::sources::path::PathEntry; -use crate::sources::registry::index::{IndexPackage, RegistryDependency}; use crate::sources::{CRATES_IO_REGISTRY, PathSource}; use crate::util::FileLock; use crate::util::Filesystem; @@ -35,6 +34,7 @@ use crate::util::toml::prepare_for_publish; use crate::{drop_println, ops}; use anyhow::{Context as _, bail}; use cargo_util::paths; +use cargo_util_schemas::index::{IndexPackage, RegistryDependency}; use cargo_util_schemas::messages; use flate2::{Compression, GzBuilder}; use tar::{Builder, EntryType, Header, HeaderMode}; diff --git a/src/cargo/sources/registry/index/mod.rs b/src/cargo/sources/registry/index/mod.rs index f09fa778076..f2cd5ae39a5 100644 --- a/src/cargo/sources/registry/index/mod.rs +++ b/src/cargo/sources/registry/index/mod.rs @@ -28,6 +28,7 @@ use crate::util::IntoUrl; use crate::util::interning::InternedString; use crate::util::{CargoResult, Filesystem, GlobalContext, OptVersionReq, internal}; use cargo_util::registry::make_dep_path; +use cargo_util_schemas::index::{IndexPackage, RegistryDependency}; use cargo_util_schemas::manifest::RustVersion; use semver::Version; use serde::{Deserialize, Serialize}; @@ -194,97 +195,29 @@ impl IndexSummary { } } -/// A single line in the index representing a single version of a package. -#[derive(Deserialize, Serialize)] -pub struct IndexPackage<'a> { - /// Name of the package. - #[serde(borrow)] - pub name: Cow<'a, str>, - /// The version of this dependency. - pub vers: Version, - /// All kinds of direct dependencies of the package, including dev and - /// build dependencies. - #[serde(borrow)] - pub deps: Vec>, - /// Set of features defined for the package, i.e., `[features]` table. - #[serde(default)] - pub features: BTreeMap, Vec>>, - /// This field contains features with new, extended syntax. Specifically, - /// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`). - /// - /// This is separated from `features` because versions older than 1.19 - /// will fail to load due to not being able to parse the new syntax, even - /// with a `Cargo.lock` file. - pub features2: Option, Vec>>>, - /// Checksum for verifying the integrity of the corresponding downloaded package. - pub cksum: String, - /// If `true`, Cargo will skip this version when resolving. - /// - /// This was added in 2014. Everything in the crates.io index has this set - /// now, so this probably doesn't need to be an option anymore. - pub yanked: Option, - /// Native library name this package links to. - /// - /// Added early 2018 (see ), - /// can be `None` if published before then. - pub links: Option>, - /// Required version of rust - /// - /// Corresponds to `package.rust-version`. - /// - /// Added in 2023 (see ), - /// can be `None` if published before then or if not set in the manifest. - pub rust_version: Option, - /// The schema version for this entry. - /// - /// If this is None, it defaults to version `1`. Entries with unknown - /// versions are ignored. - /// - /// Version `2` schema adds the `features2` field. - /// - /// Version `3` schema adds `artifact`, `bindep_targes`, and `lib` for - /// artifact dependencies support. - /// - /// 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 2 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 schema. A - /// workaround is to downgrade any packages that are incompatible with the - /// `--precise` flag of `cargo update`. - pub v: Option, -} - -impl IndexPackage<'_> { - fn to_summary(&self, source_id: SourceId) -> CargoResult { - // ****CAUTION**** Please be extremely careful with returning errors, see - // `IndexSummary::parse` for details - let pkgid = PackageId::new(self.name.as_ref().into(), self.vers.clone(), source_id); - let deps = self - .deps - .iter() - .map(|dep| dep.clone().into_dep(source_id)) - .collect::>>()?; - let mut features = self.features.clone(); - if let Some(features2) = self.features2.clone() { - for (name, values) in features2 { - features.entry(name).or_default().extend(values); - } +fn index_package_to_summary(pkg: &IndexPackage<'_>, source_id: SourceId) -> CargoResult { + // ****CAUTION**** Please be extremely careful with returning errors, see + // `IndexSummary::parse` for details + let pkgid = PackageId::new(pkg.name.as_ref().into(), pkg.vers.clone(), source_id); + let deps = pkg + .deps + .iter() + .map(|dep| registry_dependency_into_dep(dep.clone(), source_id)) + .collect::>>()?; + let mut features = pkg.features.clone(); + if let Some(features2) = pkg.features2.clone() { + for (name, values) in features2 { + features.entry(name).or_default().extend(values); } - let features = features - .into_iter() - .map(|(name, values)| (name.into(), values.into_iter().map(|v| v.into()).collect())) - .collect::>(); - let links: Option = self.links.as_ref().map(|l| l.as_ref().into()); - let mut summary = Summary::new(pkgid, deps, &features, links, self.rust_version.clone())?; - summary.set_checksum(self.cksum.clone()); - Ok(summary) } + let features = features + .into_iter() + .map(|(name, values)| (name.into(), values.into_iter().map(|v| v.into()).collect())) + .collect::>(); + let links: Option = pkg.links.as_ref().map(|l| l.as_ref().into()); + let mut summary = Summary::new(pkgid, deps, &features, links, pkg.rust_version.clone())?; + summary.set_checksum(pkg.cksum.clone()); + Ok(summary) } #[derive(Deserialize, Serialize)] @@ -303,48 +236,6 @@ struct IndexPackageV { v: Option, } -/// A dependency as encoded in the [`IndexPackage`] index JSON. -#[derive(Deserialize, Serialize, Clone)] -pub struct RegistryDependency<'a> { - /// Name of the dependency. If the dependency is renamed, the original - /// would be stored in [`RegistryDependency::package`]. - #[serde(borrow)] - pub name: Cow<'a, str>, - /// The SemVer requirement for this dependency. - #[serde(borrow)] - pub req: Cow<'a, str>, - /// Set of features enabled for this dependency. - #[serde(default)] - pub features: Vec>, - /// Whether or not this is an optional dependency. - #[serde(default)] - pub optional: bool, - /// Whether or not default features are enabled. - #[serde(default = "default_true")] - pub default_features: bool, - /// The target platform for this dependency. - pub target: Option>, - /// The dependency kind. "dev", "build", and "normal". - pub kind: Option>, - // The URL of the index of the registry where this dependency is from. - // `None` if it is from the same index. - pub registry: Option>, - /// The original name if the dependency is renamed. - pub package: Option>, - /// Whether or not this is a public dependency. Unstable. See [RFC 1977]. - /// - /// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html - pub public: Option, - pub artifact: Option>>, - pub bindep_target: Option>, - #[serde(default)] - pub lib: bool, -} - -fn default_true() -> bool { - true -} - impl<'gctx> RegistryIndex<'gctx> { /// Creates an empty registry index at `path`. pub fn new( @@ -753,7 +644,7 @@ impl IndexSummary { // values carefully when making changes here. let index_summary = (|| { let index = serde_json::from_slice::>(line)?; - let summary = index.to_summary(source_id)?; + let summary = index_package_to_summary(&index, source_id)?; Ok((index, summary)) })(); let (index, summary, valid) = match index_summary { @@ -784,7 +675,7 @@ impl IndexSummary { yanked: Default::default(), links: Default::default(), }; - let summary = index.to_summary(source_id)?; + let summary = index_package_to_summary(&index, source_id)?; (index, summary, false) } }; @@ -809,77 +700,78 @@ impl IndexSummary { } } -impl<'a> RegistryDependency<'a> { - /// Converts an encoded dependency in the registry to a cargo dependency - pub fn into_dep(self, default: SourceId) -> CargoResult { - let RegistryDependency { - name, - req, - mut features, - optional, - default_features, - target, - kind, - registry, - package, - public, - artifact, - bindep_target, - lib, - } = self; - - let id = if let Some(registry) = ®istry { - SourceId::for_registry(®istry.into_url()?)? - } else { - default - }; - - let interned_name = InternedString::new(package.as_ref().unwrap_or(&name)); - let mut dep = Dependency::parse(interned_name, Some(&req), id)?; - if package.is_some() { - dep.set_explicit_name_in_toml(name); - } - let kind = match kind.as_deref().unwrap_or("") { - "dev" => DepKind::Development, - "build" => DepKind::Build, - _ => DepKind::Normal, - }; - - let platform = match target { - Some(target) => Some(target.parse()?), - None => None, - }; - - // All dependencies are private by default - let public = public.unwrap_or(false); - - // Unfortunately older versions of cargo and/or the registry ended up - // publishing lots of entries where the features array contained the - // empty feature, "", inside. This confuses the resolution process much - // later on and these features aren't actually valid, so filter them all - // 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_crates_io() { - dep.set_registry_id(id); - } +/// Converts an encoded dependency in the registry to a cargo dependency +fn registry_dependency_into_dep( + dep: RegistryDependency<'_>, + default: SourceId, +) -> CargoResult { + let RegistryDependency { + name, + req, + mut features, + optional, + default_features, + target, + kind, + registry, + package, + public, + artifact, + bindep_target, + lib, + } = dep; + + let id = if let Some(registry) = ®istry { + SourceId::for_registry(®istry.into_url()?)? + } else { + default + }; + + let interned_name = InternedString::new(package.as_ref().unwrap_or(&name)); + let mut dep = Dependency::parse(interned_name, Some(&req), id)?; + if package.is_some() { + dep.set_explicit_name_in_toml(name); + } + let kind = match kind.as_deref().unwrap_or("") { + "dev" => DepKind::Development, + "build" => DepKind::Build, + _ => DepKind::Normal, + }; + + let platform = match target { + Some(target) => Some(target.parse()?), + None => None, + }; + + // All dependencies are private by default + let public = public.unwrap_or(false); + + // Unfortunately older versions of cargo and/or the registry ended up + // publishing lots of entries where the features array contained the + // empty feature, "", inside. This confuses the resolution process much + // later on and these features aren't actually valid, so filter them all + // 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_crates_io() { + dep.set_registry_id(id); + } - if let Some(artifacts) = artifact { - let artifact = Artifact::parse(&artifacts, lib, bindep_target.as_deref())?; - dep.set_artifact(artifact); - } + if let Some(artifacts) = artifact { + let artifact = Artifact::parse(&artifacts, lib, bindep_target.as_deref())?; + dep.set_artifact(artifact); + } - dep.set_optional(optional) - .set_default_features(default_features) - .set_features(features) - .set_platform(platform) - .set_kind(kind) - .set_public(public); + dep.set_optional(optional) + .set_default_features(default_features) + .set_features(features) + .set_platform(platform) + .set_kind(kind) + .set_public(public); - Ok(dep) - } + Ok(dep) } /// Like [`slice::split`] but is optimized by [`memchr`]. @@ -907,36 +799,3 @@ fn split(haystack: &[u8], needle: u8) -> impl Iterator { Split { haystack, needle } } - -#[test] -fn escaped_char_in_index_json_blob() { - let _: IndexPackage<'_> = serde_json::from_str( - r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{}}"#, - ) - .unwrap(); - let _: IndexPackage<'_> = serde_json::from_str( - r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{"test":["k","q"]},"links":"a-sys"}"# - ).unwrap(); - - // Now we add escaped cher all the places they can go - // these are not valid, but it should error later than json parsing - let _: IndexPackage<'_> = serde_json::from_str( - r#"{ - "name":"This name has a escaped cher in it \n\t\" ", - "vers":"0.0.1", - "deps":[{ - "name": " \n\t\" ", - "req": " \n\t\" ", - "features": [" \n\t\" "], - "optional": true, - "default_features": true, - "target": " \n\t\" ", - "kind": " \n\t\" ", - "registry": " \n\t\" " - }], - "cksum":"bae3", - "features":{"test \n\t\" ":["k \n\t\" ","q \n\t\" "]}, - "links":" \n\t\" "}"#, - ) - .unwrap(); -} diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 4d351f7861e..69bb2d875c8 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -73,7 +73,7 @@ //! about the format of the registry: //! //! 1. Each crate will have one file corresponding to it. Each version for a -//! crate will just be a line in this file (see [`IndexPackage`] for its +//! crate will just be a line in this file (see [`cargo_util_schemas::index::IndexPackage`] for its //! representation). //! 2. There will be two tiers of directories for crate names, under which //! crates corresponding to those tiers will be located. @@ -125,7 +125,7 @@ //! //! Each file in the index is the history of one crate over time. Each line in //! the file corresponds to one version of a crate, stored in JSON format (see -//! the [`IndexPackage`] structure). +//! the [`cargo_util_schemas::index::IndexPackage`] structure). //! //! As new versions are published, new lines are appended to this file. **The //! only modifications to this file that should happen over time are yanks of a @@ -181,7 +181,6 @@ //! ... //! ``` //! -//! [`IndexPackage`]: index::IndexPackage use std::collections::HashSet; use std::fs; From 8811325b5a0ba500c6e9d8e07202397805ebe36d Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Sun, 27 Jul 2025 14:47:28 +0200 Subject: [PATCH 2/2] feat: add JSON schema support for `IndexPackage` and `RegistryDependency` structs Signed-off-by: 0xPoe --- crates/cargo-util-schemas/index.schema.json | 186 ++++++++++++++++++++ crates/cargo-util-schemas/src/index.rs | 18 +- 2 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 crates/cargo-util-schemas/index.schema.json diff --git a/crates/cargo-util-schemas/index.schema.json b/crates/cargo-util-schemas/index.schema.json new file mode 100644 index 00000000000..6435e3ab6e5 --- /dev/null +++ b/crates/cargo-util-schemas/index.schema.json @@ -0,0 +1,186 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "IndexPackage", + "description": "A single line in the index representing a single version of a package.", + "type": "object", + "properties": { + "name": { + "description": "Name of the package.", + "type": "string" + }, + "vers": { + "description": "The version of this dependency.", + "$ref": "#/$defs/SemVer" + }, + "deps": { + "description": "All kinds of direct dependencies of the package, including dev and\nbuild dependencies.", + "type": "array", + "items": { + "$ref": "#/$defs/RegistryDependency" + } + }, + "features": { + "description": "Set of features defined for the package, i.e., `[features]` table.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": {} + }, + "features2": { + "description": "This field contains features with new, extended syntax. Specifically,\nnamespaced features (`dep:`) and weak dependencies (`pkg?/feat`).\n\nThis is separated from `features` because versions older than 1.19\nwill fail to load due to not being able to parse the new syntax, even\nwith a `Cargo.lock` file.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "cksum": { + "description": "Checksum for verifying the integrity of the corresponding downloaded package.", + "type": "string" + }, + "yanked": { + "description": "If `true`, Cargo will skip this version when resolving.\n\nThis was added in 2014. Everything in the crates.io index has this set\nnow, so this probably doesn't need to be an option anymore.", + "type": [ + "boolean", + "null" + ] + }, + "links": { + "description": "Native library name this package links to.\n\nAdded early 2018 (see ),\ncan be `None` if published before then.", + "type": [ + "string", + "null" + ] + }, + "rust_version": { + "description": "Required version of rust\n\nCorresponds to `package.rust-version`.\n\nAdded in 2023 (see ),\ncan be `None` if published before then or if not set in the manifest.", + "type": [ + "string", + "null" + ] + }, + "v": { + "description": "The schema version for this entry.\n\nIf this is None, it defaults to version `1`. Entries with unknown\nversions are ignored.\n\nVersion `2` schema adds the `features2` field.\n\nVersion `3` schema adds `artifact`, `bindep_targes`, and `lib` for\nartifact dependencies support.\n\nThis provides a method to safely introduce changes to index entries\nand allow older versions of cargo to ignore newer entries it doesn't\nunderstand. This is honored as of 1.51, so unfortunately older\nversions will ignore it, and potentially misinterpret version 2 and\nnewer entries.\n\nThe intent is that versions older than 1.51 will work with a\npre-existing `Cargo.lock`, but they may not correctly process `cargo\nupdate` or build a lock from scratch. In that case, cargo may\nincorrectly select a new package that uses a new index schema. A\nworkaround is to downgrade any packages that are incompatible with the\n`--precise` flag of `cargo update`.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "name", + "vers", + "deps", + "cksum" + ], + "$defs": { + "SemVer": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "RegistryDependency": { + "description": "A dependency as encoded in the [`IndexPackage`] index JSON.", + "type": "object", + "properties": { + "name": { + "description": "Name of the dependency. If the dependency is renamed, the original\nwould be stored in [`RegistryDependency::package`].", + "type": "string" + }, + "req": { + "description": "The SemVer requirement for this dependency.", + "type": "string" + }, + "features": { + "description": "Set of features enabled for this dependency.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "optional": { + "description": "Whether or not this is an optional dependency.", + "type": "boolean", + "default": false + }, + "default_features": { + "description": "Whether or not default features are enabled.", + "type": "boolean", + "default": true + }, + "target": { + "description": "The target platform for this dependency.", + "type": [ + "string", + "null" + ] + }, + "kind": { + "description": "The dependency kind. \"dev\", \"build\", and \"normal\".", + "type": [ + "string", + "null" + ] + }, + "registry": { + "description": "The URL of the index of the registry where this dependency is from.\n`None` if it is from the same index.", + "type": [ + "string", + "null" + ] + }, + "package": { + "description": "The original name if the dependency is renamed.", + "type": [ + "string", + "null" + ] + }, + "public": { + "description": "Whether or not this is a public dependency. Unstable. See [RFC 1977].\n\n[RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html", + "type": [ + "boolean", + "null" + ] + }, + "artifact": { + "description": "The artifacts to build from this dependency.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "bindep_target": { + "description": "The target for bindep.", + "type": [ + "string", + "null" + ] + }, + "lib": { + "description": "Whether or not this is a library dependency.", + "type": "boolean", + "default": false + } + }, + "required": [ + "name", + "req" + ] + } + } +} \ No newline at end of file diff --git a/crates/cargo-util-schemas/src/index.rs b/crates/cargo-util-schemas/src/index.rs index 489d110b23f..1e5fb0938da 100644 --- a/crates/cargo-util-schemas/src/index.rs +++ b/crates/cargo-util-schemas/src/index.rs @@ -5,6 +5,7 @@ use std::{borrow::Cow, collections::BTreeMap}; /// A single line in the index representing a single version of a package. #[derive(Deserialize, Serialize)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct IndexPackage<'a> { /// Name of the package. #[serde(borrow)] @@ -43,6 +44,7 @@ pub struct IndexPackage<'a> { /// /// Added in 2023 (see ), /// can be `None` if published before then or if not set in the manifest. + #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub rust_version: Option, /// The schema version for this entry. /// @@ -71,6 +73,7 @@ pub struct IndexPackage<'a> { /// A dependency as encoded in the [`IndexPackage`] index JSON. #[derive(Deserialize, Serialize, Clone)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct RegistryDependency<'a> { /// Name of the dependency. If the dependency is renamed, the original /// would be stored in [`RegistryDependency::package`]. @@ -92,8 +95,8 @@ pub struct RegistryDependency<'a> { pub target: Option>, /// The dependency kind. "dev", "build", and "normal". pub kind: Option>, - // The URL of the index of the registry where this dependency is from. - // `None` if it is from the same index. + /// The URL of the index of the registry where this dependency is from. + /// `None` if it is from the same index. pub registry: Option>, /// The original name if the dependency is renamed. pub package: Option>, @@ -101,8 +104,11 @@ pub struct RegistryDependency<'a> { /// /// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html pub public: Option, + /// The artifacts to build from this dependency. pub artifact: Option>>, + /// The target for bindep. pub bindep_target: Option>, + /// Whether or not this is a library dependency. #[serde(default)] pub lib: bool, } @@ -143,3 +149,11 @@ fn escaped_char_in_index_json_blob() { ) .unwrap(); } + +#[cfg(feature = "unstable-schema")] +#[test] +fn dump_index_schema() { + let schema = schemars::schema_for!(crate::index::IndexPackage<'_>); + let dump = serde_json::to_string_pretty(&schema).unwrap(); + snapbox::assert_data_eq!(dump, snapbox::file!("../index.schema.json").raw()); +}