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};
130use tracing::debug;
131
132use crate::GlobalContext;
133use crate::resolver::ResolveBehavior;
134use crate::util::errors::CargoResult;
135use crate::util::indented_lines;
136
137pub const SEE_CHANNELS: &str = "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
411#[derive(Debug, Default, Deserialize)]
413pub enum EmbedMetadata {
414 Embed,
416 DoNotEmbed,
418 #[default]
420 Unset,
421}
422
423macro_rules! features {
435 (
436 $(
437 $(#[$attr:meta])*
438 ($stab:ident, $feature:ident, $version:expr, $docs:expr),
439 )*
440 ) => (
441 #[derive(Default, Clone, Debug)]
446 pub struct Features {
447 $($feature: bool,)*
448 activated: Vec<String>,
450 nightly_features_allowed: bool,
452 is_local: bool,
454 }
455
456 impl Feature {
457 $(
458 $(#[$attr])*
459 #[doc = concat!("\n\n\nSee <https://doc.rust-lang.org/nightly/cargo/", $docs, ">.")]
460 pub const fn $feature() -> &'static Feature {
461 fn get(features: &Features) -> bool {
462 stab!($stab) == Status::Stable || features.$feature
463 }
464 const FEAT: Feature = Feature {
465 name: stringify!($feature),
466 stability: stab!($stab),
467 version: $version,
468 docs: $docs,
469 get,
470 };
471 &FEAT
472 }
473 )*
474
475 fn is_enabled(&self, features: &Features) -> bool {
477 (self.get)(features)
478 }
479
480 pub(crate) fn name(&self) -> &str {
481 self.name
482 }
483 }
484
485 impl Features {
486 fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> {
487 if feature.contains("_") {
488 return None;
489 }
490 let feature = feature.replace("-", "_");
491 $(
492 if feature == stringify!($feature) {
493 return Some((&mut self.$feature, Feature::$feature()));
494 }
495 )*
496 None
497 }
498 }
499 )
500}
501
502macro_rules! stab {
503 (stable) => {
504 Status::Stable
505 };
506 (unstable) => {
507 Status::Unstable
508 };
509 (removed) => {
510 Status::Removed
511 };
512}
513
514features! {
516 (stable, test_dummy_stable, "1.0", ""),
519
520 (unstable, test_dummy_unstable, "", "reference/unstable.html"),
523
524 (stable, alternative_registries, "1.34", "reference/registries.html"),
526
527 (stable, edition, "1.31", "reference/manifest.html#the-edition-field"),
529
530 (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"),
532
533 (removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"),
535
536 (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"),
538
539 (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"),
541
542 (unstable, metabuild, "", "reference/unstable.html#metabuild"),
544
545 (unstable, public_dependency, "", "reference/unstable.html#public-dependency"),
547
548 (stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"),
550
551 (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"),
553
554 (stable, strip, "1.58", "reference/profiles.html#strip-option"),
556
557 (stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"),
559
560 (stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"),
562
563 (unstable, per_package_target, "", "reference/unstable.html#per-package-target"),
565
566 (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"),
568
569 (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"),
571
572 (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"),
574
575 (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"),
577
578 (stable, edition2024, "1.85", "reference/manifest.html#the-edition-field"),
580
581 (unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"),
583
584 (unstable, open_namespaces, "", "reference/unstable.html#open-namespaces"),
586
587 (unstable, path_bases, "", "reference/unstable.html#path-bases"),
589
590 (unstable, unstable_editions, "", "reference/unstable.html#unstable-editions"),
592
593 (unstable, multiple_build_scripts, "", "reference/unstable.html#multiple-build-scripts"),
595
596 (unstable, panic_immediate_abort, "", "reference/unstable.html#panic-immediate-abort"),
598}
599
600#[derive(Debug)]
602pub struct Feature {
603 name: &'static str,
605 stability: Status,
606 version: &'static str,
608 docs: &'static str,
610 get: fn(&Features) -> bool,
611}
612
613impl Features {
614 pub fn new(
616 features: &[String],
617 gctx: &GlobalContext,
618 warnings: &mut Vec<String>,
619 is_local: bool,
620 ) -> CargoResult<Features> {
621 let mut ret = Features::default();
622 ret.nightly_features_allowed = gctx.nightly_features_allowed;
623 ret.is_local = is_local;
624 for feature in features {
625 ret.add(feature, gctx, warnings)?;
626 ret.activated.push(feature.to_string());
627 }
628 Ok(ret)
629 }
630
631 fn add(
632 &mut self,
633 feature_name: &str,
634 gctx: &GlobalContext,
635 warnings: &mut Vec<String>,
636 ) -> CargoResult<()> {
637 let nightly_features_allowed = self.nightly_features_allowed;
638 let Some((slot, feature)) = self.status(feature_name) else {
639 let mut msg = format!("unknown Cargo.toml feature `{feature_name}`\n\n");
640 let mut append_see_docs = true;
641
642 if feature_name.contains('_') {
643 let _ = writeln!(msg, "Feature names must use '-' instead of '_'.");
644 append_see_docs = false;
645 } else {
646 let underscore_name = feature_name.replace('-', "_");
647 if CliUnstable::help()
648 .iter()
649 .any(|(option, _)| *option == underscore_name)
650 {
651 let _ = writeln!(
652 msg,
653 "This feature can be enabled via -Z{feature_name} or the `[unstable]` section in config.toml."
654 );
655 }
656 }
657
658 if append_see_docs {
659 let _ = writeln!(
660 msg,
661 "See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information."
662 );
663 }
664 bail!(msg)
665 };
666
667 if *slot {
668 bail!(
669 "the cargo feature `{}` has already been activated",
670 feature_name
671 );
672 }
673
674 let see_docs = || {
675 format!(
676 "See {} for more information about using this feature.",
677 cargo_docs_link(feature.docs)
678 )
679 };
680
681 match feature.stability {
682 Status::Stable => {
683 let warning = format!(
684 "the cargo feature `{}` has been stabilized in the {} \
685 release and is no longer necessary to be listed in the \
686 manifest\n {}",
687 feature_name,
688 feature.version,
689 see_docs()
690 );
691 warnings.push(warning);
692 }
693 Status::Unstable if !nightly_features_allowed => bail!(
694 "the cargo feature `{}` requires a nightly version of \
695 Cargo, but this is the `{}` channel\n\
696 {}\n{}",
697 feature_name,
698 channel(),
699 SEE_CHANNELS,
700 see_docs()
701 ),
702 Status::Unstable => {
703 if let Some(allow) = &gctx.cli_unstable().allow_features {
704 if !allow.contains(feature_name) {
705 bail!(
706 "the feature `{}` is not in the list of allowed features: [{}]",
707 feature_name,
708 itertools::join(allow, ", "),
709 );
710 }
711 }
712 }
713 Status::Removed => {
714 let mut msg = format!(
715 "the cargo feature `{}` has been removed in the {} release\n\n",
716 feature_name, feature.version
717 );
718 if self.is_local {
719 let _ = writeln!(
720 msg,
721 "Remove the feature from Cargo.toml to remove this error."
722 );
723 } else {
724 let _ = writeln!(
725 msg,
726 "This package cannot be used with this version of Cargo, \
727 as the unstable feature `{}` is no longer supported.",
728 feature_name
729 );
730 }
731 let _ = writeln!(msg, "{}", see_docs());
732 bail!(msg);
733 }
734 }
735
736 *slot = true;
737
738 Ok(())
739 }
740
741 pub fn activated(&self) -> &[String] {
743 &self.activated
744 }
745
746 pub fn require(&self, feature: &Feature) -> CargoResult<()> {
748 self.require_with_hint(feature, None)
749 }
750
751 pub(crate) fn require_with_hint(
757 &self,
758 feature: &Feature,
759 hint: Option<&str>,
760 ) -> CargoResult<()> {
761 if feature.is_enabled(self) {
762 return Ok(());
763 }
764 let feature_name = feature.name.replace("_", "-");
765 let mut msg = format!(
766 "feature `{}` is required\n\
767 \n\
768 The package requires the Cargo feature called `{}`, but \
769 that feature is not stabilized in this version of Cargo ({}).\n\
770 ",
771 feature_name,
772 feature_name,
773 crate::version(),
774 );
775
776 if self.nightly_features_allowed {
777 if self.is_local {
778 let _ = writeln!(
779 msg,
780 "Consider adding `cargo-features = [\"{}\"]` \
781 to the top of Cargo.toml (above the [package] table) \
782 to tell Cargo you are opting in to use this unstable feature.",
783 feature_name
784 );
785 } else {
786 let _ = writeln!(msg, "Consider trying a more recent nightly release.");
787 }
788 } else {
789 let _ = writeln!(
790 msg,
791 "Consider trying a newer version of Cargo \
792 (this may require the nightly release)."
793 );
794 }
795 let _ = writeln!(
796 msg,
797 "See https://doc.rust-lang.org/nightly/cargo/{} for more information \
798 about the status of this feature.",
799 feature.docs
800 );
801 if let Some(hint) = hint {
802 let _ = writeln!(msg, "{hint}");
803 }
804
805 bail!("{}", msg);
806 }
807
808 pub fn is_enabled(&self, feature: &Feature) -> bool {
810 feature.is_enabled(self)
811 }
812}
813
814macro_rules! unstable_cli_options {
818 (
819 $(
820 $(#[$meta:meta])?
821 $element: ident: $ty: ty$( = ($help:literal))?,
822 )*
823 ) => {
824 #[derive(Default, Debug, Deserialize)]
830 #[serde(default, rename_all = "kebab-case")]
831 pub struct CliUnstable {
832 $(
833 $(#[doc = $help])?
834 $(#[$meta])?
835 pub $element: $ty
836 ),*
837 }
838 impl CliUnstable {
839 pub fn help() -> Vec<(&'static str, Option<&'static str>)> {
841 let fields = vec![$((stringify!($element), None$(.or(Some($help)))?)),*];
842 fields
843 }
844 }
845
846 #[cfg(test)]
847 mod test {
848 #[test]
849 fn ensure_sorted() {
850 let location = std::panic::Location::caller();
852 println!(
853 "\nTo fix this test, sort the features inside the macro at {}:{}\n",
854 location.file(),
855 location.line()
856 );
857 let mut expected = vec![$(stringify!($element)),*];
858 expected[3..].sort();
860 let expected = format!("{:#?}", expected);
861 let actual = format!("{:#?}", vec![$(stringify!($element)),*]);
862 snapbox::assert_data_eq!(actual, expected);
863 }
864 }
865 }
866}
867
868unstable_cli_options!(
869 allow_features: Option<AllowFeatures> = ("Allow *only* the listed unstable features"),
871 embed_metadata: EmbedMetadata = ("Avoid embedding metadata in library artifacts"),
872 print_im_a_teapot: bool,
873
874 advanced_env: bool,
877 any_build_script_metadata: bool = ("Allow any build script to specify env vars via cargo::metadata=key=value"),
878 asymmetric_token: bool = ("Allows authenticating with asymmetric tokens"),
879 avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"),
880 binary_dep_depinfo: bool = ("Track changes to dependency artifacts"),
881 bindeps: bool = ("Allow Cargo packages to depend on bin, cdylib, and staticlib crates, and use the artifacts built by those crates"),
882 build_analysis: bool = ("Record and persist build metrics across runs, with commands to query past builds."),
883 build_dir_new_layout: bool = ("Use the new build-dir filesystem layout"),
884 #[serde(deserialize_with = "deserialize_comma_separated_list")]
885 build_std: Option<Vec<String>> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
886 #[serde(deserialize_with = "deserialize_comma_separated_list")]
887 build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"),
888 cargo_lints: bool = ("Enable the `[lints.cargo]` table"),
889 checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"),
890 codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
891 direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
892 dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
893 feature_unification: bool = ("Enable new feature unification modes in workspaces"),
894 features: Option<Vec<String>>,
895 fine_grain_locking: bool = ("Use fine grain locking instead of locking the entire build cache"),
896 fix_edition: Option<FixEdition> = ("Permanently unstable edition migration helper"),
897 gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
898 #[serde(deserialize_with = "deserialize_git_features")]
899 git: Option<GitFeatures> = ("Enable support for shallow git fetch operations"),
900 #[serde(deserialize_with = "deserialize_gitoxide_features")]
901 gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
902 hint_msrv: bool = ("Enable passing `package.rust-version` to rustc for lints"),
903 host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
904 json_target_spec: bool = ("Enable `.json` target spec files"),
905 min_publish_age: bool = ("Enable the `min-publish-age` configuration for dependency version age filtering"),
906 minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
907 msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
908 mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
909 next_lockfile_bump: bool,
910 no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
911 panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
912 panic_immediate_abort: bool = ("Enable setting `panic = \"immediate-abort\"` in profiles"),
913 profile_hint_mostly_unused: bool = ("Enable the `hint-mostly-unused` setting in profiles to mark a crate as mostly unused."),
914 profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
915 public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
916 publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
917 root_dir: Option<PathBuf> = ("Set the root directory relative to which paths are printed (defaults to workspace root)"),
918 rustc_unicode: bool = ("Enable `rustc`'s unicode error format in Cargo's error messages"),
919 rustdoc_depinfo: bool = ("Use dep-info files in rustdoc rebuild detection"),
920 rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
921 rustdoc_mergeable_info: bool = ("Use rustdoc mergeable cross-crate-info files"),
922 rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
923 sbom: bool = ("Enable the `sbom` option in build config in .cargo/config.toml file"),
924 script: bool = ("Enable support for single-file, `.rs` packages"),
925 section_timings: bool = ("Enable support for extended compilation sections in --timings output"),
926 separate_nightlies: bool,
927 skip_rustdoc_fingerprint: bool,
928 target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
929 trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
930 unstable_options: bool = ("Allow the usage of unstable options"),
931);
932
933const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
934 enabled when used on an interactive console.\n\
935 See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
936 for information on controlling the progress bar.";
937
938const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
939 --offline CLI option";
940
941const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
942
943const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
944 they appear out of date.\n\
945 See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
946 information on how upgrading works.";
947
948const STABILIZED_CONFIG_PROFILE: &str = "See \
949 https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
950 information about specifying profiles in config.";
951
952const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
953 automatically added to the documentation.";
954
955const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
956 available in virtual workspaces, and `member/feature-name` syntax is also \
957 always available. Other extensions require setting `resolver = \"2\"` in \
958 Cargo.toml.\n\
959 See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
960 for more information.";
961
962const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
963 by specifying `resolver = \"2\"` in Cargo.toml.\n\
964 See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
965 for more information.";
966
967const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \
968 supported without passing this flag.";
969
970const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled.";
971
972const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled.";
973
974const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\
975 See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \
976 for more information";
977
978const STABILIZED_DOCTEST_IN_WORKSPACE: &str =
979 "The doctest-in-workspace feature is now always enabled.";
980
981const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
982 "The future-incompat-report feature is now always enabled.";
983
984const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
985
986const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
987
988const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings.";
989
990const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available.";
991
992const STABILIZED_TERMINAL_WIDTH: &str =
993 "The -Zterminal-width option is now always enabled for terminal output.";
994
995const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";
996
997const STABILIZED_CREDENTIAL_PROCESS: &str =
998 "Authentication with a credential provider is always available.";
999
1000const STABILIZED_REGISTRY_AUTH: &str =
1001 "Authenticated registries are available if a credential provider is configured.";
1002
1003const STABILIZED_LINTS: &str = "The `[lints]` table is now always available.";
1004
1005const STABILIZED_CHECK_CFG: &str =
1006 "Compile-time checking of conditional (a.k.a. `-Zcheck-cfg`) is now always enabled.";
1007
1008const STABILIZED_DOCTEST_XCOMPILE: &str = "Doctest cross-compiling is now always enabled.";
1009
1010const STABILIZED_PACKAGE_WORKSPACE: &str =
1011 "Workspace packaging and publishing (a.k.a. `-Zpackage-workspace`) is now always enabled.";
1012
1013const STABILIZED_BUILD_DIR: &str = "build.build-dir is now always enabled.";
1014
1015const STABILIZED_CONFIG_INCLUDE: &str = "The `include` config key is now always available";
1016
1017const STABILIZED_LOCKFILE_PATH: &str = "The `lockfile-path` config key is now always available";
1018
1019const STABILIZED_WARNINGS: &str = "The `build.warnings` config key is now always available";
1020
1021fn deserialize_comma_separated_list<'de, D>(
1022 deserializer: D,
1023) -> Result<Option<Vec<String>>, D::Error>
1024where
1025 D: serde::Deserializer<'de>,
1026{
1027 let Some(list) = <Option<Vec<String>>>::deserialize(deserializer)? else {
1028 return Ok(None);
1029 };
1030 let v = list
1031 .iter()
1032 .flat_map(|s| s.split(','))
1033 .filter(|s| !s.is_empty())
1034 .map(String::from)
1035 .collect();
1036 Ok(Some(v))
1037}
1038
1039#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
1040#[serde(default)]
1041pub struct GitFeatures {
1042 pub shallow_index: bool,
1044 pub shallow_deps: bool,
1046}
1047
1048impl GitFeatures {
1049 pub fn all() -> Self {
1050 GitFeatures {
1051 shallow_index: true,
1052 shallow_deps: true,
1053 }
1054 }
1055
1056 fn expecting() -> String {
1057 let fields = ["`shallow-index`", "`shallow-deps`"];
1058 format!(
1059 "unstable 'git' only takes {} as valid inputs",
1060 fields.join(" and ")
1061 )
1062 }
1063}
1064
1065fn deserialize_git_features<'de, D>(deserializer: D) -> Result<Option<GitFeatures>, D::Error>
1066where
1067 D: serde::de::Deserializer<'de>,
1068{
1069 struct GitFeaturesVisitor;
1070
1071 impl<'de> serde::de::Visitor<'de> for GitFeaturesVisitor {
1072 type Value = Option<GitFeatures>;
1073
1074 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1075 formatter.write_str(&GitFeatures::expecting())
1076 }
1077
1078 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1079 where
1080 E: serde::de::Error,
1081 {
1082 if v {
1083 Ok(Some(GitFeatures::all()))
1084 } else {
1085 Ok(None)
1086 }
1087 }
1088
1089 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1090 where
1091 E: serde::de::Error,
1092 {
1093 Ok(parse_git(s.split(",")).map_err(serde::de::Error::custom)?)
1094 }
1095
1096 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1097 where
1098 D: serde::de::Deserializer<'de>,
1099 {
1100 let git = GitFeatures::deserialize(deserializer)?;
1101 Ok(Some(git))
1102 }
1103
1104 fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1105 where
1106 V: serde::de::MapAccess<'de>,
1107 {
1108 let mvd = serde::de::value::MapAccessDeserializer::new(map);
1109 Ok(Some(GitFeatures::deserialize(mvd)?))
1110 }
1111 }
1112
1113 deserializer.deserialize_any(GitFeaturesVisitor)
1114}
1115
1116fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<GitFeatures>> {
1117 let mut out = GitFeatures::default();
1118 let GitFeatures {
1119 shallow_index,
1120 shallow_deps,
1121 } = &mut out;
1122
1123 for e in it {
1124 match e.as_ref() {
1125 "shallow-index" => *shallow_index = true,
1126 "shallow-deps" => *shallow_deps = true,
1127 _ => {
1128 bail!(GitFeatures::expecting())
1129 }
1130 }
1131 }
1132 Ok(Some(out))
1133}
1134
1135#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
1136#[serde(default)]
1137pub struct GitoxideFeatures {
1138 pub fetch: bool,
1140 pub checkout: bool,
1143 pub internal_use_git2: bool,
1147}
1148
1149impl GitoxideFeatures {
1150 pub fn all() -> Self {
1151 GitoxideFeatures {
1152 fetch: true,
1153 checkout: true,
1154 internal_use_git2: false,
1155 }
1156 }
1157
1158 fn safe() -> Self {
1161 GitoxideFeatures {
1162 fetch: true,
1163 checkout: true,
1164 internal_use_git2: false,
1165 }
1166 }
1167
1168 fn expecting() -> String {
1169 let fields = ["`fetch`", "`checkout`", "`internal-use-git2`"];
1170 format!(
1171 "unstable 'gitoxide' only takes {} as valid inputs, for shallow fetches see `-Zgit=shallow-index,shallow-deps`",
1172 fields.join(" and ")
1173 )
1174 }
1175}
1176
1177fn deserialize_gitoxide_features<'de, D>(
1178 deserializer: D,
1179) -> Result<Option<GitoxideFeatures>, D::Error>
1180where
1181 D: serde::de::Deserializer<'de>,
1182{
1183 struct GitoxideFeaturesVisitor;
1184
1185 impl<'de> serde::de::Visitor<'de> for GitoxideFeaturesVisitor {
1186 type Value = Option<GitoxideFeatures>;
1187
1188 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1189 formatter.write_str(&GitoxideFeatures::expecting())
1190 }
1191
1192 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1193 where
1194 E: serde::de::Error,
1195 {
1196 Ok(parse_gitoxide(s.split(",")).map_err(serde::de::Error::custom)?)
1197 }
1198
1199 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1200 where
1201 E: serde::de::Error,
1202 {
1203 if v {
1204 Ok(Some(GitoxideFeatures::all()))
1205 } else {
1206 Ok(None)
1207 }
1208 }
1209
1210 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1211 where
1212 D: serde::de::Deserializer<'de>,
1213 {
1214 let gitoxide = GitoxideFeatures::deserialize(deserializer)?;
1215 Ok(Some(gitoxide))
1216 }
1217
1218 fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1219 where
1220 V: serde::de::MapAccess<'de>,
1221 {
1222 let mvd = serde::de::value::MapAccessDeserializer::new(map);
1223 Ok(Some(GitoxideFeatures::deserialize(mvd)?))
1224 }
1225 }
1226
1227 deserializer.deserialize_any(GitoxideFeaturesVisitor)
1228}
1229
1230fn parse_gitoxide(
1231 it: impl Iterator<Item = impl AsRef<str>>,
1232) -> CargoResult<Option<GitoxideFeatures>> {
1233 let mut out = GitoxideFeatures::default();
1234 let GitoxideFeatures {
1235 fetch,
1236 checkout,
1237 internal_use_git2,
1238 } = &mut out;
1239
1240 for e in it {
1241 match e.as_ref() {
1242 "fetch" => *fetch = true,
1243 "checkout" => *checkout = true,
1244 "internal-use-git2" => *internal_use_git2 = true,
1245 _ => {
1246 bail!(GitoxideFeatures::expecting())
1247 }
1248 }
1249 }
1250 Ok(Some(out))
1251}
1252
1253impl CliUnstable {
1254 pub fn parse(
1257 &mut self,
1258 flags: &[String],
1259 nightly_features_allowed: bool,
1260 ) -> CargoResult<Vec<String>> {
1261 if !flags.is_empty() && !nightly_features_allowed {
1262 bail!(
1263 "the `-Z` flag is only accepted on the nightly channel of Cargo, \
1264 but this is the `{}` channel\n\
1265 {}",
1266 channel(),
1267 SEE_CHANNELS
1268 );
1269 }
1270 let mut warnings = Vec::new();
1271 for flag in flags {
1274 if flag.starts_with("allow-features=") {
1275 self.add(flag, &mut warnings)?;
1276 }
1277 }
1278 for flag in flags {
1279 self.add(flag, &mut warnings)?;
1280 }
1281
1282 if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() {
1283 self.gitoxide = GitoxideFeatures::safe().into();
1284 }
1285
1286 if is_new_build_dir_layout_opt_out() {
1289 self.build_dir_new_layout = false;
1290 }
1291
1292 self.implicitly_enable_features_if_needed();
1293
1294 Ok(warnings)
1295 }
1296
1297 fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
1298 let mut parts = flag.splitn(2, '=');
1299 let k = parts.next().unwrap();
1300 let v = parts.next();
1301
1302 fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
1303 match value {
1304 None | Some("yes") => Ok(true),
1305 Some("no") => Ok(false),
1306 Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
1307 }
1308 }
1309
1310 fn parse_embed_metadata(key: &str, value: Option<&str>) -> CargoResult<EmbedMetadata> {
1311 match value {
1312 None => Ok(EmbedMetadata::Unset),
1313 Some("yes") => Ok(EmbedMetadata::Embed),
1314 Some("no") => Ok(EmbedMetadata::DoNotEmbed),
1315 Some(s) => bail!("flag -Z{key} expected `no` or `yes`, found: `{s}`"),
1316 }
1317 }
1318
1319 fn parse_list(value: Option<&str>) -> Vec<String> {
1321 match value {
1322 None => Vec::new(),
1323 Some("") => Vec::new(),
1324 Some(v) => v.split(',').map(|s| s.to_string()).collect(),
1325 }
1326 }
1327
1328 fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
1330 if let Some(v) = value {
1331 bail!("flag -Z{} does not take a value, found: `{}`", key, v);
1332 }
1333 Ok(true)
1334 }
1335
1336 let mut stabilized_warn = |key: &str, version: &str, message: &str| {
1337 warnings.push(format!(
1338 "flag `-Z {}` has been stabilized in the {} release, \
1339 and is no longer necessary\n{}",
1340 key,
1341 version,
1342 indented_lines(message)
1343 ));
1344 };
1345
1346 let stabilized_err = |key: &str, version: &str, message: &str| {
1348 Err(anyhow::format_err!(
1349 "flag `-Z {}` has been stabilized in the {} release\n{}",
1350 key,
1351 version,
1352 indented_lines(message)
1353 ))
1354 };
1355
1356 if let Some(allowed) = &self.allow_features {
1357 if k != "allow-features" && !allowed.contains(k) {
1358 bail!(
1359 "the feature `{}` is not in the list of allowed features: [{}]",
1360 k,
1361 itertools::join(allowed, ", ")
1362 );
1363 }
1364 }
1365
1366 match k {
1367 "allow-features" => self.allow_features = Some(parse_list(v).into_iter().collect()),
1370 "embed-metadata" => self.embed_metadata = parse_embed_metadata(k, v)?,
1371 "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
1372
1373 "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
1376 "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
1377 "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
1378 "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
1379 "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
1380 "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
1381 "features" => {
1382 let feats = parse_list(v);
1390 let stab_is_not_empty = feats.iter().any(|feat| {
1391 matches!(
1392 feat.as_str(),
1393 "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
1394 )
1395 });
1396 if stab_is_not_empty || feats.is_empty() {
1397 stabilized_warn(k, "1.51", STABILIZED_FEATURES);
1399 }
1400 self.features = Some(feats);
1401 }
1402 "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
1403 "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV),
1404 "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG),
1405 "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG),
1406 "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES),
1407 "future-incompat-report" => {
1408 stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT)
1409 }
1410 "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
1411 "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS),
1412 "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
1413 "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET),
1414 "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
1415 "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
1416 "doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
1417 "credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
1418 "lints" => stabilized_warn(k, "1.74", STABILIZED_LINTS),
1419 "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),
1420 "check-cfg" => stabilized_warn(k, "1.80", STABILIZED_CHECK_CFG),
1421 "doctest-xcompile" => stabilized_warn(k, "1.89", STABILIZED_DOCTEST_XCOMPILE),
1422 "package-workspace" => stabilized_warn(k, "1.89", STABILIZED_PACKAGE_WORKSPACE),
1423 "build-dir" => stabilized_warn(k, "1.91", STABILIZED_BUILD_DIR),
1424 "config-include" => stabilized_warn(k, "1.93", STABILIZED_CONFIG_INCLUDE),
1425 "lockfile-path" => stabilized_warn(k, "1.97", STABILIZED_LOCKFILE_PATH),
1426 "warnings" => stabilized_warn(k, "1.97", STABILIZED_WARNINGS),
1427
1428 "advanced-env" => self.advanced_env = parse_empty(k, v)?,
1431 "any-build-script-metadata" => self.any_build_script_metadata = parse_empty(k, v)?,
1432 "asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?,
1433 "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
1434 "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
1435 "bindeps" => self.bindeps = parse_empty(k, v)?,
1436 "build-analysis" => self.build_analysis = parse_empty(k, v)?,
1437 "build-dir-new-layout" => self.build_dir_new_layout = parse_empty(k, v)?,
1438 "build-std" => self.build_std = Some(parse_list(v)),
1439 "build-std-features" => self.build_std_features = Some(parse_list(v)),
1440 "cargo-lints" => self.cargo_lints = parse_empty(k, v)?,
1441 "codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
1442 "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
1443 "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
1444 "feature-unification" => self.feature_unification = parse_empty(k, v)?,
1445 "fine-grain-locking" => self.fine_grain_locking = parse_empty(k, v)?,
1446 "fix-edition" => {
1447 let fe = v
1448 .ok_or_else(|| anyhow::anyhow!("-Zfix-edition expected a value"))?
1449 .parse()?;
1450 self.fix_edition = Some(fe);
1451 }
1452 "gc" => self.gc = parse_empty(k, v)?,
1453 "git" => {
1454 self.git =
1455 v.map_or_else(|| Ok(Some(GitFeatures::all())), |v| parse_git(v.split(',')))?
1456 }
1457 "gitoxide" => {
1458 self.gitoxide = v.map_or_else(
1459 || Ok(Some(GitoxideFeatures::all())),
1460 |v| parse_gitoxide(v.split(',')),
1461 )?
1462 }
1463 "host-config" => self.host_config = parse_empty(k, v)?,
1464 "json-target-spec" => self.json_target_spec = parse_empty(k, v)?,
1465 "min-publish-age" => self.min_publish_age = parse_empty(k, v)?,
1466 "hint-msrv" => self.hint_msrv = parse_empty(k, v)?,
1467 "next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
1468 "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
1469 "msrv-policy" => self.msrv_policy = parse_empty(k, v)?,
1470 "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
1472 "no-index-update" => self.no_index_update = parse_empty(k, v)?,
1473 "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
1474 "public-dependency" => self.public_dependency = parse_empty(k, v)?,
1475 "profile-hint-mostly-unused" => self.profile_hint_mostly_unused = parse_empty(k, v)?,
1476 "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
1477 "trim-paths" => self.trim_paths = parse_empty(k, v)?,
1478 "publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
1479 "root-dir" => self.root_dir = v.map(|v| v.into()),
1480 "rustc-unicode" => self.rustc_unicode = parse_empty(k, v)?,
1481 "rustdoc-depinfo" => self.rustdoc_depinfo = parse_empty(k, v)?,
1482 "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
1483 "rustdoc-mergeable-info" => self.rustdoc_mergeable_info = parse_empty(k, v)?,
1484 "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
1485 "sbom" => self.sbom = parse_empty(k, v)?,
1486 "section-timings" => self.section_timings = parse_empty(k, v)?,
1487 "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
1488 "checksum-freshness" => self.checksum_freshness = parse_empty(k, v)?,
1489 "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
1490 "script" => self.script = parse_empty(k, v)?,
1491 "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
1492 "panic-immediate-abort" => self.panic_immediate_abort = parse_empty(k, v)?,
1493 "unstable-options" => self.unstable_options = parse_empty(k, v)?,
1494 _ => bail!(
1495 "\
1496 unknown `-Z` flag specified: {k}\n\n\
1497 For available unstable features, see \
1498 https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\
1499 If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"
1500 ),
1501 }
1502
1503 Ok(())
1504 }
1505
1506 pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
1509 self.fail_if_stable_opt_custom_z(flag, issue, "unstable-options", self.unstable_options)
1510 }
1511
1512 pub fn fail_if_stable_opt_custom_z(
1513 &self,
1514 flag: &str,
1515 issue: u32,
1516 z_name: &str,
1517 enabled: bool,
1518 ) -> CargoResult<()> {
1519 if !enabled {
1520 let see = format!(
1521 "See https://github.com/rust-lang/cargo/issues/{issue} for more \
1522 information about the `{flag}` flag."
1523 );
1524 let channel = channel();
1526 if channel == "nightly" || channel == "dev" {
1527 bail!(
1528 "the `{flag}` flag is unstable, pass `-Z {z_name}` to enable it\n\
1529 {see}"
1530 );
1531 } else {
1532 bail!(
1533 "the `{flag}` flag is unstable, and only available on the nightly channel \
1534 of Cargo, but this is the `{channel}` channel\n\
1535 {SEE_CHANNELS}\n\
1536 {see}"
1537 );
1538 }
1539 }
1540 Ok(())
1541 }
1542
1543 pub fn fail_if_stable_command(
1546 &self,
1547 gctx: &GlobalContext,
1548 command: &str,
1549 issue: u32,
1550 z_name: &str,
1551 enabled: bool,
1552 ) -> CargoResult<()> {
1553 if enabled {
1554 return Ok(());
1555 }
1556 let see = format!(
1557 "See https://github.com/rust-lang/cargo/issues/{} for more \
1558 information about the `cargo {}` command.",
1559 issue, command
1560 );
1561 if gctx.nightly_features_allowed {
1562 bail!(
1563 "the `cargo {command}` command is unstable, pass `-Z {z_name}` \
1564 to enable it\n\
1565 {see}",
1566 );
1567 } else {
1568 bail!(
1569 "the `cargo {}` command is unstable, and only available on the \
1570 nightly channel of Cargo, but this is the `{}` channel\n\
1571 {}\n\
1572 {}",
1573 command,
1574 channel(),
1575 SEE_CHANNELS,
1576 see
1577 );
1578 }
1579 }
1580
1581 fn implicitly_enable_features_if_needed(&mut self) {
1582 if self.fine_grain_locking && !self.build_dir_new_layout {
1583 debug!("-Zbuild-dir-new-layout implicitly enabled by -Zfine-grain-locking");
1584 self.build_dir_new_layout = true;
1585 }
1586 }
1587}
1588
1589pub fn channel() -> String {
1591 #[expect(
1592 clippy::disallowed_methods,
1593 reason = "testing only, no reason for config support"
1594 )]
1595 if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
1596 return override_channel;
1597 }
1598 #[expect(
1599 clippy::disallowed_methods,
1600 reason = "consistency with rustc, not specified behavior"
1601 )]
1602 if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
1603 if staging == "1" {
1604 return "dev".to_string();
1605 }
1606 }
1607 crate::version()
1608 .release_channel
1609 .unwrap_or_else(|| String::from("dev"))
1610}
1611
1612#[expect(
1616 clippy::disallowed_methods,
1617 reason = "testing only, no reason for config support"
1618)]
1619fn cargo_use_gitoxide_instead_of_git2() -> bool {
1620 std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
1621}
1622
1623#[expect(
1624 clippy::disallowed_methods,
1625 reason = "Temporary opt out that is not part of the public interface"
1626)]
1627fn is_new_build_dir_layout_opt_out() -> bool {
1628 std::env::var("__CARGO_TEMPORARY_BUILD_DIR_NEW_LAYOUT_OPT_OUT").as_deref() == Ok("1")
1629}
1630
1631pub fn cargo_docs_link(path: &str) -> String {
1634 let url_channel = match channel().as_str() {
1635 "dev" | "nightly" => "nightly/",
1636 "beta" => "beta/",
1637 _ => "",
1638 };
1639 format!("https://doc.rust-lang.org/{url_channel}cargo/{path}")
1640}