diff --git a/src/cargo/core/compiler/compile_kind.rs b/src/cargo/core/compiler/compile_kind.rs index 222732ddebc..a2e276a98f3 100644 --- a/src/cargo/core/compiler/compile_kind.rs +++ b/src/cargo/core/compiler/compile_kind.rs @@ -7,6 +7,7 @@ use crate::util::{try_canonicalize, GlobalContext, StableHasher}; use anyhow::Context as _; use serde::Serialize; use std::collections::BTreeSet; +use std::ffi::OsStr; use std::fs; use std::hash::{Hash, Hasher}; use std::path::Path; @@ -131,7 +132,7 @@ impl CompileTarget { if name.is_empty() { anyhow::bail!("target was empty"); } - if !name.ends_with(".json") { + if !name.has_json_ext_ignore_case() { return Ok(CompileTarget { name: name.into() }); } @@ -170,7 +171,7 @@ impl CompileTarget { // name without ".json") as a short name for this target. Note that the // `unwrap()` here should never trigger since we have a nonempty name // and it starts as utf-8 so it's always utf-8 - if self.name.ends_with(".json") { + if self.has_json_ext_ignore_case() { Path::new(&self.name).file_stem().unwrap().to_str().unwrap() } else { &self.name @@ -181,8 +182,7 @@ impl CompileTarget { pub fn fingerprint_hash(&self) -> u64 { let mut hasher = StableHasher::new(); match self - .name - .ends_with(".json") + .has_json_ext_ignore_case() .then(|| fs::read_to_string(self.name)) { Some(Ok(contents)) => { @@ -198,3 +198,55 @@ impl CompileTarget { hasher.finish() } } + +pub trait HasExtIgnoreCase { + /// Checks if the `self` ends with `ext` __case-insensitive__. + /// The `ext` should not have leading dot. + /// + /// Does not check if the path exists. + /// + /// Use it only for user's input like target names, + /// but not in algorithms like filesystem traversal because on case-sensitive fs it may lead logical errors + /// if you'll have met two files "a.foo" and "a.FOO", for example. + fn has_ext_ignore_case>(&self, ext: Ext) -> bool; + + /// Checks if the `self` ends with ".json" (case-insensitive). + /// + /// See [`has_ext_ignore_case`][HasExtIgnoreCase::has_ext_ignore_case], + /// used as default implementation. + #[inline] + fn has_json_ext_ignore_case(&self) -> bool { + self.has_ext_ignore_case("json") + } +} + +impl> HasExtIgnoreCase for T { + fn has_ext_ignore_case>(&self, ext: Ext) -> bool { + Path::new(self) + .extension() + .is_some_and(|s| s.eq_ignore_ascii_case(ext)) + } +} + +impl HasExtIgnoreCase for CompileTarget { + /// Checks if the name of `self` ends with `ext` __case-insensitive__. + /// The `ext` should not have leading dot. + /// + /// See [`has_ext_ignore_case`][HasExtIgnoreCase::has_ext_ignore_case], + fn has_ext_ignore_case>(&self, ext: Ext) -> bool { + self.name.has_ext_ignore_case(ext) + } +} + +impl HasExtIgnoreCase for CompileKind { + /// Checks if the `self`'s target name ends with `ext` __case-insensitive__. + /// The `ext` should not have leading dot. + /// + /// See [`has_ext_ignore_case`][HasExtIgnoreCase::has_ext_ignore_case], + fn has_ext_ignore_case>(&self, ext: Ext) -> bool { + match self { + CompileKind::Host => false, + CompileKind::Target(target) => target.has_ext_ignore_case(ext), + } + } +} diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 70dfb3085d3..ef6967c3e3a 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -74,7 +74,7 @@ pub use self::build_context::{ use self::build_plan::BuildPlan; pub use self::build_runner::{BuildRunner, Metadata}; pub use self::compilation::{Compilation, Doctest, UnitOutput}; -pub use self::compile_kind::{CompileKind, CompileTarget}; +pub use self::compile_kind::{CompileKind, CompileTarget, HasExtIgnoreCase}; pub use self::crate_type::CrateType; pub use self::custom_build::LinkArgTarget; pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts}; diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index f275c23877a..996ba4d9302 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -68,6 +68,7 @@ use std::time::Instant; use self::ConfigValue as CV; use crate::core::compiler::rustdoc::RustdocExternMap; +use crate::core::compiler::HasExtIgnoreCase as _; use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker}; use crate::core::shell::Verbosity; use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig}; @@ -2651,7 +2652,7 @@ impl BuildTargetConfig { /// Gets values of `build.target` as a list of strings. pub fn values(&self, gctx: &GlobalContext) -> CargoResult> { let map = |s: &String| { - if s.ends_with(".json") { + if s.has_json_ext_ignore_case() { // Path to a target specification file (in JSON). // self.inner