Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/cargo-util-schemas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ workspace = true

[dev-dependencies]
snapbox.workspace = true
serde_json.workspace = true

[features]
unstable-schema = ["dep:schemars", "dep:serde_json"]
186 changes: 186 additions & 0 deletions crates/cargo-util-schemas/index.schema.json
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/rust-lang/cargo/pull/4978>),\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 <https://github.com/rust-lang/crates.io/pull/6267>),\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"
]
}
}
}
159 changes: 159 additions & 0 deletions crates/cargo-util-schemas/src/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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)]
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
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<RegistryDependency<'a>>,
/// Set of features defined for the package, i.e., `[features]` table.
#[serde(default)]
pub features: BTreeMap<Cow<'a, str>, Vec<Cow<'a, str>>>,
/// 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<BTreeMap<Cow<'a, str>, Vec<Cow<'a, str>>>>,
/// 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<bool>,
/// Native library name this package links to.
///
/// Added early 2018 (see <https://github.com/rust-lang/cargo/pull/4978>),
/// can be `None` if published before then.
pub links: Option<Cow<'a, str>>,
/// Required version of rust
///
/// Corresponds to `package.rust-version`.
///
/// Added in 2023 (see <https://github.com/rust-lang/crates.io/pull/6267>),
/// can be `None` if published before then or if not set in the manifest.
#[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
pub rust_version: Option<RustVersion>,
/// 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<u32>,
}

/// 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`].
#[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<Cow<'a, str>>,
/// 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<Cow<'a, str>>,
/// The dependency kind. "dev", "build", and "normal".
pub kind: Option<Cow<'a, str>>,
/// The URL of the index of the registry where this dependency is from.
/// `None` if it is from the same index.
pub registry: Option<Cow<'a, str>>,
/// The original name if the dependency is renamed.
pub package: Option<Cow<'a, str>>,
/// 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<bool>,
/// The artifacts to build from this dependency.
pub artifact: Option<Vec<Cow<'a, str>>>,
/// The target for bindep.
pub bindep_target: Option<Cow<'a, str>>,
/// Whether or not this is a library dependency.
#[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();
}

#[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());
}
1 change: 1 addition & 0 deletions crates/cargo-util-schemas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/cargo_package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down
Loading