From b7002ffd5e86310dd5e5bece88016ca749307937 Mon Sep 17 00:00:00 2001
From: Weihang Lo
Date: Tue, 25 Feb 2025 01:31:39 -0500
Subject: [PATCH 1/2] test(package): fail with unpublished cyclic/transitive
deps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
After dd698ff0483bda065d1f78968e70ee86fb116a72,
`cargo package --no-verify` at least fails in three different cases:
* An unpublished package depending on itself as a dev-dependency (cyclic self-referential dev-dependencies).
* Can be resolved by removing the `version` field from the affected dev-dependency.
* `-Zpackage-workspace` doesn't help with it.
* Existing `cargo package` has `--package ` specifying certain unpublished packages.
* Can be resolved by specifying all unpublished packages in one `cargo` call.
* `-Zpackage-workspace` also requires all dependency versions available in the target registry when calling, so doesn't help.
* `cargo package --no-verify` has been used as a kind of “plumbing commands” to create tarballs without considering dependency orders. The use cases include:
* Preparing tarballs for other package managers.
* Integrating into custom develop workflows for unpublished/internal crates.
* Constructing custom/private registries.
This commit shows the former two cases.
---
tests/testsuite/package.rs | 140 +++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs
index 0263607449c..858b7a1ccaf 100644
--- a/tests/testsuite/package.rs
+++ b/tests/testsuite/package.rs
@@ -7284,3 +7284,143 @@ This might cause the `.crate` file to include incorrect or incomplete files
],
);
}
+
+#[cargo_test]
+fn exclude_lockfile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2015"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package --list")
+ .with_stdout_data(str![[r#"
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/lib.rs
+
+"#]])
+ .with_stderr_data("")
+ .run();
+
+ p.cargo("package")
+ .with_stderr_data(str![[r#"
+[PACKAGING] foo v0.0.1 ([ROOT]/foo)
+[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
+[VERIFYING] foo v0.0.1 ([ROOT]/foo)
+[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+ .run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ (),
+ );
+}
+
+// A failing case from
+#[cargo_test]
+fn unpublished_cyclic_dev_dependencies() {
+ registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2015"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+
+ [dev-dependencies]
+ foo = { path = ".", version = "0.0.1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify")
+ .with_status(101)
+ .with_stderr_data(str![[r#"
+[PACKAGING] foo v0.0.1 ([ROOT]/foo)
+[UPDATING] `dummy-registry` index
+[ERROR] failed to prepare local package for uploading
+
+Caused by:
+ no matching package named `foo` found
+ location searched: `dummy-registry` index (which is replacing registry `crates-io`)
+ required by package `foo v0.0.1 ([ROOT]/foo)`
+
+"#]])
+ .run();
+}
+
+// A failing case from
+#[cargo_test]
+fn unpublished_dependency() {
+ registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2015"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+
+ [dependencies]
+ dep = { path = "./dep", version = "0.0.1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "dep/Cargo.toml",
+ r#"
+ [package]
+ name = "dep"
+ version = "0.0.1"
+ edition = "2015"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify -p foo")
+ .with_status(101)
+ .with_stderr_data(str![[r#"
+[PACKAGING] foo v0.0.1 ([ROOT]/foo)
+[UPDATING] `dummy-registry` index
+[ERROR] failed to prepare local package for uploading
+
+Caused by:
+ no matching package named `dep` found
+ location searched: `dummy-registry` index (which is replacing registry `crates-io`)
+ required by package `foo v0.0.1 ([ROOT]/foo)`
+
+"#]])
+ .run();
+}
From 4e8b337eb3034834fc3fc1491eff8245ff310bce Mon Sep 17 00:00:00 2001
From: NoisyCoil
Date: Sat, 8 Feb 2025 00:54:02 +0100
Subject: [PATCH 2/2] feat(package): add `--exclude-lockfile` flag
When `--exclude-lockfile` is enabled,
`cargo package` will not verify the lock file if present,
nor will it generate a new one if absent.
Cargo.lock will not be included in the resulting tarball.
Together with `--no-verify`,
this flag decouples packaging from checking the registry index.
While this is useful for some non-normal workflows that requires
to assemble packages having unpublished dependencies.
It is recommended to use `-Zpackage-workspace` to package the entire
workspace, instead of opting out lockfile.
---
src/bin/cargo/commands/package.rs | 5 ++
src/cargo/ops/cargo_package/mod.rs | 25 ++++---
src/cargo/ops/registry/publish.rs | 1 +
src/doc/man/cargo-package.md | 12 +++-
src/doc/man/generated_txt/cargo-package.txt | 12 +++-
src/doc/src/commands/cargo-package.md | 11 ++-
src/etc/man/cargo-package.1 | 13 +++-
.../cargo_package/help/stdout.term.svg | 70 ++++++++++---------
tests/testsuite/package.rs | 49 +++++++------
9 files changed, 123 insertions(+), 75 deletions(-)
diff --git a/src/bin/cargo/commands/package.rs b/src/bin/cargo/commands/package.rs
index 251fa286ec5..58b935d0b9b 100644
--- a/src/bin/cargo/commands/package.rs
+++ b/src/bin/cargo/commands/package.rs
@@ -26,6 +26,10 @@ pub fn cli() -> Command {
"allow-dirty",
"Allow dirty working directories to be packaged",
))
+ .arg(flag(
+ "exclude-lockfile",
+ "Don't include the lock file when packaging",
+ ))
.arg_silent_suggestion()
.arg_package_spec_no_all(
"Package(s) to assemble",
@@ -79,6 +83,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
list: args.flag("list"),
check_metadata: !args.flag("no-metadata"),
allow_dirty: args.flag("allow-dirty"),
+ include_lockfile: !args.flag("exclude-lockfile"),
to_package: specs,
targets: args.targets()?,
jobs: args.jobs()?,
diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs
index 5074425309b..1c58fd87aa8 100644
--- a/src/cargo/ops/cargo_package/mod.rs
+++ b/src/cargo/ops/cargo_package/mod.rs
@@ -46,6 +46,7 @@ pub struct PackageOpts<'gctx> {
pub list: bool,
pub check_metadata: bool,
pub allow_dirty: bool,
+ pub include_lockfile: bool,
pub verify: bool,
pub jobs: Option,
pub keep_going: bool,
@@ -194,6 +195,7 @@ fn do_package<'a>(
.as_path_unlocked()
.join(LOCKFILE_NAME)
.exists()
+ && opts.include_lockfile
{
// Make sure the Cargo.lock is up-to-date and valid.
let dry_run = false;
@@ -389,7 +391,7 @@ fn prepare_archive(
// Check (git) repository state, getting the current commit hash.
let vcs_info = vcs::check_repo_state(pkg, &src_files, gctx, &opts)?;
- build_ar_list(ws, pkg, src_files, vcs_info)
+ build_ar_list(ws, pkg, src_files, vcs_info, opts.include_lockfile)
}
/// Builds list of files to archive.
@@ -399,6 +401,7 @@ fn build_ar_list(
pkg: &Package,
src_files: Vec,
vcs_info: Option,
+ include_lockfile: bool,
) -> CargoResult> {
let mut result = HashMap::new();
let root = pkg.root();
@@ -453,15 +456,17 @@ fn build_ar_list(
))?;
}
- let rel_str = "Cargo.lock";
- result
- .entry(UncasedAscii::new(rel_str))
- .or_insert_with(Vec::new)
- .push(ArchiveFile {
- rel_path: PathBuf::from(rel_str),
- rel_str: rel_str.to_string(),
- contents: FileContents::Generated(GeneratedFile::Lockfile),
- });
+ if include_lockfile {
+ let rel_str = "Cargo.lock";
+ result
+ .entry(UncasedAscii::new(rel_str))
+ .or_insert_with(Vec::new)
+ .push(ArchiveFile {
+ rel_path: PathBuf::from(rel_str),
+ rel_str: rel_str.to_string(),
+ contents: FileContents::Generated(GeneratedFile::Lockfile),
+ });
+ }
if let Some(vcs_info) = vcs_info {
let rel_str = VCS_INFO_FILE;
diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs
index 0d1d22e8f14..c064c01c2dd 100644
--- a/src/cargo/ops/registry/publish.rs
+++ b/src/cargo/ops/registry/publish.rs
@@ -146,6 +146,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
list: false,
check_metadata: true,
allow_dirty: opts.allow_dirty,
+ include_lockfile: true,
// `package_with_dep_graph` ignores this field in favor of
// the already-resolved list of packages
to_package: ops::Packages::Default,
diff --git a/src/doc/man/cargo-package.md b/src/doc/man/cargo-package.md
index 986fb0b4c57..e3e12090998 100644
--- a/src/doc/man/cargo-package.md
+++ b/src/doc/man/cargo-package.md
@@ -27,8 +27,8 @@ stored in the `target/package` directory. This performs the following steps:
- `[patch]`, `[replace]`, and `[workspace]` sections are removed from the
manifest.
- `Cargo.lock` is always included. When missing, a new lock file will be
- generated. {{man "cargo-install" 1}} will use the packaged lock file if
- the `--locked` flag is used.
+ generated unless the `--exclude-lockfile` flag is used. {{man "cargo-install" 1}}
+ will use the packaged lock file if the `--locked` flag is used.
- A `.cargo_vcs_info.json` file is included that contains information
about the current VCS checkout hash if available, as well as a flag if the
worktree is dirty.
@@ -98,6 +98,14 @@ or the license).
Allow working directories with uncommitted VCS changes to be packaged.
{{/option}}
+{{#option "`--exclude-lockfile`" }}
+Don't include the lock file when packaging.
+
+This flag is not for general use.
+Some tools may expect a lock file to be present (e.g. `cargo install --locked`).
+Consider other options before using this.
+{{/option}}
+
{{> options-index }}
{{#option "`--registry` _registry_"}}
diff --git a/src/doc/man/generated_txt/cargo-package.txt b/src/doc/man/generated_txt/cargo-package.txt
index 421613b17f6..25b1f6bc289 100644
--- a/src/doc/man/generated_txt/cargo-package.txt
+++ b/src/doc/man/generated_txt/cargo-package.txt
@@ -27,8 +27,9 @@ DESCRIPTION
manifest.
o Cargo.lock is always included. When missing, a new lock file will
- be generated. cargo-install(1) will use the packaged lock file if
- the --locked flag is used.
+ be generated unless the --exclude-lockfile flag is used.
+ cargo-install(1) will use the packaged lock file if the --locked
+ flag is used.
o A .cargo_vcs_info.json file is included that contains information
about the current VCS checkout hash if available, as well as a
@@ -96,6 +97,13 @@ OPTIONS
Allow working directories with uncommitted VCS changes to be
packaged.
+ --exclude-lockfile
+ Don’t include the lock file when packaging.
+
+ This flag is not for general use. Some tools may expect a lock file
+ to be present (e.g. cargo install --locked). Consider other options
+ before using this.
+
--index index
The URL of the registry index to use.
diff --git a/src/doc/src/commands/cargo-package.md b/src/doc/src/commands/cargo-package.md
index 6e2a40fede9..d131bc7f902 100644
--- a/src/doc/src/commands/cargo-package.md
+++ b/src/doc/src/commands/cargo-package.md
@@ -22,8 +22,8 @@ stored in the `target/package` directory. This performs the following steps:
- `[patch]`, `[replace]`, and `[workspace]` sections are removed from the
manifest.
- `Cargo.lock` is always included. When missing, a new lock file will be
- generated. [cargo-install(1)](cargo-install.html) will use the packaged lock file if
- the `--locked` flag is used.
+ generated unless the `--exclude-lockfile` flag is used. [cargo-install(1)](cargo-install.html)
+ will use the packaged lock file if the `--locked` flag is used.
- A `.cargo_vcs_info.json` file is included that contains information
about the current VCS checkout hash if available, as well as a flag if the
worktree is dirty.
@@ -94,6 +94,13 @@ or the license).
Allow working directories with uncommitted VCS changes to be packaged.
+
--exclude-lockfile
+
Don’t include the lock file when packaging.
+
This flag is not for general use.
+Some tools may expect a lock file to be present (e.g. cargo install --locked).
+Consider other options before using this.
+
+
--indexindex
The URL of the registry index to use.
diff --git a/src/etc/man/cargo-package.1 b/src/etc/man/cargo-package.1
index 25dec593370..f07c4a9a2ca 100644
--- a/src/etc/man/cargo-package.1
+++ b/src/etc/man/cargo-package.1
@@ -36,8 +36,8 @@ manifest.
.sp
.RS 4
\h'-04'\(bu\h'+03'\fBCargo.lock\fR is always included. When missing, a new lock file will be
-generated. \fBcargo\-install\fR(1) will use the packaged lock file if
-the \fB\-\-locked\fR flag is used.
+generated unless the \fB\-\-exclude\-lockfile\fR flag is used. \fBcargo\-install\fR(1)
+will use the packaged lock file if the \fB\-\-locked\fR flag is used.
.RE
.sp
.RS 4
@@ -127,6 +127,15 @@ or the license).
Allow working directories with uncommitted VCS changes to be packaged.
.RE
.sp
+\fB\-\-exclude\-lockfile\fR
+.RS 4
+Don\[cq]t include the lock file when packaging.
+.sp
+This flag is not for general use.
+Some tools may expect a lock file to be present (e.g. \fBcargo install \-\-locked\fR).
+Consider other options before using this.
+.RE
+.sp
\fB\-\-index\fR \fIindex\fR
.RS 4
The URL of the registry index to use.
diff --git a/tests/testsuite/cargo_package/help/stdout.term.svg b/tests/testsuite/cargo_package/help/stdout.term.svg
index 2c41962a18f..0e301a216c4 100644
--- a/tests/testsuite/cargo_package/help/stdout.term.svg
+++ b/tests/testsuite/cargo_package/help/stdout.term.svg
@@ -1,4 +1,4 @@
-