diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index 5009c0b67cc..201907bb268 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -103,7 +103,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { if allowed_registries.is_empty() { bail!( "`{}` cannot be published.\n\ - `package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.", + `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish.", pkg.name(), ); } else if !allowed_registries.contains(®_name) { diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index 482268923fc..8e3912288bf 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -6,8 +6,6 @@ use crate::Config; const DEFAULT_EDITION: crate::core::features::Edition = crate::core::features::Edition::LATEST_STABLE; -const DEFAULT_VERSION: &str = "0.0.0"; -const DEFAULT_PUBLISH: bool = false; const AUTO_FIELDS: &[&str] = &["autobins", "autoexamples", "autotests", "autobenches"]; pub fn expand_manifest( @@ -123,9 +121,6 @@ fn expand_manifest_( package .entry("name".to_owned()) .or_insert(toml::Value::String(name)); - package - .entry("version".to_owned()) - .or_insert_with(|| toml::Value::String(DEFAULT_VERSION.to_owned())); package.entry("edition".to_owned()).or_insert_with(|| { let _ = config.shell().warn(format_args!( "`package.edition` is unspecified, defaulting to `{}`", @@ -136,9 +131,6 @@ fn expand_manifest_( package .entry("build".to_owned()) .or_insert_with(|| toml::Value::Boolean(false)); - package - .entry("publish".to_owned()) - .or_insert_with(|| toml::Value::Boolean(DEFAULT_PUBLISH)); for field in AUTO_FIELDS { package .entry(field.to_owned()) @@ -621,8 +613,6 @@ autotests = false build = false edition = "2021" name = "test-" -publish = false -version = "0.0.0" [profile.release] strip = true @@ -651,8 +641,6 @@ autotests = false build = false edition = "2021" name = "test-" -publish = false -version = "0.0.0" [profile.release] strip = true diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 42562584331..52f99bb59dc 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -550,11 +550,17 @@ impl TomlManifest { let version = package .version .clone() - .resolve("version", || inherit()?.version())?; + .map(|version| version.resolve("version", || inherit()?.version())) + .transpose()?; - package.version = MaybeWorkspace::Defined(version.clone()); + package.version = version.clone().map(MaybeWorkspace::Defined); - let pkgid = package.to_package_id(source_id, version)?; + let pkgid = package.to_package_id( + source_id, + version + .clone() + .unwrap_or_else(|| semver::Version::new(0, 0, 0)), + )?; let edition = if let Some(edition) = package.edition.clone() { let edition: Edition = edition @@ -1006,9 +1012,14 @@ impl TomlManifest { let publish = match publish { Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), Some(VecStringOrBool::Bool(false)) => Some(vec![]), - None | Some(VecStringOrBool::Bool(true)) => None, + Some(VecStringOrBool::Bool(true)) => None, + None => version.is_none().then_some(vec![]), }; + if version.is_none() && publish != Some(vec![]) { + bail!("`package.publish` requires `package.version` be specified"); + } + if summary.features().contains_key("default-features") { warnings.push( "`default-features = [\"..\"]` was found in [features]. \ @@ -1659,8 +1670,7 @@ pub struct TomlPackage { edition: Option, rust_version: Option, name: InternedString, - #[serde(deserialize_with = "version_trim_whitespace")] - version: MaybeWorkspaceSemverVersion, + version: Option, authors: Option, build: Option, metabuild: Option, @@ -1709,22 +1719,6 @@ impl TomlPackage { } } -fn version_trim_whitespace<'de, D>(deserializer: D) -> Result -where - D: de::Deserializer<'de>, -{ - UntaggedEnumVisitor::new() - .expecting("SemVer version") - .string( - |value| match value.trim().parse().map_err(de::Error::custom) { - Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), - Err(e) => Err(e), - }, - ) - .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) - .deserialize(deserializer) -} - /// This Trait exists to make [`MaybeWorkspace::Workspace`] generic. It makes deserialization of /// [`MaybeWorkspace`] much easier, as well as making error messages for /// [`MaybeWorkspace::resolve`] much nicer @@ -1793,6 +1787,23 @@ impl MaybeWorkspace { //. This already has a `Deserialize` impl from version_trim_whitespace type MaybeWorkspaceSemverVersion = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceSemverVersion { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("SemVer version") + .string( + |value| match value.trim().parse().map_err(de::Error::custom) { + Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), + Err(e) => Err(e), + }, + ) + .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) + .deserialize(d) + } +} type MaybeWorkspaceString = MaybeWorkspace; impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index b203f4053d6..e3168a47fdc 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -109,6 +109,8 @@ resolve dependencies, and for guidelines on setting your own version. See the [SemVer compatibility] chapter for more details on exactly what constitutes a breaking change. +This field is optional and defaults to `0.0.0`. The field is required for publishing packages. + [Resolver]: resolver.md [SemVer compatibility]: semver.md @@ -470,23 +472,22 @@ if any of those files change. ### The `publish` field -The `publish` field can be used to prevent a package from being published to a -package registry (like *crates.io*) by mistake, for instance to keep a package -private in a company. - +The `publish` field can be used to control which registries names the package +may be published to: ```toml [package] # ... -publish = false +publish = ["some-registry-name"] ``` -The value may also be an array of strings which are registry names that are -allowed to be published to. - +To prevent a package from being published to a registry (like crates.io) by mistake, +for instance to keep a package private in a company, +you can omit the [`version`](#the-version-field) field. +If you'd like to be more explicit, you can disable publishing: ```toml [package] # ... -publish = ["some-registry-name"] +publish = false ``` If publish array contains a single registry, `cargo publish` command will use diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index b66469c8ce5..12a56d72298 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1221,9 +1221,6 @@ at the start of the infostring at the top of the file. Inferred / defaulted manifest fields: - `package.name = ` -- `package.version = "0.0.0"` to [call attention to this crate being used in unexpected places](https://matklad.github.io/2021/08/22/large-rust-workspaces.html#Smaller-Tips) -- `package.publish = false` to avoid accidental publishes, particularly if we - later add support for including them in a workspace. - `package.edition = ` to avoid always having to add an embedded manifest at the cost of potentially breaking scripts on rust upgrades - Warn when `edition` is unspecified to raise awareness of this diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index b74bd620996..03611ae67e7 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -1496,3 +1496,26 @@ fn check_unused_manifest_keys() { ) .run(); } + +#[cargo_test] +fn versionless_package() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("check") + .with_stderr( + "\ +[CHECKING] foo v0.0.0 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index fbead4dea97..888cdce8c0e 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -4257,3 +4257,285 @@ fn workspace_metadata_with_dependencies_no_deps_artifact() { ) .run(); } + +#[cargo_test] +fn versionless_packages() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + + [dependencies] + foobar = "0.0.1" + baz = { path = "../baz/" } + "#, + ) + .file("bar/src/lib.rs", "") + .file( + "baz/Cargo.toml", + r#" + [package] + name = "baz" + + [dependencies] + foobar = "0.0.1" + "#, + ) + .file("baz/src/lib.rs", "") + .build(); + Package::new("foobar", "0.0.1").publish(); + + p.cargo("metadata -q --format-version 1") + .with_json( + r#" +{ + "packages": [ + { + "name": "bar", + "version": "0.0.0", + "id": "bar 0.0.0 [..]", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "baz", + "source": null, + "req": "*", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null, + "path": "[..]/baz" + }, + { + "name": "foobar", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "bar", + "src_path": "[..]/bar/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/bar/Cargo.toml", + "metadata": null, + "publish": [], + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + }, + { + "name": "baz", + "version": "0.0.0", + "id": "baz 0.0.0 [..]", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "foobar", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "baz", + "src_path": "[..]/baz/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/baz/Cargo.toml", + "metadata": null, + "publish": [], + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + }, + { + "name": "foobar", + "version": "0.0.1", + "id": "foobar 0.0.1 [..]", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "foobar", + "src_path": "[..]/foobar-0.0.1/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/foobar-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + } + ], + "workspace_members": [ + "bar 0.0.0 [..]", + "baz 0.0.0 [..]" + ], + "workspace_default_members": [ + "bar 0.0.0 [..]", + "baz 0.0.0 [..]" + ], + "resolve": { + "nodes": [ + { + "id": "bar 0.0.0 [..]", + "dependencies": [ + "baz 0.0.0 [..]", + "foobar 0.0.1 [..]" + ], + "deps": [ + { + "name": "baz", + "pkg": "baz 0.0.0 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + }, + { + "name": "foobar", + "pkg": "foobar 0.0.1 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + }, + { + "id": "baz 0.0.0 [..]", + "dependencies": [ + "foobar 0.0.1 [..]" + ], + "deps": [ + { + "name": "foobar", + "pkg": "foobar 0.0.1 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + }, + { + "id": "foobar 0.0.1 [..]", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": null + }, + "target_directory": "[..]/foo/target", + "version": 1, + "workspace_root": "[..]", + "metadata": null +} +"#, + ) + .run(); +} diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 8a7334d890d..4ec4fc0d6fc 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -3095,3 +3095,40 @@ src/main.rs &[], ); } + +#[cargo_test] +fn versionless_package() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("package") + .with_stderr( + "\ +warning: manifest has no license, license-file, documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. + Packaging foo v0.0.0 ([CWD]) + Verifying foo v0.0.0 ([CWD]) + Compiling foo v0.0.0 ([CWD]/target/package/foo-0.0.0) + Finished dev [unoptimized + debuginfo] target(s) in [..]s + Packaged 4 files, [..]B ([..]B compressed) +", + ) + .run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.0.crate")).unwrap(); + validate_crate_contents( + f, + "foo-0.0.0.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[], + ); +} diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 67569bf3b6a..5d29ac88a91 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -420,7 +420,7 @@ fn unpublishable_crate() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -794,7 +794,7 @@ fn publish_empty_list() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -1020,7 +1020,7 @@ fn block_publish_no_registry() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -3004,3 +3004,32 @@ Caused by: .with_status(101) .run(); } + +#[cargo_test] +fn versionless_package() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +error: `foo` cannot be published. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. +", + ) + .run(); +}