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.

+ +
--index index
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 @@ - +