1use std::collections::BTreeSet;
122use std::env;
123use std::fmt::{self, Write};
124use std::path::PathBuf;
125use std::str::FromStr;
126
127use anyhow::{Error, bail};
128use cargo_util::ProcessBuilder;
129use serde::{Deserialize, Serialize};
130
131use crate::GlobalContext;
132use crate::core::resolver::ResolveBehavior;
133use crate::util::errors::CargoResult;
134use crate::util::indented_lines;
135
136pub const SEE_CHANNELS: &str = "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
137 about Rust release channels.";
138
139pub type AllowFeatures = BTreeSet<String>;
141
142#[derive(
180 Default, Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize,
181)]
182pub enum Edition {
183 #[default]
185 Edition2015,
186 Edition2018,
188 Edition2021,
190 Edition2024,
192 EditionFuture,
194}
195
196impl Edition {
197 pub const LATEST_UNSTABLE: Option<Edition> = None;
204 pub const LATEST_STABLE: Edition = Edition::Edition2024;
206 pub const ALL: &'static [Edition] = &[
207 Self::Edition2015,
208 Self::Edition2018,
209 Self::Edition2021,
210 Self::Edition2024,
211 Self::EditionFuture,
212 ];
213 pub const CLI_VALUES: [&'static str; 4] = ["2015", "2018", "2021", "2024"];
221
222 pub(crate) fn first_version(&self) -> Option<semver::Version> {
225 use Edition::*;
226 match self {
227 Edition2015 => None,
228 Edition2018 => Some(semver::Version::new(1, 31, 0)),
229 Edition2021 => Some(semver::Version::new(1, 56, 0)),
230 Edition2024 => Some(semver::Version::new(1, 85, 0)),
231 EditionFuture => None,
232 }
233 }
234
235 pub fn is_stable(&self) -> bool {
237 use Edition::*;
238 match self {
239 Edition2015 => true,
240 Edition2018 => true,
241 Edition2021 => true,
242 Edition2024 => true,
243 EditionFuture => false,
244 }
245 }
246
247 pub fn previous(&self) -> Option<Edition> {
251 use Edition::*;
252 match self {
253 Edition2015 => None,
254 Edition2018 => Some(Edition2015),
255 Edition2021 => Some(Edition2018),
256 Edition2024 => Some(Edition2021),
257 EditionFuture => panic!("future does not have a previous edition"),
258 }
259 }
260
261 pub fn saturating_next(&self) -> Edition {
264 use Edition::*;
265 match self {
267 Edition2015 => Edition2018,
268 Edition2018 => Edition2021,
269 Edition2021 => Edition2024,
270 Edition2024 => Edition2024,
271 EditionFuture => EditionFuture,
272 }
273 }
274
275 pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) {
278 cmd.arg(format!("--edition={}", self));
279 if !self.is_stable() {
280 cmd.arg("-Z").arg("unstable-options");
281 }
282 }
283
284 pub(crate) fn force_warn_arg(&self, cmd: &mut ProcessBuilder) {
286 use Edition::*;
287 match self {
288 Edition2015 => {}
289 EditionFuture => {
290 cmd.arg("--force-warn=edition_future_compatibility");
291 }
292 e => {
293 cmd.arg(format!("--force-warn=rust-{e}-compatibility"));
300 }
301 }
302 }
303
304 pub(crate) fn supports_idiom_lint(&self) -> bool {
308 use Edition::*;
309 match self {
310 Edition2015 => false,
311 Edition2018 => true,
312 Edition2021 => false,
313 Edition2024 => false,
314 EditionFuture => false,
315 }
316 }
317
318 pub(crate) fn default_resolve_behavior(&self) -> ResolveBehavior {
319 if *self >= Edition::Edition2024 {
320 ResolveBehavior::V3
321 } else if *self >= Edition::Edition2021 {
322 ResolveBehavior::V2
323 } else {
324 ResolveBehavior::V1
325 }
326 }
327}
328
329impl fmt::Display for Edition {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match *self {
332 Edition::Edition2015 => f.write_str("2015"),
333 Edition::Edition2018 => f.write_str("2018"),
334 Edition::Edition2021 => f.write_str("2021"),
335 Edition::Edition2024 => f.write_str("2024"),
336 Edition::EditionFuture => f.write_str("future"),
337 }
338 }
339}
340
341impl FromStr for Edition {
342 type Err = Error;
343 fn from_str(s: &str) -> Result<Self, Error> {
344 match s {
345 "2015" => Ok(Edition::Edition2015),
346 "2018" => Ok(Edition::Edition2018),
347 "2021" => Ok(Edition::Edition2021),
348 "2024" => Ok(Edition::Edition2024),
349 "future" => Ok(Edition::EditionFuture),
350 s if s.parse().map_or(false, |y: u16| y > 2024 && y < 2050) => bail!(
351 "this version of Cargo is older than the `{}` edition, \
352 and only supports `2015`, `2018`, `2021`, and `2024` editions.",
353 s
354 ),
355 s => bail!(
356 "supported edition values are `2015`, `2018`, `2021`, or `2024`, \
357 but `{}` is unknown",
358 s
359 ),
360 }
361 }
362}
363
364#[derive(Debug, Deserialize)]
366pub enum FixEdition {
367 Start(Edition),
374 End { initial: Edition, next: Edition },
382}
383
384impl FromStr for FixEdition {
385 type Err = anyhow::Error;
386 fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
387 if let Some(start) = s.strip_prefix("start=") {
388 Ok(FixEdition::Start(start.parse()?))
389 } else if let Some(end) = s.strip_prefix("end=") {
390 let (initial, next) = end
391 .split_once(',')
392 .ok_or_else(|| anyhow::format_err!("expected `initial,next`"))?;
393 Ok(FixEdition::End {
394 initial: initial.parse()?,
395 next: next.parse()?,
396 })
397 } else {
398 bail!("invalid `-Zfix-edition, expected start= or end=, got `{s}`");
399 }
400 }
401}
402
403#[derive(Debug, PartialEq)]
404enum Status {
405 Stable,
406 Unstable,
407 Removed,
408}
409
410macro_rules! features {
422 (
423 $(
424 $(#[$attr:meta])*
425 ($stab:ident, $feature:ident, $version:expr, $docs:expr),
426 )*
427 ) => (
428 #[derive(Default, Clone, Debug)]
433 pub struct Features {
434 $($feature: bool,)*
435 activated: Vec<String>,
437 nightly_features_allowed: bool,
439 is_local: bool,
441 }
442
443 impl Feature {
444 $(
445 $(#[$attr])*
446 #[doc = concat!("\n\n\nSee <https://doc.rust-lang.org/nightly/cargo/", $docs, ">.")]
447 pub const fn $feature() -> &'static Feature {
448 fn get(features: &Features) -> bool {
449 stab!($stab) == Status::Stable || features.$feature
450 }
451 const FEAT: Feature = Feature {
452 name: stringify!($feature),
453 stability: stab!($stab),
454 version: $version,
455 docs: $docs,
456 get,
457 };
458 &FEAT
459 }
460 )*
461
462 fn is_enabled(&self, features: &Features) -> bool {
464 (self.get)(features)
465 }
466
467 pub(crate) fn name(&self) -> &str {
468 self.name
469 }
470 }
471
472 impl Features {
473 fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> {
474 if feature.contains("_") {
475 return None;
476 }
477 let feature = feature.replace("-", "_");
478 $(
479 if feature == stringify!($feature) {
480 return Some((&mut self.$feature, Feature::$feature()));
481 }
482 )*
483 None
484 }
485 }
486 )
487}
488
489macro_rules! stab {
490 (stable) => {
491 Status::Stable
492 };
493 (unstable) => {
494 Status::Unstable
495 };
496 (removed) => {
497 Status::Removed
498 };
499}
500
501features! {
503 (stable, test_dummy_stable, "1.0", ""),
506
507 (unstable, test_dummy_unstable, "", "reference/unstable.html"),
510
511 (stable, alternative_registries, "1.34", "reference/registries.html"),
513
514 (stable, edition, "1.31", "reference/manifest.html#the-edition-field"),
516
517 (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"),
519
520 (removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"),
522
523 (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"),
525
526 (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"),
528
529 (unstable, metabuild, "", "reference/unstable.html#metabuild"),
531
532 (unstable, public_dependency, "", "reference/unstable.html#public-dependency"),
534
535 (stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"),
537
538 (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"),
540
541 (stable, strip, "1.58", "reference/profiles.html#strip-option"),
543
544 (stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"),
546
547 (stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"),
549
550 (unstable, per_package_target, "", "reference/unstable.html#per-package-target"),
552
553 (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"),
555
556 (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"),
558
559 (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"),
561
562 (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"),
564
565 (stable, edition2024, "1.85", "reference/manifest.html#the-edition-field"),
567
568 (unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"),
570
571 (unstable, open_namespaces, "", "reference/unstable.html#open-namespaces"),
573
574 (unstable, path_bases, "", "reference/unstable.html#path-bases"),
576
577 (unstable, unstable_editions, "", "reference/unstable.html#unstable-editions"),
579
580 (unstable, multiple_build_scripts, "", "reference/unstable.html#multiple-build-scripts"),
582
583 (unstable, panic_immediate_abort, "", "reference/unstable.html#panic-immediate-abort"),
585}
586
587#[derive(Debug)]
589pub struct Feature {
590 name: &'static str,
592 stability: Status,
593 version: &'static str,
595 docs: &'static str,
597 get: fn(&Features) -> bool,
598}
599
600impl Features {
601 pub fn new(
603 features: &[String],
604 gctx: &GlobalContext,
605 warnings: &mut Vec<String>,
606 is_local: bool,
607 ) -> CargoResult<Features> {
608 let mut ret = Features::default();
609 ret.nightly_features_allowed = gctx.nightly_features_allowed;
610 ret.is_local = is_local;
611 for feature in features {
612 ret.add(feature, gctx, warnings)?;
613 ret.activated.push(feature.to_string());
614 }
615 Ok(ret)
616 }
617
618 fn add(
619 &mut self,
620 feature_name: &str,
621 gctx: &GlobalContext,
622 warnings: &mut Vec<String>,
623 ) -> CargoResult<()> {
624 let nightly_features_allowed = self.nightly_features_allowed;
625 let Some((slot, feature)) = self.status(feature_name) else {
626 let mut msg = format!("unknown Cargo.toml feature `{feature_name}`\n\n");
627 let mut append_see_docs = true;
628
629 if feature_name.contains('_') {
630 let _ = writeln!(msg, "Feature names must use '-' instead of '_'.");
631 append_see_docs = false;
632 } else {
633 let underscore_name = feature_name.replace('-', "_");
634 if CliUnstable::help()
635 .iter()
636 .any(|(option, _)| *option == underscore_name)
637 {
638 let _ = writeln!(
639 msg,
640 "This feature can be enabled via -Z{feature_name} or the `[unstable]` section in config.toml."
641 );
642 }
643 }
644
645 if append_see_docs {
646 let _ = writeln!(
647 msg,
648 "See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information."
649 );
650 }
651 bail!(msg)
652 };
653
654 if *slot {
655 bail!(
656 "the cargo feature `{}` has already been activated",
657 feature_name
658 );
659 }
660
661 let see_docs = || {
662 format!(
663 "See {} for more information about using this feature.",
664 cargo_docs_link(feature.docs)
665 )
666 };
667
668 match feature.stability {
669 Status::Stable => {
670 let warning = format!(
671 "the cargo feature `{}` has been stabilized in the {} \
672 release and is no longer necessary to be listed in the \
673 manifest\n {}",
674 feature_name,
675 feature.version,
676 see_docs()
677 );
678 warnings.push(warning);
679 }
680 Status::Unstable if !nightly_features_allowed => bail!(
681 "the cargo feature `{}` requires a nightly version of \
682 Cargo, but this is the `{}` channel\n\
683 {}\n{}",
684 feature_name,
685 channel(),
686 SEE_CHANNELS,
687 see_docs()
688 ),
689 Status::Unstable => {
690 if let Some(allow) = &gctx.cli_unstable().allow_features {
691 if !allow.contains(feature_name) {
692 bail!(
693 "the feature `{}` is not in the list of allowed features: [{}]",
694 feature_name,
695 itertools::join(allow, ", "),
696 );
697 }
698 }
699 }
700 Status::Removed => {
701 let mut msg = format!(
702 "the cargo feature `{}` has been removed in the {} release\n\n",
703 feature_name, feature.version
704 );
705 if self.is_local {
706 let _ = writeln!(
707 msg,
708 "Remove the feature from Cargo.toml to remove this error."
709 );
710 } else {
711 let _ = writeln!(
712 msg,
713 "This package cannot be used with this version of Cargo, \
714 as the unstable feature `{}` is no longer supported.",
715 feature_name
716 );
717 }
718 let _ = writeln!(msg, "{}", see_docs());
719 bail!(msg);
720 }
721 }
722
723 *slot = true;
724
725 Ok(())
726 }
727
728 pub fn activated(&self) -> &[String] {
730 &self.activated
731 }
732
733 pub fn require(&self, feature: &Feature) -> CargoResult<()> {
735 if feature.is_enabled(self) {
736 return Ok(());
737 }
738 let feature_name = feature.name.replace("_", "-");
739 let mut msg = format!(
740 "feature `{}` is required\n\
741 \n\
742 The package requires the Cargo feature called `{}`, but \
743 that feature is not stabilized in this version of Cargo ({}).\n\
744 ",
745 feature_name,
746 feature_name,
747 crate::version(),
748 );
749
750 if self.nightly_features_allowed {
751 if self.is_local {
752 let _ = writeln!(
753 msg,
754 "Consider adding `cargo-features = [\"{}\"]` \
755 to the top of Cargo.toml (above the [package] table) \
756 to tell Cargo you are opting in to use this unstable feature.",
757 feature_name
758 );
759 } else {
760 let _ = writeln!(msg, "Consider trying a more recent nightly release.");
761 }
762 } else {
763 let _ = writeln!(
764 msg,
765 "Consider trying a newer version of Cargo \
766 (this may require the nightly release)."
767 );
768 }
769 let _ = writeln!(
770 msg,
771 "See https://doc.rust-lang.org/nightly/cargo/{} for more information \
772 about the status of this feature.",
773 feature.docs
774 );
775
776 bail!("{}", msg);
777 }
778
779 pub fn is_enabled(&self, feature: &Feature) -> bool {
781 feature.is_enabled(self)
782 }
783}
784
785macro_rules! unstable_cli_options {
789 (
790 $(
791 $(#[$meta:meta])?
792 $element: ident: $ty: ty$( = ($help:literal))?,
793 )*
794 ) => {
795 #[derive(Default, Debug, Deserialize)]
801 #[serde(default, rename_all = "kebab-case")]
802 pub struct CliUnstable {
803 $(
804 $(#[doc = $help])?
805 $(#[$meta])?
806 pub $element: $ty
807 ),*
808 }
809 impl CliUnstable {
810 pub fn help() -> Vec<(&'static str, Option<&'static str>)> {
812 let fields = vec![$((stringify!($element), None$(.or(Some($help)))?)),*];
813 fields
814 }
815 }
816
817 #[cfg(test)]
818 mod test {
819 #[test]
820 fn ensure_sorted() {
821 let location = std::panic::Location::caller();
823 println!(
824 "\nTo fix this test, sort the features inside the macro at {}:{}\n",
825 location.file(),
826 location.line()
827 );
828 let mut expected = vec![$(stringify!($element)),*];
829 expected[2..].sort();
830 let expected = format!("{:#?}", expected);
831 let actual = format!("{:#?}", vec![$(stringify!($element)),*]);
832 snapbox::assert_data_eq!(actual, expected);
833 }
834 }
835 }
836}
837
838unstable_cli_options!(
839 allow_features: Option<AllowFeatures> = ("Allow *only* the listed unstable features"),
841 print_im_a_teapot: bool,
842
843 advanced_env: bool,
846 asymmetric_token: bool = ("Allows authenticating with asymmetric tokens"),
847 avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"),
848 binary_dep_depinfo: bool = ("Track changes to dependency artifacts"),
849 bindeps: bool = ("Allow Cargo packages to depend on bin, cdylib, and staticlib crates, and use the artifacts built by those crates"),
850 build_analysis: bool = ("Record and persist build metrics across runs, with commands to query past builds."),
851 build_dir_new_layout: bool = ("Use the new build-dir filesystem layout"),
852 #[serde(deserialize_with = "deserialize_comma_separated_list")]
853 build_std: Option<Vec<String>> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
854 #[serde(deserialize_with = "deserialize_comma_separated_list")]
855 build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"),
856 cargo_lints: bool = ("Enable the `[lints.cargo]` table"),
857 checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"),
858 codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
859 config_include: bool = ("Enable the `include` key in config files"),
860 direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
861 dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
862 feature_unification: bool = ("Enable new feature unification modes in workspaces"),
863 features: Option<Vec<String>>,
864 fix_edition: Option<FixEdition> = ("Permanently unstable edition migration helper"),
865 gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
866 #[serde(deserialize_with = "deserialize_git_features")]
867 git: Option<GitFeatures> = ("Enable support for shallow git fetch operations"),
868 #[serde(deserialize_with = "deserialize_gitoxide_features")]
869 gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
870 host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
871 minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
872 msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
873 mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
874 next_lockfile_bump: bool,
875 no_embed_metadata: bool = ("Avoid embedding metadata in library artifacts"),
876 no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
877 panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
878 panic_immediate_abort: bool = ("Enable setting `panic = \"immediate-abort\"` in profiles"),
879 profile_hint_mostly_unused: bool = ("Enable the `hint-mostly-unused` setting in profiles to mark a crate as mostly unused."),
880 profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
881 public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
882 publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
883 root_dir: Option<PathBuf> = ("Set the root directory relative to which paths are printed (defaults to workspace root)"),
884 rustc_unicode: bool = ("Enable `rustc`'s unicode error format in Cargo's error messages"),
885 rustdoc_depinfo: bool = ("Use dep-info files in rustdoc rebuild detection"),
886 rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
887 rustdoc_mergeable_info: bool = ("Use rustdoc mergeable cross-crate-info files"),
888 rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
889 sbom: bool = ("Enable the `sbom` option in build config in .cargo/config.toml file"),
890 script: bool = ("Enable support for single-file, `.rs` packages"),
891 section_timings: bool = ("Enable support for extended compilation sections in --timings output"),
892 separate_nightlies: bool,
893 skip_rustdoc_fingerprint: bool,
894 target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
895 trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
896 unstable_options: bool = ("Allow the usage of unstable options"),
897 warnings: bool = ("Allow use of the build.warnings config key"),
898);
899
900const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
901 enabled when used on an interactive console.\n\
902 See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
903 for information on controlling the progress bar.";
904
905const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
906 --offline CLI option";
907
908const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
909
910const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
911 they appear out of date.\n\
912 See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
913 information on how upgrading works.";
914
915const STABILIZED_CONFIG_PROFILE: &str = "See \
916 https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
917 information about specifying profiles in config.";
918
919const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
920 automatically added to the documentation.";
921
922const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
923 available in virtual workspaces, and `member/feature-name` syntax is also \
924 always available. Other extensions require setting `resolver = \"2\"` in \
925 Cargo.toml.\n\
926 See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
927 for more information.";
928
929const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
930 by specifying `resolver = \"2\"` in Cargo.toml.\n\
931 See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
932 for more information.";
933
934const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \
935 supported without passing this flag.";
936
937const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled.";
938
939const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled.";
940
941const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\
942 See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \
943 for more information";
944
945const STABILIZED_DOCTEST_IN_WORKSPACE: &str =
946 "The doctest-in-workspace feature is now always enabled.";
947
948const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
949 "The future-incompat-report feature is now always enabled.";
950
951const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
952
953const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
954
955const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings.";
956
957const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available.";
958
959const STABILIZED_TERMINAL_WIDTH: &str =
960 "The -Zterminal-width option is now always enabled for terminal output.";
961
962const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";
963
964const STABILIZED_CREDENTIAL_PROCESS: &str =
965 "Authentication with a credential provider is always available.";
966
967const STABILIZED_REGISTRY_AUTH: &str =
968 "Authenticated registries are available if a credential provider is configured.";
969
970const STABILIZED_LINTS: &str = "The `[lints]` table is now always available.";
971
972const STABILIZED_CHECK_CFG: &str =
973 "Compile-time checking of conditional (a.k.a. `-Zcheck-cfg`) is now always enabled.";
974
975const STABILIZED_DOCTEST_XCOMPILE: &str = "Doctest cross-compiling is now always enabled.";
976
977const STABILIZED_PACKAGE_WORKSPACE: &str =
978 "Workspace packaging and publishing (a.k.a. `-Zpackage-workspace`) is now always enabled.";
979
980const STABILIZED_BUILD_DIR: &str = "build.build-dir is now always enabled.";
981
982fn deserialize_comma_separated_list<'de, D>(
983 deserializer: D,
984) -> Result<Option<Vec<String>>, D::Error>
985where
986 D: serde::Deserializer<'de>,
987{
988 let Some(list) = <Option<Vec<String>>>::deserialize(deserializer)? else {
989 return Ok(None);
990 };
991 let v = list
992 .iter()
993 .flat_map(|s| s.split(','))
994 .filter(|s| !s.is_empty())
995 .map(String::from)
996 .collect();
997 Ok(Some(v))
998}
999
1000#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
1001#[serde(default)]
1002pub struct GitFeatures {
1003 pub shallow_index: bool,
1005 pub shallow_deps: bool,
1007}
1008
1009impl GitFeatures {
1010 pub fn all() -> Self {
1011 GitFeatures {
1012 shallow_index: true,
1013 shallow_deps: true,
1014 }
1015 }
1016
1017 fn expecting() -> String {
1018 let fields = ["`shallow-index`", "`shallow-deps`"];
1019 format!(
1020 "unstable 'git' only takes {} as valid inputs",
1021 fields.join(" and ")
1022 )
1023 }
1024}
1025
1026fn deserialize_git_features<'de, D>(deserializer: D) -> Result<Option<GitFeatures>, D::Error>
1027where
1028 D: serde::de::Deserializer<'de>,
1029{
1030 struct GitFeaturesVisitor;
1031
1032 impl<'de> serde::de::Visitor<'de> for GitFeaturesVisitor {
1033 type Value = Option<GitFeatures>;
1034
1035 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1036 formatter.write_str(&GitFeatures::expecting())
1037 }
1038
1039 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1040 where
1041 E: serde::de::Error,
1042 {
1043 if v {
1044 Ok(Some(GitFeatures::all()))
1045 } else {
1046 Ok(None)
1047 }
1048 }
1049
1050 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1051 where
1052 E: serde::de::Error,
1053 {
1054 Ok(parse_git(s.split(",")).map_err(serde::de::Error::custom)?)
1055 }
1056
1057 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1058 where
1059 D: serde::de::Deserializer<'de>,
1060 {
1061 let git = GitFeatures::deserialize(deserializer)?;
1062 Ok(Some(git))
1063 }
1064
1065 fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1066 where
1067 V: serde::de::MapAccess<'de>,
1068 {
1069 let mvd = serde::de::value::MapAccessDeserializer::new(map);
1070 Ok(Some(GitFeatures::deserialize(mvd)?))
1071 }
1072 }
1073
1074 deserializer.deserialize_any(GitFeaturesVisitor)
1075}
1076
1077fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<GitFeatures>> {
1078 let mut out = GitFeatures::default();
1079 let GitFeatures {
1080 shallow_index,
1081 shallow_deps,
1082 } = &mut out;
1083
1084 for e in it {
1085 match e.as_ref() {
1086 "shallow-index" => *shallow_index = true,
1087 "shallow-deps" => *shallow_deps = true,
1088 _ => {
1089 bail!(GitFeatures::expecting())
1090 }
1091 }
1092 }
1093 Ok(Some(out))
1094}
1095
1096#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
1097#[serde(default)]
1098pub struct GitoxideFeatures {
1099 pub fetch: bool,
1101 pub checkout: bool,
1104 pub internal_use_git2: bool,
1108}
1109
1110impl GitoxideFeatures {
1111 pub fn all() -> Self {
1112 GitoxideFeatures {
1113 fetch: true,
1114 checkout: true,
1115 internal_use_git2: false,
1116 }
1117 }
1118
1119 fn safe() -> Self {
1122 GitoxideFeatures {
1123 fetch: true,
1124 checkout: true,
1125 internal_use_git2: false,
1126 }
1127 }
1128
1129 fn expecting() -> String {
1130 let fields = ["`fetch`", "`checkout`", "`internal-use-git2`"];
1131 format!(
1132 "unstable 'gitoxide' only takes {} as valid inputs, for shallow fetches see `-Zgit=shallow-index,shallow-deps`",
1133 fields.join(" and ")
1134 )
1135 }
1136}
1137
1138fn deserialize_gitoxide_features<'de, D>(
1139 deserializer: D,
1140) -> Result<Option<GitoxideFeatures>, D::Error>
1141where
1142 D: serde::de::Deserializer<'de>,
1143{
1144 struct GitoxideFeaturesVisitor;
1145
1146 impl<'de> serde::de::Visitor<'de> for GitoxideFeaturesVisitor {
1147 type Value = Option<GitoxideFeatures>;
1148
1149 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1150 formatter.write_str(&GitoxideFeatures::expecting())
1151 }
1152
1153 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1154 where
1155 E: serde::de::Error,
1156 {
1157 Ok(parse_gitoxide(s.split(",")).map_err(serde::de::Error::custom)?)
1158 }
1159
1160 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1161 where
1162 E: serde::de::Error,
1163 {
1164 if v {
1165 Ok(Some(GitoxideFeatures::all()))
1166 } else {
1167 Ok(None)
1168 }
1169 }
1170
1171 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1172 where
1173 D: serde::de::Deserializer<'de>,
1174 {
1175 let gitoxide = GitoxideFeatures::deserialize(deserializer)?;
1176 Ok(Some(gitoxide))
1177 }
1178
1179 fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1180 where
1181 V: serde::de::MapAccess<'de>,
1182 {
1183 let mvd = serde::de::value::MapAccessDeserializer::new(map);
1184 Ok(Some(GitoxideFeatures::deserialize(mvd)?))
1185 }
1186 }
1187
1188 deserializer.deserialize_any(GitoxideFeaturesVisitor)
1189}
1190
1191fn parse_gitoxide(
1192 it: impl Iterator<Item = impl AsRef<str>>,
1193) -> CargoResult<Option<GitoxideFeatures>> {
1194 let mut out = GitoxideFeatures::default();
1195 let GitoxideFeatures {
1196 fetch,
1197 checkout,
1198 internal_use_git2,
1199 } = &mut out;
1200
1201 for e in it {
1202 match e.as_ref() {
1203 "fetch" => *fetch = true,
1204 "checkout" => *checkout = true,
1205 "internal-use-git2" => *internal_use_git2 = true,
1206 _ => {
1207 bail!(GitoxideFeatures::expecting())
1208 }
1209 }
1210 }
1211 Ok(Some(out))
1212}
1213
1214impl CliUnstable {
1215 pub fn parse(
1218 &mut self,
1219 flags: &[String],
1220 nightly_features_allowed: bool,
1221 ) -> CargoResult<Vec<String>> {
1222 if !flags.is_empty() && !nightly_features_allowed {
1223 bail!(
1224 "the `-Z` flag is only accepted on the nightly channel of Cargo, \
1225 but this is the `{}` channel\n\
1226 {}",
1227 channel(),
1228 SEE_CHANNELS
1229 );
1230 }
1231 let mut warnings = Vec::new();
1232 for flag in flags {
1235 if flag.starts_with("allow-features=") {
1236 self.add(flag, &mut warnings)?;
1237 }
1238 }
1239 for flag in flags {
1240 self.add(flag, &mut warnings)?;
1241 }
1242
1243 if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() {
1244 self.gitoxide = GitoxideFeatures::safe().into();
1245 }
1246 Ok(warnings)
1247 }
1248
1249 fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
1250 let mut parts = flag.splitn(2, '=');
1251 let k = parts.next().unwrap();
1252 let v = parts.next();
1253
1254 fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
1255 match value {
1256 None | Some("yes") => Ok(true),
1257 Some("no") => Ok(false),
1258 Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
1259 }
1260 }
1261
1262 fn parse_list(value: Option<&str>) -> Vec<String> {
1264 match value {
1265 None => Vec::new(),
1266 Some("") => Vec::new(),
1267 Some(v) => v.split(',').map(|s| s.to_string()).collect(),
1268 }
1269 }
1270
1271 fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
1273 if let Some(v) = value {
1274 bail!("flag -Z{} does not take a value, found: `{}`", key, v);
1275 }
1276 Ok(true)
1277 }
1278
1279 let mut stabilized_warn = |key: &str, version: &str, message: &str| {
1280 warnings.push(format!(
1281 "flag `-Z {}` has been stabilized in the {} release, \
1282 and is no longer necessary\n{}",
1283 key,
1284 version,
1285 indented_lines(message)
1286 ));
1287 };
1288
1289 let stabilized_err = |key: &str, version: &str, message: &str| {
1291 Err(anyhow::format_err!(
1292 "flag `-Z {}` has been stabilized in the {} release\n{}",
1293 key,
1294 version,
1295 indented_lines(message)
1296 ))
1297 };
1298
1299 if let Some(allowed) = &self.allow_features {
1300 if k != "allow-features" && !allowed.contains(k) {
1301 bail!(
1302 "the feature `{}` is not in the list of allowed features: [{}]",
1303 k,
1304 itertools::join(allowed, ", ")
1305 );
1306 }
1307 }
1308
1309 match k {
1310 "allow-features" => self.allow_features = Some(parse_list(v).into_iter().collect()),
1313 "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
1314
1315 "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
1318 "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
1319 "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
1320 "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
1321 "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
1322 "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
1323 "features" => {
1324 let feats = parse_list(v);
1332 let stab_is_not_empty = feats.iter().any(|feat| {
1333 matches!(
1334 feat.as_str(),
1335 "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
1336 )
1337 });
1338 if stab_is_not_empty || feats.is_empty() {
1339 stabilized_warn(k, "1.51", STABILIZED_FEATURES);
1341 }
1342 self.features = Some(feats);
1343 }
1344 "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
1345 "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV),
1346 "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG),
1347 "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG),
1348 "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES),
1349 "future-incompat-report" => {
1350 stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT)
1351 }
1352 "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
1353 "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS),
1354 "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
1355 "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET),
1356 "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
1357 "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
1358 "doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
1359 "credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
1360 "lints" => stabilized_warn(k, "1.74", STABILIZED_LINTS),
1361 "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),
1362 "check-cfg" => stabilized_warn(k, "1.80", STABILIZED_CHECK_CFG),
1363 "doctest-xcompile" => stabilized_warn(k, "1.89", STABILIZED_DOCTEST_XCOMPILE),
1364 "package-workspace" => stabilized_warn(k, "1.89", STABILIZED_PACKAGE_WORKSPACE),
1365 "build-dir" => stabilized_warn(k, "1.91", STABILIZED_BUILD_DIR),
1366
1367 "advanced-env" => self.advanced_env = parse_empty(k, v)?,
1370 "asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?,
1371 "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
1372 "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
1373 "bindeps" => self.bindeps = parse_empty(k, v)?,
1374 "build-analysis" => self.build_analysis = parse_empty(k, v)?,
1375 "build-dir-new-layout" => self.build_dir_new_layout = parse_empty(k, v)?,
1376 "build-std" => self.build_std = Some(parse_list(v)),
1377 "build-std-features" => self.build_std_features = Some(parse_list(v)),
1378 "cargo-lints" => self.cargo_lints = parse_empty(k, v)?,
1379 "codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
1380 "config-include" => self.config_include = parse_empty(k, v)?,
1381 "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
1382 "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
1383 "feature-unification" => self.feature_unification = parse_empty(k, v)?,
1384 "fix-edition" => {
1385 let fe = v
1386 .ok_or_else(|| anyhow::anyhow!("-Zfix-edition expected a value"))?
1387 .parse()?;
1388 self.fix_edition = Some(fe);
1389 }
1390 "gc" => self.gc = parse_empty(k, v)?,
1391 "git" => {
1392 self.git =
1393 v.map_or_else(|| Ok(Some(GitFeatures::all())), |v| parse_git(v.split(',')))?
1394 }
1395 "gitoxide" => {
1396 self.gitoxide = v.map_or_else(
1397 || Ok(Some(GitoxideFeatures::all())),
1398 |v| parse_gitoxide(v.split(',')),
1399 )?
1400 }
1401 "host-config" => self.host_config = parse_empty(k, v)?,
1402 "next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
1403 "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
1404 "msrv-policy" => self.msrv_policy = parse_empty(k, v)?,
1405 "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
1407 "no-embed-metadata" => self.no_embed_metadata = parse_empty(k, v)?,
1408 "no-index-update" => self.no_index_update = parse_empty(k, v)?,
1409 "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
1410 "public-dependency" => self.public_dependency = parse_empty(k, v)?,
1411 "profile-hint-mostly-unused" => self.profile_hint_mostly_unused = parse_empty(k, v)?,
1412 "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
1413 "trim-paths" => self.trim_paths = parse_empty(k, v)?,
1414 "publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
1415 "root-dir" => self.root_dir = v.map(|v| v.into()),
1416 "rustc-unicode" => self.rustc_unicode = parse_empty(k, v)?,
1417 "rustdoc-depinfo" => self.rustdoc_depinfo = parse_empty(k, v)?,
1418 "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
1419 "rustdoc-mergeable-info" => self.rustdoc_mergeable_info = parse_empty(k, v)?,
1420 "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
1421 "sbom" => self.sbom = parse_empty(k, v)?,
1422 "section-timings" => self.section_timings = parse_empty(k, v)?,
1423 "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
1424 "checksum-freshness" => self.checksum_freshness = parse_empty(k, v)?,
1425 "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
1426 "script" => self.script = parse_empty(k, v)?,
1427 "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
1428 "panic-immediate-abort" => self.panic_immediate_abort = parse_empty(k, v)?,
1429 "unstable-options" => self.unstable_options = parse_empty(k, v)?,
1430 "warnings" => self.warnings = parse_empty(k, v)?,
1431 _ => bail!(
1432 "\
1433 unknown `-Z` flag specified: {k}\n\n\
1434 For available unstable features, see \
1435 https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\
1436 If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"
1437 ),
1438 }
1439
1440 Ok(())
1441 }
1442
1443 pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
1446 self.fail_if_stable_opt_custom_z(flag, issue, "unstable-options", self.unstable_options)
1447 }
1448
1449 pub fn fail_if_stable_opt_custom_z(
1450 &self,
1451 flag: &str,
1452 issue: u32,
1453 z_name: &str,
1454 enabled: bool,
1455 ) -> CargoResult<()> {
1456 if !enabled {
1457 let see = format!(
1458 "See https://github.com/rust-lang/cargo/issues/{issue} for more \
1459 information about the `{flag}` flag."
1460 );
1461 let channel = channel();
1463 if channel == "nightly" || channel == "dev" {
1464 bail!(
1465 "the `{flag}` flag is unstable, pass `-Z {z_name}` to enable it\n\
1466 {see}"
1467 );
1468 } else {
1469 bail!(
1470 "the `{flag}` flag is unstable, and only available on the nightly channel \
1471 of Cargo, but this is the `{channel}` channel\n\
1472 {SEE_CHANNELS}\n\
1473 {see}"
1474 );
1475 }
1476 }
1477 Ok(())
1478 }
1479
1480 pub fn fail_if_stable_command(
1483 &self,
1484 gctx: &GlobalContext,
1485 command: &str,
1486 issue: u32,
1487 z_name: &str,
1488 enabled: bool,
1489 ) -> CargoResult<()> {
1490 if enabled {
1491 return Ok(());
1492 }
1493 let see = format!(
1494 "See https://github.com/rust-lang/cargo/issues/{} for more \
1495 information about the `cargo {}` command.",
1496 issue, command
1497 );
1498 if gctx.nightly_features_allowed {
1499 bail!(
1500 "the `cargo {command}` command is unstable, pass `-Z {z_name}` \
1501 to enable it\n\
1502 {see}",
1503 );
1504 } else {
1505 bail!(
1506 "the `cargo {}` command is unstable, and only available on the \
1507 nightly channel of Cargo, but this is the `{}` channel\n\
1508 {}\n\
1509 {}",
1510 command,
1511 channel(),
1512 SEE_CHANNELS,
1513 see
1514 );
1515 }
1516 }
1517}
1518
1519pub fn channel() -> String {
1521 #[allow(clippy::disallowed_methods)]
1523 if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
1524 return override_channel;
1525 }
1526 #[allow(clippy::disallowed_methods)]
1530 if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
1531 if staging == "1" {
1532 return "dev".to_string();
1533 }
1534 }
1535 crate::version()
1536 .release_channel
1537 .unwrap_or_else(|| String::from("dev"))
1538}
1539
1540#[allow(clippy::disallowed_methods)]
1545fn cargo_use_gitoxide_instead_of_git2() -> bool {
1546 std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
1547}
1548
1549pub fn cargo_docs_link(path: &str) -> String {
1552 let url_channel = match channel().as_str() {
1553 "dev" | "nightly" => "nightly/",
1554 "beta" => "beta/",
1555 _ => "",
1556 };
1557 format!("https://doc.rust-lang.org/{url_channel}cargo/{path}")
1558}