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