@@ -119,7 +119,11 @@ impl EnvGetter for StdEnvGetter {
119
119
/// - `"x86"`, `"i586"` or `"i686"`
120
120
/// - `"arm"` or `"thumbv7a"`
121
121
///
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.
123
127
///
124
128
/// This function will return `None` if the tool could not be found, or it will
125
129
/// return `Some(cmd)` which represents a command that's ready to execute the
@@ -168,6 +172,15 @@ pub(crate) fn find_tool_inner(
168
172
return impl_:: find_devenv ( target, env_getter) ;
169
173
}
170
174
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
+
171
184
// Ok, if we're here, now comes the fun part of the probing. Default shells
172
185
// or shells like MSYS aren't really configured to execute `cl.exe` and the
173
186
// various compiler tools shipped as part of Visual Studio. Here we try to
@@ -509,6 +522,44 @@ mod impl_ {
509
522
find_tool_in_vs16plus_path ( r"MSBuild\Current\Bin\MSBuild.exe" , target, "16" , env_getter)
510
523
}
511
524
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
+
512
563
// In MSVC 15 (2017) MS once again changed the scheme for locating
513
564
// the tooling. Now we must go through some COM interfaces, which
514
565
// is super fun for Rust.
@@ -1058,6 +1109,152 @@ mod impl_ {
1058
1109
}
1059
1110
}
1060
1111
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
+
1061
1258
// Given a registry key, look at all the sub keys and find the one which has
1062
1259
// the maximal numeric value.
1063
1260
//
@@ -1179,6 +1376,16 @@ mod impl_ {
1179
1376
None
1180
1377
}
1181
1378
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
+
1182
1389
/// Attempt to find the tool using environment variables set by vcvars.
1183
1390
pub ( super ) fn find_msvc_environment (
1184
1391
tool : & str ,
0 commit comments