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