diff --git a/crates/cargo-util-schemas/src/lib.rs b/crates/cargo-util-schemas/src/lib.rs
index e3a51284f07..a850c894a8b 100644
--- a/crates/cargo-util-schemas/src/lib.rs
+++ b/crates/cargo-util-schemas/src/lib.rs
@@ -10,6 +10,7 @@
pub mod core;
pub mod manifest;
+pub mod messages;
#[cfg(feature = "unstable-schema")]
pub mod schema;
diff --git a/crates/cargo-util-schemas/src/messages.rs b/crates/cargo-util-schemas/src/messages.rs
new file mode 100644
index 00000000000..45eb0ece4ea
--- /dev/null
+++ b/crates/cargo-util-schemas/src/messages.rs
@@ -0,0 +1,32 @@
+//! Schemas for JSON messages emitted by Cargo.
+
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+
+/// File information of a package archive generated by `cargo package --list`.
+#[derive(Debug, serde::Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct PackageList {
+ /// The Package ID Spec of the package.
+ pub id: crate::core::PackageIdSpec,
+ /// A map of relative paths in the archive to their detailed file information.
+ pub files: BTreeMap,
+}
+
+/// Where the file is from.
+#[derive(Debug, serde::Serialize)]
+#[serde(rename_all = "snake_case", tag = "kind")]
+pub enum PackageFile {
+ /// File being copied from another location.
+ Copy {
+ /// An absolute path to the actual file content
+ path: PathBuf,
+ },
+ /// File being generated during packaging
+ Generate {
+ /// An absolute path to the original file the generated one is based on.
+ /// if any.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ path: Option,
+ },
+}
diff --git a/src/bin/cargo/commands/package.rs b/src/bin/cargo/commands/package.rs
index 58b935d0b9b..dbc61e914a5 100644
--- a/src/bin/cargo/commands/package.rs
+++ b/src/bin/cargo/commands/package.rs
@@ -1,6 +1,8 @@
use crate::command_prelude::*;
-use cargo::ops::{self, PackageOpts};
+use cargo::ops;
+use cargo::ops::PackageMessageFormat;
+use cargo::ops::PackageOpts;
pub fn cli() -> Command {
subcommand("package")
@@ -30,6 +32,13 @@ pub fn cli() -> Command {
"exclude-lockfile",
"Don't include the lock file when packaging",
))
+ .arg(
+ opt("message-format", "Output representation (unstable)")
+ .value_name("FMT")
+ // This currently requires and only works with `--list`.
+ .requires("list")
+ .value_parser(PackageMessageFormat::POSSIBLE_VALUES),
+ )
.arg_silent_suggestion()
.arg_package_spec_no_all(
"Package(s) to assemble",
@@ -75,12 +84,21 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
}
let specs = args.packages_from_flags()?;
+ let fmt = if let Some(fmt) = args._value_of("message-format") {
+ gctx.cli_unstable()
+ .fail_if_stable_opt("--message-format", 11666)?;
+ fmt.parse()?
+ } else {
+ PackageMessageFormat::Human
+ };
+
ops::package(
&ws,
&PackageOpts {
gctx,
verify: !args.flag("no-verify"),
list: args.flag("list"),
+ fmt,
check_metadata: !args.flag("no-metadata"),
allow_dirty: args.flag("allow-dirty"),
include_lockfile: !args.flag("exclude-lockfile"),
diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs
index 973c78679c8..71d43bc0198 100644
--- a/src/cargo/ops/cargo_package/mod.rs
+++ b/src/cargo/ops/cargo_package/mod.rs
@@ -1,4 +1,6 @@
-use std::collections::{BTreeSet, HashMap};
+use std::collections::BTreeMap;
+use std::collections::BTreeSet;
+use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::SeekFrom;
@@ -32,6 +34,7 @@ use crate::util::HumanBytes;
use crate::{drop_println, ops};
use anyhow::{bail, Context as _};
use cargo_util::paths;
+use cargo_util_schemas::messages;
use flate2::{Compression, GzBuilder};
use tar::{Builder, EntryType, Header, HeaderMode};
use tracing::debug;
@@ -40,10 +43,38 @@ use unicase::Ascii as UncasedAscii;
mod vcs;
mod verify;
+/// Message format for `cargo package`.
+///
+/// Currently only affect the output of the `--list` flag.
+#[derive(Debug, Clone)]
+pub enum PackageMessageFormat {
+ Human,
+ Json,
+}
+
+impl PackageMessageFormat {
+ pub const POSSIBLE_VALUES: [&str; 2] = ["human", "json"];
+
+ pub const DEFAULT: &str = "human";
+}
+
+impl std::str::FromStr for PackageMessageFormat {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "human" => Ok(PackageMessageFormat::Human),
+ "json" => Ok(PackageMessageFormat::Json),
+ f => bail!("unknown message format `{f}`"),
+ }
+ }
+}
+
#[derive(Clone)]
pub struct PackageOpts<'gctx> {
pub gctx: &'gctx GlobalContext,
pub list: bool,
+ pub fmt: PackageMessageFormat,
pub check_metadata: bool,
pub allow_dirty: bool,
pub include_lockfile: bool,
@@ -78,9 +109,13 @@ enum FileContents {
enum GeneratedFile {
/// Generates `Cargo.toml` by rewriting the original.
- Manifest,
- /// Generates `Cargo.lock` in some cases (like if there is a binary).
- Lockfile,
+ ///
+ /// Associated path is the original manifest path.
+ Manifest(PathBuf),
+ /// Generates `Cargo.lock`.
+ ///
+ /// Associated path is the path to the original lock file, if existing.
+ Lockfile(Option),
/// Adds a `.cargo_vcs_info.json` file if in a git repo.
VcsInfo(vcs::VcsInfo),
}
@@ -236,8 +271,33 @@ fn do_package<'a>(
let ar_files = prepare_archive(ws, &pkg, &opts)?;
if opts.list {
- for ar_file in &ar_files {
- drop_println!(ws.gctx(), "{}", ar_file.rel_str);
+ match opts.fmt {
+ PackageMessageFormat::Human => {
+ // While this form is called "human",
+ // it keeps the old file-per-line format for compatibility.
+ for ar_file in &ar_files {
+ drop_println!(ws.gctx(), "{}", ar_file.rel_str);
+ }
+ }
+ PackageMessageFormat::Json => {
+ let message = messages::PackageList {
+ id: pkg.package_id().to_spec(),
+ files: BTreeMap::from_iter(ar_files.into_iter().map(|f| {
+ let file = match f.contents {
+ FileContents::OnDisk(path) => messages::PackageFile::Copy { path },
+ FileContents::Generated(
+ GeneratedFile::Manifest(path)
+ | GeneratedFile::Lockfile(Some(path)),
+ ) => messages::PackageFile::Generate { path: Some(path) },
+ FileContents::Generated(
+ GeneratedFile::VcsInfo(_) | GeneratedFile::Lockfile(None),
+ ) => messages::PackageFile::Generate { path: None },
+ };
+ (f.rel_path, file)
+ })),
+ };
+ let _ = ws.gctx().shell().print_json(&message);
+ }
}
} else {
let tarball = create_package(ws, &pkg, ar_files, local_reg.as_ref())?;
@@ -444,7 +504,9 @@ fn build_ar_list(
.push(ArchiveFile {
rel_path: PathBuf::from("Cargo.toml"),
rel_str: "Cargo.toml".to_string(),
- contents: FileContents::Generated(GeneratedFile::Manifest),
+ contents: FileContents::Generated(GeneratedFile::Manifest(
+ pkg.manifest_path().to_owned(),
+ )),
});
} else {
ws.gctx().shell().warn(&format!(
@@ -454,6 +516,8 @@ fn build_ar_list(
}
if include_lockfile {
+ let lockfile_path = ws.lock_root().as_path_unlocked().join(LOCKFILE_NAME);
+ let lockfile_path = lockfile_path.exists().then_some(lockfile_path);
let rel_str = "Cargo.lock";
result
.entry(UncasedAscii::new(rel_str))
@@ -461,7 +525,7 @@ fn build_ar_list(
.push(ArchiveFile {
rel_path: PathBuf::from(rel_str),
rel_str: rel_str.to_string(),
- contents: FileContents::Generated(GeneratedFile::Lockfile),
+ contents: FileContents::Generated(GeneratedFile::Lockfile(lockfile_path)),
});
}
@@ -780,8 +844,10 @@ fn tar(
}
FileContents::Generated(generated_kind) => {
let contents = match generated_kind {
- GeneratedFile::Manifest => publish_pkg.manifest().to_normalized_contents()?,
- GeneratedFile::Lockfile => build_lock(ws, &publish_pkg, local_reg)?,
+ GeneratedFile::Manifest(_) => {
+ publish_pkg.manifest().to_normalized_contents()?
+ }
+ GeneratedFile::Lockfile(_) => build_lock(ws, &publish_pkg, local_reg)?,
GeneratedFile::VcsInfo(ref s) => serde_json::to_string_pretty(s)?,
};
header.set_entry_type(EntryType::file());
diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs
index 7d35f952995..9960c12649d 100644
--- a/src/cargo/ops/mod.rs
+++ b/src/cargo/ops/mod.rs
@@ -10,7 +10,10 @@ pub use self::cargo_fetch::{fetch, FetchOptions};
pub use self::cargo_install::{install, install_list};
pub use self::cargo_new::{init, new, NewOptions, NewProjectKind, VersionControl};
pub use self::cargo_output_metadata::{output_metadata, ExportInfo, OutputMetadataOptions};
-pub use self::cargo_package::{check_yanked, package, PackageOpts};
+pub use self::cargo_package::check_yanked;
+pub use self::cargo_package::package;
+pub use self::cargo_package::PackageMessageFormat;
+pub use self::cargo_package::PackageOpts;
pub use self::cargo_pkgid::pkgid;
pub use self::cargo_read_manifest::read_package;
pub use self::cargo_run::run;
diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs
index c064c01c2dd..85cb6118023 100644
--- a/src/cargo/ops/registry/publish.rs
+++ b/src/cargo/ops/registry/publish.rs
@@ -144,6 +144,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
gctx: opts.gctx,
verify: opts.verify,
list: false,
+ fmt: ops::PackageMessageFormat::Human,
check_metadata: true,
allow_dirty: opts.allow_dirty,
include_lockfile: true,
diff --git a/src/doc/man/cargo-package.md b/src/doc/man/cargo-package.md
index e3e12090998..217f41632df 100644
--- a/src/doc/man/cargo-package.md
+++ b/src/doc/man/cargo-package.md
@@ -116,6 +116,48 @@ lock-files will be generated under the assumption that dependencies will be
published to this registry.
{{/option}}
+{{#option "`--message-format` _fmt_" }}
+Specifies the output message format.
+Currently, it only works with `--list` and affects the file listing format.
+This is unstable and requires `-Zunstable-options`.
+Valid output formats:
+
+- `human` (default): Display in a file-per-line format.
+- `json`: Emit machine-readable JSON information about each package.
+ One package per JSON line (Newline delimited JSON).
+ ```javascript
+ {
+ /* The Package ID Spec of the package. */
+ "id": "path+file:///home/foo#0.0.0",
+ /* Files of this package */
+ "files" {
+ /* Relative path in the archive file. */
+ "Cargo.toml.orig": {
+ /* Where the file is from.
+ - "generate" for file being generated during packaging
+ - "copy" for file being copied from another location.
+ */
+ "kind": "copy",
+ /* For the "copy" kind,
+ it is an absolute path to the actual file content.
+ For the "generate" kind,
+ it is the original file the generated one is based on.
+ */
+ "path": "/home/foo/Cargo.toml"
+ },
+ "Cargo.toml": {
+ "kind": "generate",
+ "path": "/home/foo/Cargo.toml"
+ },
+ "src/main.rs": {
+ "kind": "copy",
+ "path": "/home/foo/src/main.rs"
+ }
+ }
+ }
+ ```
+{{/option}}
+
{{/options}}
{{> section-package-selection }}
diff --git a/src/doc/man/generated_txt/cargo-package.txt b/src/doc/man/generated_txt/cargo-package.txt
index 25b1f6bc289..086b68d050d 100644
--- a/src/doc/man/generated_txt/cargo-package.txt
+++ b/src/doc/man/generated_txt/cargo-package.txt
@@ -114,6 +114,45 @@ OPTIONS
multiple inter-dependent crates, lock-files will be generated under
the assumption that dependencies will be published to this registry.
+ --message-format fmt
+ Specifies the output message format. Currently, it only works with
+ --list and affects the file listing format. This is unstable and
+ requires -Zunstable-options. Valid output formats:
+
+ o human (default): Display in a file-per-line format.
+
+ o json: Emit machine-readable JSON information about each package.
+ One package per JSON line (Newline delimited JSON).
+ {
+ /* The Package ID Spec of the package. */
+ "id": "path+file:///home/foo#0.0.0",
+ /* Files of this package */
+ "files" {
+ /* Relative path in the archive file. */
+ "Cargo.toml.orig": {
+ /* Where the file is from.
+ - "generate" for file being generated during packaging
+ - "copy" for file being copied from another location.
+ */
+ "kind": "copy",
+ /* For the "copy" kind,
+ it is an absolute path to the actual file content.
+ For the "generate" kind,
+ it is the original file the generated one is based on.
+ */
+ "path": "/home/foo/Cargo.toml"
+ },
+ "Cargo.toml": {
+ "kind": "generate",
+ "path": "/home/foo/Cargo.toml"
+ },
+ "src/main.rs": {
+ "kind": "copy",
+ "path": "/home/foo/src/main.rs"
+ }
+ }
+ }
+
Package Selection
By default, when no package selection options are given, the packages
selected depend on the selected manifest file (based on the current
diff --git a/src/doc/src/commands/cargo-package.md b/src/doc/src/commands/cargo-package.md
index d131bc7f902..ea9ee490bee 100644
--- a/src/doc/src/commands/cargo-package.md
+++ b/src/doc/src/commands/cargo-package.md
@@ -113,6 +113,49 @@ lock-files will be generated under the assumption that dependencies will be
published to this registry.
+
--message-formatfmt
+
Specifies the output message format.
+Currently, it only works with --list and affects the file listing format.
+This is unstable and requires -Zunstable-options.
+Valid output formats:
+
+
human (default): Display in a file-per-line format.
+
json: Emit machine-readable JSON information about each package.
+One package per JSON line (Newline delimited JSON).
+
{
+ /* The Package ID Spec of the package. */
+ "id": "path+file:///home/foo#0.0.0",
+ /* Files of this package */
+ "files" {
+ /* Relative path in the archive file. */
+ "Cargo.toml.orig": {
+ /* Where the file is from.
+ - "generate" for file being generated during packaging
+ - "copy" for file being copied from another location.
+ */
+ "kind": "copy",
+ /* For the "copy" kind,
+ it is an absolute path to the actual file content.
+ For the "generate" kind,
+ it is the original file the generated one is based on.
+ */
+ "path": "/home/foo/Cargo.toml"
+ },
+ "Cargo.toml": {
+ "kind": "generate",
+ "path": "/home/foo/Cargo.toml"
+ },
+ "src/main.rs": {
+ "kind": "copy",
+ "path": "/home/foo/src/main.rs"
+ }
+ }
+}
+
+
+
+
+
### Package Selection
diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md
index d28258ea556..362b92b730b 100644
--- a/src/doc/src/reference/unstable.md
+++ b/src/doc/src/reference/unstable.md
@@ -123,6 +123,7 @@ Each new feature described below should explain how to use it.
* [package-workspace](#package-workspace) --- Allows for packaging and publishing multiple crates in a workspace.
* [native-completions](#native-completions) --- Move cargo shell completions to native completions.
* [warnings](#warnings) --- controls warning behavior; options for allowing or denying warnings.
+ * [Package message format](#package-message-format) --- Message format for `cargo package`.
## allow-features
@@ -1889,6 +1890,16 @@ Specify which packages participate in [feature unification](../reference/feature
* `package` _(unimplemented)_: Dependency features are considered on a package-by-package basis,
preferring duplicate builds of dependencies when different sets of features are activated by the packages.
+## Package message format
+
+* Tracking Issue: [#11666](https://github.com/rust-lang/cargo/issues/11666)
+
+The `--message-format` flag in `cargo package` controls the output message format.
+Currently, it only works with the `--list` flag and affects the file listing format,
+Requires `-Zunstable-options`.
+See [`cargo package --message-format`](../commands/cargo-package.md#option-cargo-package---message-format)
+for more information.
+
# Stabilized and removed features
## Compile progress
diff --git a/src/etc/man/cargo-package.1 b/src/etc/man/cargo-package.1
index f07c4a9a2ca..0dca951f851 100644
--- a/src/etc/man/cargo-package.1
+++ b/src/etc/man/cargo-package.1
@@ -149,6 +149,57 @@ to this registry, but if we are packaging multiple inter\-dependent crates,
lock\-files will be generated under the assumption that dependencies will be
published to this registry.
.RE
+.sp
+\fB\-\-message\-format\fR \fIfmt\fR
+.RS 4
+Specifies the output message format.
+Currently, it only works with \fB\-\-list\fR and affects the file listing format.
+This is unstable and requires \fB\-Zunstable\-options\fR\&.
+Valid output formats:
+.sp
+.RS 4
+\h'-04'\(bu\h'+03'\fBhuman\fR (default): Display in a file\-per\-line format.
+.RE
+.sp
+.RS 4
+\h'-04'\(bu\h'+03'\fBjson\fR: Emit machine\-readable JSON information about each package.
+One package per JSON line (Newline delimited JSON).
+.sp
+.RS 4
+.nf
+{
+ /* The Package ID Spec of the package. */
+ "id": "path+file:///home/foo#0.0.0",
+ /* Files of this package */
+ "files" {
+ /* Relative path in the archive file. */
+ "Cargo.toml.orig": {
+ /* Where the file is from.
+ \- "generate" for file being generated during packaging
+ \- "copy" for file being copied from another location.
+ */
+ "kind": "copy",
+ /* For the "copy" kind,
+ it is an absolute path to the actual file content.
+ For the "generate" kind,
+ it is the original file the generated one is based on.
+ */
+ "path": "/home/foo/Cargo.toml"
+ },
+ "Cargo.toml": {
+ "kind": "generate",
+ "path": "/home/foo/Cargo.toml"
+ },
+ "src/main.rs": {
+ "kind": "copy",
+ "path": "/home/foo/src/main.rs"
+ }
+ }
+}
+.fi
+.RE
+.RE
+.RE
.SS "Package Selection"
By default, when no package selection options are given, the packages selected
depend on the selected manifest file (based on the current working directory if
diff --git a/tests/testsuite/cargo_package/help/stdout.term.svg b/tests/testsuite/cargo_package/help/stdout.term.svg
index 48804731064..671b716e705 100644
--- a/tests/testsuite/cargo_package/help/stdout.term.svg
+++ b/tests/testsuite/cargo_package/help/stdout.term.svg
@@ -1,4 +1,4 @@
-