Skip to content

Commit c221fec

Browse files
committed
Implement support for rust-version field in project metadata
1 parent 50cc40b commit c221fec

File tree

21 files changed

+445
-6
lines changed

21 files changed

+445
-6
lines changed

crates/cargo-test-support/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ lazy_static = "1.0"
1919
remove_dir_all = "0.5"
2020
serde_json = "1.0"
2121
tar = { version = "0.4.18", default-features = false }
22+
toml = "0.5.7"
2223
url = "2.0"

crates/cargo-test-support/src/registry.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ pub struct Package {
146146
invalid_json: bool,
147147
proc_macro: bool,
148148
links: Option<String>,
149+
rust_version: Option<String>,
150+
cargo_features: Vec<String>,
149151
}
150152

151153
#[derive(Clone)]
@@ -247,6 +249,8 @@ impl Package {
247249
invalid_json: false,
248250
proc_macro: false,
249251
links: None,
252+
rust_version: None,
253+
cargo_features: Vec::new(),
250254
}
251255
}
252256

@@ -363,6 +367,12 @@ impl Package {
363367
self
364368
}
365369

370+
/// Specify a minimal Rust version.
371+
pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
372+
self.rust_version = Some(rust_version.into());
373+
self
374+
}
375+
366376
/// Causes the JSON line emitted in the index to be invalid, presumably
367377
/// causing Cargo to skip over this version.
368378
pub fn invalid_json(&mut self, invalid: bool) -> &mut Package {
@@ -375,6 +385,11 @@ impl Package {
375385
self
376386
}
377387

388+
pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
389+
self.cargo_features.push(feature.to_owned());
390+
self
391+
}
392+
378393
/// Creates the package and place it in the registry.
379394
///
380395
/// This does not actually use Cargo's publishing system, but instead
@@ -502,15 +517,29 @@ impl Package {
502517
}
503518

504519
fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
505-
let mut manifest = format!(
520+
let mut manifest = String::new();
521+
522+
if !self.cargo_features.is_empty() {
523+
manifest.push_str(&format!(
524+
"cargo-features = {}\n\n",
525+
toml::to_string(&self.cargo_features).unwrap()
526+
));
527+
}
528+
529+
manifest.push_str(&format!(
506530
r#"
507531
[package]
508532
name = "{}"
509533
version = "{}"
510534
authors = []
511535
"#,
512536
self.name, self.vers
513-
);
537+
));
538+
539+
if let Some(version) = &self.rust_version {
540+
manifest.push_str(&format!("rust-version = \"{}\"", version));
541+
}
542+
514543
for dep in self.deps.iter() {
515544
let target = match dep.target {
516545
None => String::new(),

src/bin/cargo/commands/bench.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn cli() -> App {
3939
.arg_target_triple("Build for the target triple")
4040
.arg_target_dir()
4141
.arg_manifest_path()
42+
.arg_ignore_rust_version()
4243
.arg_message_format()
4344
.arg(opt(
4445
"no-fail-fast",

src/bin/cargo/commands/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn cli() -> App {
3939
.value_name("PATH"),
4040
)
4141
.arg_manifest_path()
42+
.arg_ignore_rust_version()
4243
.arg_message_format()
4344
.arg_build_plan()
4445
.arg_unit_graph()

src/bin/cargo/commands/check.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub fn cli() -> App {
3232
.arg_target_triple("Check for the target triple")
3333
.arg_target_dir()
3434
.arg_manifest_path()
35+
.arg_ignore_rust_version()
3536
.arg_message_format()
3637
.arg_unit_graph()
3738
.after_help("Run `cargo help check` for more detailed information.\n")

src/bin/cargo/commands/doc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub fn cli() -> App {
3030
.arg_target_dir()
3131
.arg_manifest_path()
3232
.arg_message_format()
33+
.arg_ignore_rust_version()
3334
.arg_unit_graph()
3435
.after_help("Run `cargo help doc` for more detailed information.\n")
3536
}

src/bin/cargo/commands/fix.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub fn cli() -> App {
7272
.long("allow-staged")
7373
.help("Fix code even if the working directory has staged changes"),
7474
)
75+
.arg_ignore_rust_version()
7576
.after_help("Run `cargo help fix` for more detailed information.\n")
7677
}
7778

src/bin/cargo/commands/run.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn cli() -> App {
2626
.arg_manifest_path()
2727
.arg_message_format()
2828
.arg_unit_graph()
29+
.arg_ignore_rust_version()
2930
.after_help("Run `cargo help run` for more detailed information.\n")
3031
}
3132

src/bin/cargo/commands/rustc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub fn cli() -> App {
3030
.arg_manifest_path()
3131
.arg_message_format()
3232
.arg_unit_graph()
33+
.arg_ignore_rust_version()
3334
.after_help("Run `cargo help rustc` for more detailed information.\n")
3435
}
3536

src/bin/cargo/commands/rustdoc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub fn cli() -> App {
3434
.arg_manifest_path()
3535
.arg_message_format()
3636
.arg_unit_graph()
37+
.arg_ignore_rust_version()
3738
.after_help("Run `cargo help rustdoc` for more detailed information.\n")
3839
}
3940

src/bin/cargo/commands/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub fn cli() -> App {
5353
.arg_target_triple("Build for the target triple")
5454
.arg_target_dir()
5555
.arg_manifest_path()
56+
.arg_ignore_rust_version()
5657
.arg_message_format()
5758
.arg_unit_graph()
5859
.after_help("Run `cargo help test` for more detailed information.\n")

src/cargo/core/features.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ pub enum Edition {
7070
Edition2021,
7171
}
7272

73+
impl Edition {
74+
pub(crate) fn first_version(&self) -> Option<semver::Version> {
75+
use Edition::*;
76+
match self {
77+
Edition2015 => None,
78+
Edition2018 => Some(semver::Version::new(1, 31, 0)),
79+
Edition2021 => Some(semver::Version::new(1, 62, 0)),
80+
}
81+
}
82+
}
83+
7384
impl fmt::Display for Edition {
7485
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7586
match *self {
@@ -218,6 +229,9 @@ features! {
218229

219230
// Allow to specify whether binaries should be stripped.
220231
[unstable] strip: bool,
232+
233+
// Specifying a minimal 'rust-version' attribute for crates
234+
[unstable] rust_version: bool,
221235
}
222236
}
223237

src/cargo/core/manifest.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub struct Manifest {
4646
original: Rc<TomlManifest>,
4747
unstable_features: Features,
4848
edition: Edition,
49+
rust_version: Option<String>,
4950
im_a_teapot: Option<bool>,
5051
default_run: Option<String>,
5152
metabuild: Option<Vec<String>>,
@@ -379,6 +380,7 @@ impl Manifest {
379380
workspace: WorkspaceConfig,
380381
unstable_features: Features,
381382
edition: Edition,
383+
rust_version: Option<String>,
382384
im_a_teapot: Option<bool>,
383385
default_run: Option<String>,
384386
original: Rc<TomlManifest>,
@@ -401,6 +403,7 @@ impl Manifest {
401403
workspace,
402404
unstable_features,
403405
edition,
406+
rust_version,
404407
original,
405408
im_a_teapot,
406409
default_run,
@@ -520,6 +523,10 @@ impl Manifest {
520523
self.edition
521524
}
522525

526+
pub fn rust_version(&self) -> Option<&str> {
527+
self.rust_version.as_deref()
528+
}
529+
523530
pub fn custom_metadata(&self) -> Option<&toml::Value> {
524531
self.custom_metadata.as_ref()
525532
}

src/cargo/core/package.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ impl Package {
170170
pub fn proc_macro(&self) -> bool {
171171
self.targets().iter().any(|target| target.proc_macro())
172172
}
173+
/// Gets the package's minimum Rust version.
174+
pub fn rust_version(&self) -> Option<&str> {
175+
self.manifest().rust_version()
176+
}
173177

174178
/// Returns `true` if the package uses a custom build script for any target.
175179
pub fn has_custom_build(&self) -> bool {

src/cargo/ops/cargo_compile.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub struct CompileOptions {
7878
/// Whether the `--document-private-items` flags was specified and should
7979
/// be forwarded to `rustdoc`.
8080
pub rustdoc_document_private_items: bool,
81+
/// Whether the build process should check the minimum Rust version
82+
/// defined in the cargo metadata for a crate.
83+
pub honor_rust_version: bool,
8184
}
8285

8386
impl<'a> CompileOptions {
@@ -95,6 +98,7 @@ impl<'a> CompileOptions {
9598
target_rustc_args: None,
9699
local_rustdoc_args: None,
97100
rustdoc_document_private_items: false,
101+
honor_rust_version: true,
98102
})
99103
}
100104
}
@@ -306,6 +310,7 @@ pub fn create_bcx<'a, 'cfg>(
306310
ref target_rustc_args,
307311
ref local_rustdoc_args,
308312
rustdoc_document_private_items,
313+
honor_rust_version,
309314
} = *options;
310315
let config = ws.config();
311316

@@ -551,6 +556,36 @@ pub fn create_bcx<'a, 'cfg>(
551556
}
552557
}
553558

559+
if honor_rust_version {
560+
// Remove any pre-release identifiers for easier comparison
561+
let current_version = &target_data.rustc.version;
562+
let untagged_version = semver::Version::new(
563+
current_version.major,
564+
current_version.minor,
565+
current_version.patch,
566+
);
567+
568+
for unit in unit_graph.keys() {
569+
let version = match unit.pkg.rust_version() {
570+
Some(v) => v,
571+
None => continue,
572+
};
573+
574+
let req = semver::VersionReq::parse(version).unwrap();
575+
if req.matches(&untagged_version) {
576+
continue;
577+
}
578+
579+
anyhow::bail!(
580+
"package `{}` cannot be built because it requires rustc {} or newer, \
581+
while the currently active rustc version is {}",
582+
unit.pkg,
583+
version,
584+
current_version,
585+
);
586+
}
587+
}
588+
554589
let bcx = BuildContext::new(
555590
ws,
556591
pkg_set,

src/cargo/ops/cargo_package.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
701701
target_rustc_args: rustc_args,
702702
local_rustdoc_args: None,
703703
rustdoc_document_private_items: false,
704+
honor_rust_version: true,
704705
},
705706
&exec,
706707
)?;

src/cargo/util/command_prelude.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ pub trait AppExt: Sized {
212212
fn arg_dry_run(self, dry_run: &'static str) -> Self {
213213
self._arg(opt("dry-run", dry_run))
214214
}
215+
216+
fn arg_ignore_rust_version(self) -> Self {
217+
self._arg(
218+
opt(
219+
"ignore-rust-version",
220+
"Ignore `rust-version` specification in packages",
221+
)
222+
.hidden(true), // nightly only (`rust-version` feature)
223+
)
224+
}
215225
}
216226

217227
impl AppExt for App {
@@ -488,8 +498,15 @@ pub trait ArgMatchesExt {
488498
target_rustc_args: None,
489499
local_rustdoc_args: None,
490500
rustdoc_document_private_items: false,
501+
honor_rust_version: !self._is_present("ignore-rust-version"),
491502
};
492503

504+
if !opts.honor_rust_version {
505+
config
506+
.cli_unstable()
507+
.fail_if_stable_opt("--ignore-rust-version", 8072)?;
508+
}
509+
493510
if let Some(ws) = workspace {
494511
self.check_optional_opts(ws, &opts)?;
495512
} else if self.is_present_with_zero_values("package") {

0 commit comments

Comments
 (0)