Skip to content

Commit 307486e

Browse files
committed
Auto merge of #12786 - epage:version, r=weihanglo
feat(toml): Allow version-less manifests ### What does this PR try to resolve? Expected behavior with this PR: - `package.version` defaults to `0.0.0` - `package.publish` is defaulted to `version.is_some()` This also updates "cargo script" to rely on this new behavior. My motivation is to find ways to close the gap between "cargo script" and `Cargo.toml`. With "cargo script", we want to allow people to only write however much of a manifest is directly needed for the work they are doing (which includes having no manifest). Each difference between "cargo script" and `Cargo.toml` is a cost we have to pay in our documentation and a hurdle in a users understanding of what is happening. There has been other interest in this which I also find of interest (from #9829): - Lower boilerplate, whether for [cargo xtasks](https://github.com/matklad/cargo-xtask), nested packages (rust-lang/rfcs#3452), etc - Unmet expectations from users because this field is primarily targeted at registry operations when they want it for their marketing version (#6583). - Make "unpublished" packages stand out This then unblocks unifying `package.publish` by making the field's default based on the presence of a version as inspired by the proposal in #9829. Without this change, we were trading one form of boilerplate (`version = "0.0.0"`) for another (`publish = false`). Fixes #9829 Fixes #12690 Fixes #6153 ### How should we test and review this PR? The initial commit has test cases I thought would be relevant for this change and you can see how each commit affects those or existing test cases. Would definitely be interested in hearing of other troubling cases to test Implementation wise, I made `MaybeWorkspaceVersion` deserializer trim spaces so I could more easily handle the field being an `Option`. This is in its own commit. ### Additional information Alternatives considered - Making the default version "stand out more" with it being something like `0.0.0+HEAD`. The extra noise didn't seem worth it and people would contend over what the metadata field *should be* - Make the default version the lowest version possible (`0.0.0-0`?). Unsure if this will ever really matter especially since you can't publish - Defer defaulting `package.publish` and instead error - Further unifying more fields made this too compelling for me :) - Put this behind `-Zscript` and make it a part of rust-lang/rfcs#3502 - Having an affect outside of that RFC, I wanted to make sure this got the attention it deserved rather than getting lost in the noise of a large RFC. - Don't just default the version but make packages versionless - I extended the concept of versionless to `PackageId`'s internals and saw no observable difference - I then started to examine the idea of version being optional everywhere (via `PackageId`s API) and ... things got messy especially when starting to look at the resolver. This would have also added a lot of error checks / asserts for "the version must be set here". Overall, the gains seemed questionable and the cost high, so I held off.
2 parents de54757 + 7ac49a9 commit 307486e

File tree

9 files changed

+418
-50
lines changed

9 files changed

+418
-50
lines changed

src/cargo/ops/registry/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
103103
if allowed_registries.is_empty() {
104104
bail!(
105105
"`{}` cannot be published.\n\
106-
`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.",
106+
`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish.",
107107
pkg.name(),
108108
);
109109
} else if !allowed_registries.contains(&reg_name) {

src/cargo/util/toml/embedded.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ use crate::Config;
66

77
const DEFAULT_EDITION: crate::core::features::Edition =
88
crate::core::features::Edition::LATEST_STABLE;
9-
const DEFAULT_VERSION: &str = "0.0.0";
10-
const DEFAULT_PUBLISH: bool = false;
119
const AUTO_FIELDS: &[&str] = &["autobins", "autoexamples", "autotests", "autobenches"];
1210

1311
pub fn expand_manifest(
@@ -123,9 +121,6 @@ fn expand_manifest_(
123121
package
124122
.entry("name".to_owned())
125123
.or_insert(toml::Value::String(name));
126-
package
127-
.entry("version".to_owned())
128-
.or_insert_with(|| toml::Value::String(DEFAULT_VERSION.to_owned()));
129124
package.entry("edition".to_owned()).or_insert_with(|| {
130125
let _ = config.shell().warn(format_args!(
131126
"`package.edition` is unspecified, defaulting to `{}`",
@@ -136,9 +131,6 @@ fn expand_manifest_(
136131
package
137132
.entry("build".to_owned())
138133
.or_insert_with(|| toml::Value::Boolean(false));
139-
package
140-
.entry("publish".to_owned())
141-
.or_insert_with(|| toml::Value::Boolean(DEFAULT_PUBLISH));
142134
for field in AUTO_FIELDS {
143135
package
144136
.entry(field.to_owned())
@@ -621,8 +613,6 @@ autotests = false
621613
build = false
622614
edition = "2021"
623615
name = "test-"
624-
publish = false
625-
version = "0.0.0"
626616
627617
[profile.release]
628618
strip = true
@@ -651,8 +641,6 @@ autotests = false
651641
build = false
652642
edition = "2021"
653643
name = "test-"
654-
publish = false
655-
version = "0.0.0"
656644
657645
[profile.release]
658646
strip = true

src/cargo/util/toml/mod.rs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,17 @@ impl TomlManifest {
550550
let version = package
551551
.version
552552
.clone()
553-
.resolve("version", || inherit()?.version())?;
553+
.map(|version| version.resolve("version", || inherit()?.version()))
554+
.transpose()?;
554555

555-
package.version = MaybeWorkspace::Defined(version.clone());
556+
package.version = version.clone().map(MaybeWorkspace::Defined);
556557

557-
let pkgid = package.to_package_id(source_id, version)?;
558+
let pkgid = package.to_package_id(
559+
source_id,
560+
version
561+
.clone()
562+
.unwrap_or_else(|| semver::Version::new(0, 0, 0)),
563+
)?;
558564

559565
let edition = if let Some(edition) = package.edition.clone() {
560566
let edition: Edition = edition
@@ -1006,9 +1012,14 @@ impl TomlManifest {
10061012
let publish = match publish {
10071013
Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()),
10081014
Some(VecStringOrBool::Bool(false)) => Some(vec![]),
1009-
None | Some(VecStringOrBool::Bool(true)) => None,
1015+
Some(VecStringOrBool::Bool(true)) => None,
1016+
None => version.is_none().then_some(vec![]),
10101017
};
10111018

1019+
if version.is_none() && publish != Some(vec![]) {
1020+
bail!("`package.publish` requires `package.version` be specified");
1021+
}
1022+
10121023
if summary.features().contains_key("default-features") {
10131024
warnings.push(
10141025
"`default-features = [\"..\"]` was found in [features]. \
@@ -1659,8 +1670,7 @@ pub struct TomlPackage {
16591670
edition: Option<MaybeWorkspaceString>,
16601671
rust_version: Option<MaybeWorkspaceRustVersion>,
16611672
name: InternedString,
1662-
#[serde(deserialize_with = "version_trim_whitespace")]
1663-
version: MaybeWorkspaceSemverVersion,
1673+
version: Option<MaybeWorkspaceSemverVersion>,
16641674
authors: Option<MaybeWorkspaceVecString>,
16651675
build: Option<StringOrBool>,
16661676
metabuild: Option<StringOrVec>,
@@ -1709,22 +1719,6 @@ impl TomlPackage {
17091719
}
17101720
}
17111721

1712-
fn version_trim_whitespace<'de, D>(deserializer: D) -> Result<MaybeWorkspaceSemverVersion, D::Error>
1713-
where
1714-
D: de::Deserializer<'de>,
1715-
{
1716-
UntaggedEnumVisitor::new()
1717-
.expecting("SemVer version")
1718-
.string(
1719-
|value| match value.trim().parse().map_err(de::Error::custom) {
1720-
Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)),
1721-
Err(e) => Err(e),
1722-
},
1723-
)
1724-
.map(|value| value.deserialize().map(MaybeWorkspace::Workspace))
1725-
.deserialize(deserializer)
1726-
}
1727-
17281722
/// This Trait exists to make [`MaybeWorkspace::Workspace`] generic. It makes deserialization of
17291723
/// [`MaybeWorkspace`] much easier, as well as making error messages for
17301724
/// [`MaybeWorkspace::resolve`] much nicer
@@ -1793,6 +1787,23 @@ impl<T, W: WorkspaceInherit> MaybeWorkspace<T, W> {
17931787

17941788
//. This already has a `Deserialize` impl from version_trim_whitespace
17951789
type MaybeWorkspaceSemverVersion = MaybeWorkspace<semver::Version, TomlWorkspaceField>;
1790+
impl<'de> de::Deserialize<'de> for MaybeWorkspaceSemverVersion {
1791+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
1792+
where
1793+
D: de::Deserializer<'de>,
1794+
{
1795+
UntaggedEnumVisitor::new()
1796+
.expecting("SemVer version")
1797+
.string(
1798+
|value| match value.trim().parse().map_err(de::Error::custom) {
1799+
Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)),
1800+
Err(e) => Err(e),
1801+
},
1802+
)
1803+
.map(|value| value.deserialize().map(MaybeWorkspace::Workspace))
1804+
.deserialize(d)
1805+
}
1806+
}
17961807

17971808
type MaybeWorkspaceString = MaybeWorkspace<String, TomlWorkspaceField>;
17981809
impl<'de> de::Deserialize<'de> for MaybeWorkspaceString {

src/doc/src/reference/manifest.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ resolve dependencies, and for guidelines on setting your own version. See the
109109
[SemVer compatibility] chapter for more details on exactly what constitutes a
110110
breaking change.
111111

112+
This field is optional and defaults to `0.0.0`. The field is required for publishing packages.
113+
112114
[Resolver]: resolver.md
113115
[SemVer compatibility]: semver.md
114116

@@ -470,23 +472,22 @@ if any of those files change.
470472

471473
### The `publish` field
472474

473-
The `publish` field can be used to prevent a package from being published to a
474-
package registry (like *crates.io*) by mistake, for instance to keep a package
475-
private in a company.
476-
475+
The `publish` field can be used to control which registries names the package
476+
may be published to:
477477
```toml
478478
[package]
479479
# ...
480-
publish = false
480+
publish = ["some-registry-name"]
481481
```
482482

483-
The value may also be an array of strings which are registry names that are
484-
allowed to be published to.
485-
483+
To prevent a package from being published to a registry (like crates.io) by mistake,
484+
for instance to keep a package private in a company,
485+
you can omit the [`version`](#the-version-field) field.
486+
If you'd like to be more explicit, you can disable publishing:
486487
```toml
487488
[package]
488489
# ...
489-
publish = ["some-registry-name"]
490+
publish = false
490491
```
491492

492493
If publish array contains a single registry, `cargo publish` command will use

src/doc/src/reference/unstable.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,9 +1221,6 @@ at the start of the infostring at the top of the file.
12211221

12221222
Inferred / defaulted manifest fields:
12231223
- `package.name = <slugified file stem>`
1224-
- `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)
1225-
- `package.publish = false` to avoid accidental publishes, particularly if we
1226-
later add support for including them in a workspace.
12271224
- `package.edition = <current>` to avoid always having to add an embedded
12281225
manifest at the cost of potentially breaking scripts on rust upgrades
12291226
- Warn when `edition` is unspecified to raise awareness of this

tests/testsuite/check.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,3 +1496,26 @@ fn check_unused_manifest_keys() {
14961496
)
14971497
.run();
14981498
}
1499+
1500+
#[cargo_test]
1501+
fn versionless_package() {
1502+
let p = project()
1503+
.file(
1504+
"Cargo.toml",
1505+
r#"
1506+
[package]
1507+
name = "foo"
1508+
description = "foo"
1509+
"#,
1510+
)
1511+
.file("src/lib.rs", "")
1512+
.build();
1513+
p.cargo("check")
1514+
.with_stderr(
1515+
"\
1516+
[CHECKING] foo v0.0.0 ([CWD])
1517+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1518+
",
1519+
)
1520+
.run();
1521+
}

0 commit comments

Comments
 (0)