@@ -119,7 +119,11 @@ impl EnvGetter for StdEnvGetter {
119119/// - `"x86"`, `"i586"` or `"i686"`
120120/// - `"arm"` or `"thumbv7a"`
121121///
122- /// The `tool` argument is the tool to find (e.g. `cl.exe` or `link.exe`).
122+ /// The `tool` argument is the tool to find. Supported tools include:
123+ /// - MSVC tools: `cl.exe`, `link.exe`, `lib.exe`, etc.
124+ /// - `MSBuild`: `msbuild.exe`
125+ /// - Visual Studio IDE: `devenv.exe`
126+ /// - Clang/LLVM tools: `clang.exe`, `clang++.exe`, `clang-*.exe`, `llvm-*.exe`, `lld.exe`, etc.
123127///
124128/// This function will return `None` if the tool could not be found, or it will
125129/// return `Some(cmd)` which represents a command that's ready to execute the
@@ -168,6 +172,15 @@ pub(crate) fn find_tool_inner(
168172 return impl_:: find_devenv ( target, env_getter) ;
169173 }
170174
175+ // Clang/LLVM isn't located in the same location as other tools like
176+ // cl.exe and lib.exe.
177+ if [ "clang" , "lldb" , "llvm" , "ld" , "lld" ]
178+ . iter ( )
179+ . any ( |& t| tool. contains ( t) )
180+ {
181+ return impl_:: find_llvm_tool ( tool, target, env_getter) ;
182+ }
183+
171184 // Ok, if we're here, now comes the fun part of the probing. Default shells
172185 // or shells like MSYS aren't really configured to execute `cl.exe` and the
173186 // various compiler tools shipped as part of Visual Studio. Here we try to
@@ -509,6 +522,44 @@ mod impl_ {
509522 find_tool_in_vs16plus_path ( r"MSBuild\Current\Bin\MSBuild.exe" , target, "16" , env_getter)
510523 }
511524
525+ pub ( super ) fn find_llvm_tool (
526+ tool : & str ,
527+ target : TargetArch ,
528+ env_getter : & dyn EnvGetter ,
529+ ) -> Option < Tool > {
530+ find_llvm_tool_vs17 ( tool, target, env_getter)
531+ }
532+
533+ fn find_llvm_tool_vs17 (
534+ tool : & str ,
535+ target : TargetArch ,
536+ env_getter : & dyn EnvGetter ,
537+ ) -> Option < Tool > {
538+ vs16plus_instances ( target, "17" , env_getter)
539+ . filter_map ( |mut base_path| {
540+ base_path. push ( r"VC\Tools\LLVM" ) ;
541+ let host_folder = match host_arch ( ) {
542+ // The default LLVM bin folder is x86, and there's separate subfolders
543+ // for the x64 and ARM64 host tools.
544+ X86 => "" ,
545+ X86_64 => "x64" ,
546+ AARCH64 => "ARM64" ,
547+ _ => return None ,
548+ } ;
549+ if host_folder != "" {
550+ // E.g. C:\...\VC\Tools\LLVM\x64
551+ base_path. push ( host_folder) ;
552+ }
553+ // E.g. C:\...\VC\Tools\LLVM\x64\bin\clang.exe
554+ base_path. push ( "bin" ) ;
555+ base_path. push ( tool) ;
556+ base_path
557+ . is_file ( )
558+ . then ( || Tool :: with_family ( base_path, MSVC_FAMILY ) )
559+ } )
560+ . next ( )
561+ }
562+
512563 // In MSVC 15 (2017) MS once again changed the scheme for locating
513564 // the tooling. Now we must go through some COM interfaces, which
514565 // is super fun for Rust.
@@ -1058,6 +1109,152 @@ mod impl_ {
10581109 }
10591110 }
10601111
1112+ #[ cfg( test) ]
1113+ mod tests {
1114+ use super :: * ;
1115+ use std:: path:: Path ;
1116+ // Import the find function from the module level
1117+ use crate :: windows:: find_tools:: find;
1118+ // Import StdEnvGetter from the parent module
1119+ use crate :: windows:: find_tools:: StdEnvGetter ;
1120+
1121+ fn host_arch_to_string ( host_arch_value : u16 ) -> & ' static str {
1122+ match host_arch_value {
1123+ X86 => "x86" ,
1124+ X86_64 => "x64" ,
1125+ AARCH64 => "arm64" ,
1126+ _ => panic ! ( "Unsupported host architecture: {}" , host_arch_value) ,
1127+ }
1128+ }
1129+
1130+ #[ test]
1131+ fn test_find_cl_exe ( ) {
1132+ // Test that we can find cl.exe for common target architectures
1133+ // and validate the correct host-target combination paths
1134+ // This should pass on Windows CI with Visual Studio installed
1135+
1136+ let target_architectures = [ "x64" , "x86" , "arm64" ] ;
1137+ let mut found_any = false ;
1138+
1139+ // Determine the host architecture
1140+ let host_arch_value = host_arch ( ) ;
1141+ let host_name = host_arch_to_string ( host_arch_value) ;
1142+
1143+ for & target_arch in & target_architectures {
1144+ if let Some ( cmd) = find ( target_arch, "cl.exe" ) {
1145+ // Verify the command looks valid
1146+ assert ! (
1147+ !cmd. get_program( ) . is_empty( ) ,
1148+ "cl.exe program path should not be empty"
1149+ ) ;
1150+ assert ! (
1151+ Path :: new( cmd. get_program( ) ) . exists( ) ,
1152+ "cl.exe should exist at: {:?}" ,
1153+ cmd. get_program( )
1154+ ) ;
1155+
1156+ // Verify the path contains the correct host-target combination
1157+ // Use case-insensitive comparison since VS IDE uses "Hostx64" while Build Tools use "HostX64"
1158+ let path_str = cmd. get_program ( ) . to_string_lossy ( ) ;
1159+ let path_str_lower = path_str. to_lowercase ( ) ;
1160+ let expected_host_target_path =
1161+ format ! ( "\\ bin\\ host{host_name}\\ {target_arch}" ) ;
1162+ let expected_host_target_path_unix =
1163+ expected_host_target_path. replace ( "\\ " , "/" ) ;
1164+
1165+ assert ! (
1166+ path_str_lower. contains( & expected_host_target_path) || path_str_lower. contains( & expected_host_target_path_unix) ,
1167+ "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}" ,
1168+ expected_host_target_path,
1169+ host_name,
1170+ target_arch,
1171+ path_str
1172+ ) ;
1173+
1174+ found_any = true ;
1175+ }
1176+ }
1177+
1178+ assert ! ( found_any, "Expected to find cl.exe for at least one target architecture (x64, x86, or arm64) on Windows CI with Visual Studio installed" ) ;
1179+ }
1180+
1181+ #[ test]
1182+ fn test_find_llvm_tools ( ) {
1183+ // Test the actual find_llvm_tool function with various LLVM tools
1184+ // This test assumes CI environment has Visual Studio + Clang installed
1185+ // We test against x64 target since clang can cross-compile to any target
1186+ let target_arch = TargetArch :: new ( "x64" ) . expect ( "Should support x64 architecture" ) ;
1187+ let llvm_tools = [ "clang.exe" , "clang++.exe" , "lld.exe" , "llvm-ar.exe" ] ;
1188+
1189+ // Determine expected host-specific path based on host architecture
1190+ let host_arch_value = host_arch ( ) ;
1191+ let expected_host_path = match host_arch_value {
1192+ X86 => "LLVM\\ bin" , // x86 host
1193+ X86_64 => "LLVM\\ x64\\ bin" , // x64 host
1194+ AARCH64 => "LLVM\\ ARM64\\ bin" , // arm64 host
1195+ _ => panic ! ( "Unsupported host architecture: {}" , host_arch_value) ,
1196+ } ;
1197+
1198+ let host_name = host_arch_to_string ( host_arch_value) ;
1199+
1200+ let mut found_tools_count = 0 ;
1201+
1202+ for & tool in & llvm_tools {
1203+ // Test finding LLVM tools using the standard environment getter
1204+ let env_getter = StdEnvGetter ;
1205+ let result = find_llvm_tool ( tool, target_arch, & env_getter) ;
1206+
1207+ match result {
1208+ Some ( found_tool) => {
1209+ found_tools_count += 1 ;
1210+
1211+ // Verify the found tool has a valid, non-empty path
1212+ assert ! (
1213+ !found_tool. path( ) . as_os_str( ) . is_empty( ) ,
1214+ "Found LLVM tool '{}' should have a non-empty path" ,
1215+ tool
1216+ ) ;
1217+
1218+ // Verify the tool path actually exists on filesystem
1219+ assert ! (
1220+ found_tool. path( ) . exists( ) ,
1221+ "LLVM tool '{}' path should exist: {:?}" ,
1222+ tool,
1223+ found_tool. path( )
1224+ ) ;
1225+
1226+ // Verify the tool path contains the expected tool name
1227+ let path_str = found_tool. path ( ) . to_string_lossy ( ) ;
1228+ assert ! (
1229+ path_str. contains( tool. trim_end_matches( ".exe" ) ) ,
1230+ "Tool path '{}' should contain tool name '{}'" ,
1231+ path_str,
1232+ tool
1233+ ) ;
1234+
1235+ // Verify it's in the correct host-specific VS LLVM directory
1236+ assert ! (
1237+ path_str. contains( expected_host_path) || path_str. contains( & expected_host_path. replace( "\\ " , "/" ) ) ,
1238+ "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}" ,
1239+ expected_host_path,
1240+ host_name,
1241+ path_str
1242+ ) ;
1243+ }
1244+ None => { }
1245+ }
1246+ }
1247+
1248+ // On CI with VS + Clang installed, we should find at least some LLVM tools
1249+ assert ! (
1250+ found_tools_count > 0 ,
1251+ "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}" ,
1252+ host_name,
1253+ found_tools_count
1254+ ) ;
1255+ }
1256+ }
1257+
10611258 // Given a registry key, look at all the sub keys and find the one which has
10621259 // the maximal numeric value.
10631260 //
@@ -1179,6 +1376,16 @@ mod impl_ {
11791376 None
11801377 }
11811378
1379+ // Finding Clang/LLVM-related tools on unix systems is not currently supported.
1380+ #[ inline( always) ]
1381+ pub ( super ) fn find_llvm_tool (
1382+ _tool : & str ,
1383+ _target : TargetArch ,
1384+ _: & dyn EnvGetter ,
1385+ ) -> Option < Tool > {
1386+ None
1387+ }
1388+
11821389 /// Attempt to find the tool using environment variables set by vcvars.
11831390 pub ( super ) fn find_msvc_environment (
11841391 tool : & str ,
0 commit comments