1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! A helper module to probe the Windows Registry when looking for |
12 | //! windows-specific tools. |
13 | |
14 | use std::process::Command; |
15 | |
16 | use crate::Tool; |
17 | #[cfg (windows)] |
18 | use crate::ToolFamily; |
19 | |
20 | #[cfg (windows)] |
21 | const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; |
22 | |
23 | /// Attempts to find a tool within an MSVC installation using the Windows |
24 | /// registry as a point to search from. |
25 | /// |
26 | /// The `target` argument is the target that the tool should work for (e.g. |
27 | /// compile or link for) and the `tool` argument is the tool to find (e.g. |
28 | /// `cl.exe` or `link.exe`). |
29 | /// |
30 | /// This function will return `None` if the tool could not be found, or it will |
31 | /// return `Some(cmd)` which represents a command that's ready to execute the |
32 | /// tool with the appropriate environment variables set. |
33 | /// |
34 | /// Note that this function always returns `None` for non-MSVC targets. |
35 | pub fn find(target: &str, tool: &str) -> Option<Command> { |
36 | find_tool(target, tool).map(|c: Tool| c.to_command()) |
37 | } |
38 | |
39 | /// Similar to the `find` function above, this function will attempt the same |
40 | /// operation (finding a MSVC tool in a local install) but instead returns a |
41 | /// `Tool` which may be introspected. |
42 | #[cfg (not(windows))] |
43 | pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> { |
44 | None |
45 | } |
46 | |
47 | /// Documented above. |
48 | #[cfg (windows)] |
49 | pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { |
50 | // This logic is all tailored for MSVC, if we're not that then bail out |
51 | // early. |
52 | if !target.contains("msvc" ) { |
53 | return None; |
54 | } |
55 | |
56 | // Looks like msbuild isn't located in the same location as other tools like |
57 | // cl.exe and lib.exe. To handle this we probe for it manually with |
58 | // dedicated registry keys. |
59 | if tool.contains("msbuild" ) { |
60 | return impl_::find_msbuild(target); |
61 | } |
62 | |
63 | if tool.contains("devenv" ) { |
64 | return impl_::find_devenv(target); |
65 | } |
66 | |
67 | // Ok, if we're here, now comes the fun part of the probing. Default shells |
68 | // or shells like MSYS aren't really configured to execute `cl.exe` and the |
69 | // various compiler tools shipped as part of Visual Studio. Here we try to |
70 | // first find the relevant tool, then we also have to be sure to fill in |
71 | // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that |
72 | // the tool is actually usable. |
73 | |
74 | return impl_::find_msvc_environment(tool, target) |
75 | .or_else(|| impl_::find_msvc_15plus(tool, target)) |
76 | .or_else(|| impl_::find_msvc_14(tool, target)) |
77 | .or_else(|| impl_::find_msvc_12(tool, target)) |
78 | .or_else(|| impl_::find_msvc_11(tool, target)); |
79 | } |
80 | |
81 | /// A version of Visual Studio |
82 | #[derive (Debug, PartialEq, Eq, Copy, Clone)] |
83 | pub enum VsVers { |
84 | /// Visual Studio 12 (2013) |
85 | Vs12, |
86 | /// Visual Studio 14 (2015) |
87 | Vs14, |
88 | /// Visual Studio 15 (2017) |
89 | Vs15, |
90 | /// Visual Studio 16 (2019) |
91 | Vs16, |
92 | /// Visual Studio 17 (2022) |
93 | Vs17, |
94 | |
95 | /// Hidden variant that should not be matched on. Callers that want to |
96 | /// handle an enumeration of `VsVers` instances should always have a default |
97 | /// case meaning that it's a VS version they don't understand. |
98 | #[doc (hidden)] |
99 | #[allow (bad_style)] |
100 | __Nonexhaustive_do_not_match_this_or_your_code_will_break, |
101 | } |
102 | |
103 | /// Find the most recent installed version of Visual Studio |
104 | /// |
105 | /// This is used by the cmake crate to figure out the correct |
106 | /// generator. |
107 | #[cfg (not(windows))] |
108 | pub fn find_vs_version() -> Result<VsVers, String> { |
109 | Err(format!("not windows" )) |
110 | } |
111 | |
112 | /// Documented above |
113 | #[cfg (windows)] |
114 | pub fn find_vs_version() -> Result<VsVers, String> { |
115 | use std::env; |
116 | |
117 | match env::var("VisualStudioVersion" ) { |
118 | Ok(version) => match &version[..] { |
119 | "17.0" => Ok(VsVers::Vs17), |
120 | "16.0" => Ok(VsVers::Vs16), |
121 | "15.0" => Ok(VsVers::Vs15), |
122 | "14.0" => Ok(VsVers::Vs14), |
123 | "12.0" => Ok(VsVers::Vs12), |
124 | vers => Err(format!( |
125 | " \n\n\ |
126 | unsupported or unknown VisualStudio version: {}\n\ |
127 | if another version is installed consider running \ |
128 | the appropriate vcvars script before building this \ |
129 | crate \n\ |
130 | " , |
131 | vers |
132 | )), |
133 | }, |
134 | _ => { |
135 | // Check for the presence of a specific registry key |
136 | // that indicates visual studio is installed. |
137 | if impl_::has_msbuild_version("17.0" ) { |
138 | Ok(VsVers::Vs17) |
139 | } else if impl_::has_msbuild_version("16.0" ) { |
140 | Ok(VsVers::Vs16) |
141 | } else if impl_::has_msbuild_version("15.0" ) { |
142 | Ok(VsVers::Vs15) |
143 | } else if impl_::has_msbuild_version("14.0" ) { |
144 | Ok(VsVers::Vs14) |
145 | } else if impl_::has_msbuild_version("12.0" ) { |
146 | Ok(VsVers::Vs12) |
147 | } else { |
148 | Err(format!( |
149 | " \n\n\ |
150 | couldn't determine visual studio generator \n\ |
151 | if VisualStudio is installed, however, consider \ |
152 | running the appropriate vcvars script before building \ |
153 | this crate \n\ |
154 | " |
155 | )) |
156 | } |
157 | } |
158 | } |
159 | } |
160 | |
161 | #[cfg (windows)] |
162 | mod impl_ { |
163 | use crate::com; |
164 | use crate::registry::{RegistryKey, LOCAL_MACHINE}; |
165 | use crate::setup_config::SetupConfiguration; |
166 | use crate::vs_instances::{VsInstances, VswhereInstance}; |
167 | use std::convert::TryFrom; |
168 | use std::env; |
169 | use std::ffi::OsString; |
170 | use std::fs::File; |
171 | use std::io::Read; |
172 | use std::iter; |
173 | use std::mem; |
174 | use std::path::{Path, PathBuf}; |
175 | use std::process::Command; |
176 | use std::str::FromStr; |
177 | |
178 | use super::MSVC_FAMILY; |
179 | use crate::Tool; |
180 | |
181 | struct MsvcTool { |
182 | tool: PathBuf, |
183 | libs: Vec<PathBuf>, |
184 | path: Vec<PathBuf>, |
185 | include: Vec<PathBuf>, |
186 | } |
187 | |
188 | impl MsvcTool { |
189 | fn new(tool: PathBuf) -> MsvcTool { |
190 | MsvcTool { |
191 | tool: tool, |
192 | libs: Vec::new(), |
193 | path: Vec::new(), |
194 | include: Vec::new(), |
195 | } |
196 | } |
197 | |
198 | fn into_tool(self) -> Tool { |
199 | let MsvcTool { |
200 | tool, |
201 | libs, |
202 | path, |
203 | include, |
204 | } = self; |
205 | let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY); |
206 | add_env(&mut tool, "LIB" , libs); |
207 | add_env(&mut tool, "PATH" , path); |
208 | add_env(&mut tool, "INCLUDE" , include); |
209 | tool |
210 | } |
211 | } |
212 | |
213 | /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the |
214 | /// given target's arch. Returns `None` if the variable does not exist. |
215 | #[cfg (windows)] |
216 | fn is_vscmd_target(target: &str) -> Option<bool> { |
217 | let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH" ).ok()?; |
218 | // Convert the Rust target arch to its VS arch equivalent. |
219 | let arch = match target.split("-" ).next() { |
220 | Some("x86_64" ) => "x64" , |
221 | Some("aarch64" ) => "arm64" , |
222 | Some("i686" ) | Some("i586" ) => "x86" , |
223 | Some("thumbv7a" ) => "arm" , |
224 | // An unrecognized arch. |
225 | _ => return Some(false), |
226 | }; |
227 | Some(vscmd_arch == arch) |
228 | } |
229 | |
230 | /// Attempt to find the tool using environment variables set by vcvars. |
231 | pub fn find_msvc_environment(tool: &str, target: &str) -> Option<Tool> { |
232 | // Early return if the environment doesn't contain a VC install. |
233 | if env::var_os("VCINSTALLDIR" ).is_none() { |
234 | return None; |
235 | } |
236 | let vs_install_dir = env::var_os("VSINSTALLDIR" )?.into(); |
237 | |
238 | // If the vscmd target differs from the requested target then |
239 | // attempt to get the tool using the VS install directory. |
240 | if is_vscmd_target(target) == Some(false) { |
241 | // We will only get here with versions 15+. |
242 | tool_from_vs15plus_instance(tool, target, &vs_install_dir) |
243 | } else { |
244 | // Fallback to simply using the current environment. |
245 | env::var_os("PATH" ) |
246 | .and_then(|path| { |
247 | env::split_paths(&path) |
248 | .map(|p| p.join(tool)) |
249 | .find(|p| p.exists()) |
250 | }) |
251 | .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) |
252 | } |
253 | } |
254 | |
255 | fn find_msbuild_vs17(target: &str) -> Option<Tool> { |
256 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "17" ) |
257 | } |
258 | |
259 | #[allow (bare_trait_objects)] |
260 | fn vs16plus_instances(target: &str, version: &'static str) -> Box<Iterator<Item = PathBuf>> { |
261 | let instances = if let Some(instances) = vs15plus_instances(target) { |
262 | instances |
263 | } else { |
264 | return Box::new(iter::empty()); |
265 | }; |
266 | Box::new(instances.into_iter().filter_map(move |instance| { |
267 | let installation_name = instance.installation_name()?; |
268 | if installation_name.starts_with(&format!("VisualStudio/ {}." , version)) { |
269 | Some(instance.installation_path()?) |
270 | } else if installation_name.starts_with(&format!("VisualStudioPreview/ {}." , version)) { |
271 | Some(instance.installation_path()?) |
272 | } else { |
273 | None |
274 | } |
275 | })) |
276 | } |
277 | |
278 | fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option<Tool> { |
279 | vs16plus_instances(target, version) |
280 | .filter_map(|path| { |
281 | let path = path.join(tool); |
282 | if !path.is_file() { |
283 | return None; |
284 | } |
285 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
286 | if target.contains("x86_64" ) { |
287 | tool.env.push(("Platform" .into(), "X64" .into())); |
288 | } |
289 | if target.contains("aarch64" ) { |
290 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
291 | } |
292 | Some(tool) |
293 | }) |
294 | .next() |
295 | } |
296 | |
297 | fn find_msbuild_vs16(target: &str) -> Option<Tool> { |
298 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "16" ) |
299 | } |
300 | |
301 | // In MSVC 15 (2017) MS once again changed the scheme for locating |
302 | // the tooling. Now we must go through some COM interfaces, which |
303 | // is super fun for Rust. |
304 | // |
305 | // Note that much of this logic can be found [online] wrt paths, COM, etc. |
306 | // |
307 | // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ |
308 | // |
309 | // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined. |
310 | // |
311 | // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. |
312 | // Hence, as the last resort we try to use vswhere.exe to list available instances. |
313 | fn vs15plus_instances(target: &str) -> Option<VsInstances> { |
314 | vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) |
315 | } |
316 | |
317 | fn vs15plus_instances_using_com() -> Option<VsInstances> { |
318 | com::initialize().ok()?; |
319 | |
320 | let config = SetupConfiguration::new().ok()?; |
321 | let enum_setup_instances = config.enum_all_instances().ok()?; |
322 | |
323 | Some(VsInstances::ComBased(enum_setup_instances)) |
324 | } |
325 | |
326 | fn vs15plus_instances_using_vswhere(target: &str) -> Option<VsInstances> { |
327 | let program_files_path: PathBuf = env::var("ProgramFiles(x86)" ) |
328 | .or_else(|_| env::var("ProgramFiles" )) |
329 | .ok()? |
330 | .into(); |
331 | |
332 | let vswhere_path = |
333 | program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe" ); |
334 | |
335 | if !vswhere_path.exists() { |
336 | return None; |
337 | } |
338 | |
339 | let arch = target.split('-' ).next().unwrap(); |
340 | let tools_arch = match arch { |
341 | "i586" | "i686" | "x86_64" => Some("x86.x64" ), |
342 | "arm" | "thumbv7a" => Some("ARM" ), |
343 | "aarch64" => Some("ARM64" ), |
344 | _ => None, |
345 | }; |
346 | |
347 | let vswhere_output = Command::new(vswhere_path) |
348 | .args(&[ |
349 | "-latest" , |
350 | "-products" , |
351 | "*" , |
352 | "-requires" , |
353 | &format!("Microsoft.VisualStudio.Component.VC.Tools. {}" , tools_arch?), |
354 | "-format" , |
355 | "text" , |
356 | "-nologo" , |
357 | ]) |
358 | .stderr(std::process::Stdio::inherit()) |
359 | .output() |
360 | .ok()?; |
361 | |
362 | let vs_instances = |
363 | VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?); |
364 | |
365 | Some(vs_instances) |
366 | } |
367 | |
368 | // Inspired from official microsoft/vswhere ParseVersionString |
369 | // i.e. at most four u16 numbers separated by '.' |
370 | fn parse_version(version: &str) -> Option<Vec<u16>> { |
371 | version |
372 | .split('.' ) |
373 | .map(|chunk| u16::from_str(chunk).ok()) |
374 | .collect() |
375 | } |
376 | |
377 | pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> { |
378 | let iter = vs15plus_instances(target)?; |
379 | iter.into_iter() |
380 | .filter_map(|instance| { |
381 | let version = parse_version(&instance.installation_version()?)?; |
382 | let instance_path = instance.installation_path()?; |
383 | let tool = tool_from_vs15plus_instance(tool, target, &instance_path)?; |
384 | Some((version, tool)) |
385 | }) |
386 | .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version)) |
387 | .map(|(_version, tool)| tool) |
388 | } |
389 | |
390 | // While the paths to Visual Studio 2017's devenv and MSBuild could |
391 | // potentially be retrieved from the registry, finding them via |
392 | // SetupConfiguration has shown to be [more reliable], and is preferred |
393 | // according to Microsoft. To help head off potential regressions though, |
394 | // we keep the registry method as a fallback option. |
395 | // |
396 | // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 |
397 | fn find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool> { |
398 | let mut path = match vs15plus_instances(target) { |
399 | Some(instances) => instances |
400 | .into_iter() |
401 | .filter_map(|instance| instance.installation_path()) |
402 | .map(|path| path.join(tool)) |
403 | .find(|ref path| path.is_file()), |
404 | None => None, |
405 | }; |
406 | |
407 | if path.is_none() { |
408 | let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7" ; |
409 | path = LOCAL_MACHINE |
410 | .open(key.as_ref()) |
411 | .ok() |
412 | .and_then(|key| key.query_str("15.0" ).ok()) |
413 | .map(|path| PathBuf::from(path).join(tool)) |
414 | .and_then(|path| if path.is_file() { Some(path) } else { None }); |
415 | } |
416 | |
417 | path.map(|path| { |
418 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
419 | if target.contains("x86_64" ) { |
420 | tool.env.push(("Platform" .into(), "X64" .into())); |
421 | } |
422 | if target.contains("aarch64" ) { |
423 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
424 | } |
425 | tool |
426 | }) |
427 | } |
428 | |
429 | fn tool_from_vs15plus_instance( |
430 | tool: &str, |
431 | target: &str, |
432 | instance_path: &PathBuf, |
433 | ) -> Option<Tool> { |
434 | let (root_path, bin_path, host_dylib_path, lib_path, include_path) = |
435 | vs15plus_vc_paths(target, instance_path)?; |
436 | let tool_path = bin_path.join(tool); |
437 | if !tool_path.exists() { |
438 | return None; |
439 | }; |
440 | |
441 | let mut tool = MsvcTool::new(tool_path); |
442 | tool.path.push(bin_path.clone()); |
443 | tool.path.push(host_dylib_path); |
444 | tool.libs.push(lib_path); |
445 | tool.include.push(include_path); |
446 | |
447 | if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) { |
448 | tool.libs.push(atl_lib_path); |
449 | tool.include.push(atl_include_path); |
450 | } |
451 | |
452 | add_sdks(&mut tool, target)?; |
453 | |
454 | Some(tool.into_tool()) |
455 | } |
456 | |
457 | fn vs15plus_vc_paths( |
458 | target: &str, |
459 | instance_path: &PathBuf, |
460 | ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, PathBuf)> { |
461 | let version_path = |
462 | instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" ); |
463 | let mut version_file = File::open(version_path).ok()?; |
464 | let mut version = String::new(); |
465 | version_file.read_to_string(&mut version).ok()?; |
466 | let version = version.trim(); |
467 | let host = match host_arch() { |
468 | X86 => "X86" , |
469 | X86_64 => "X64" , |
470 | // There is no natively hosted compiler on ARM64. |
471 | // Instead, use the x86 toolchain under emulation (there is no x64 emulation). |
472 | AARCH64 => "X86" , |
473 | _ => return None, |
474 | }; |
475 | let target = lib_subdir(target)?; |
476 | // The directory layout here is MSVC/bin/Host$host/$target/ |
477 | let path = instance_path.join(r"VC\Tools\MSVC" ).join(version); |
478 | // This is the path to the toolchain for a particular target, running |
479 | // on a given host |
480 | let bin_path = path |
481 | .join("bin" ) |
482 | .join(&format!("Host {}" , host)) |
483 | .join(&target); |
484 | // But! we also need PATH to contain the target directory for the host |
485 | // architecture, because it contains dlls like mspdb140.dll compiled for |
486 | // the host architecture. |
487 | let host_dylib_path = path |
488 | .join("bin" ) |
489 | .join(&format!("Host {}" , host)) |
490 | .join(&host.to_lowercase()); |
491 | let lib_path = path.join("lib" ).join(&target); |
492 | let include_path = path.join("include" ); |
493 | Some((path, bin_path, host_dylib_path, lib_path, include_path)) |
494 | } |
495 | |
496 | fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { |
497 | let atl_path = path.join("atlmfc" ); |
498 | let sub = lib_subdir(target)?; |
499 | if atl_path.exists() { |
500 | Some((atl_path.join("lib" ).join(sub), atl_path.join("include" ))) |
501 | } else { |
502 | None |
503 | } |
504 | } |
505 | |
506 | // For MSVC 14 we need to find the Universal CRT as well as either |
507 | // the Windows 10 SDK or Windows 8.1 SDK. |
508 | pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { |
509 | let vcdir = get_vc_dir("14.0" )?; |
510 | let mut tool = get_tool(tool, &vcdir, target)?; |
511 | add_sdks(&mut tool, target)?; |
512 | Some(tool.into_tool()) |
513 | } |
514 | |
515 | fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { |
516 | let sub = lib_subdir(target)?; |
517 | let (ucrt, ucrt_version) = get_ucrt_dir()?; |
518 | |
519 | let host = match host_arch() { |
520 | X86 => "x86" , |
521 | X86_64 => "x64" , |
522 | AARCH64 => "arm64" , |
523 | _ => return None, |
524 | }; |
525 | |
526 | tool.path |
527 | .push(ucrt.join("bin" ).join(&ucrt_version).join(host)); |
528 | |
529 | let ucrt_include = ucrt.join("include" ).join(&ucrt_version); |
530 | tool.include.push(ucrt_include.join("ucrt" )); |
531 | |
532 | let ucrt_lib = ucrt.join("lib" ).join(&ucrt_version); |
533 | tool.libs.push(ucrt_lib.join("ucrt" ).join(sub)); |
534 | |
535 | if let Some((sdk, version)) = get_sdk10_dir() { |
536 | tool.path.push(sdk.join("bin" ).join(host)); |
537 | let sdk_lib = sdk.join("lib" ).join(&version); |
538 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
539 | let sdk_include = sdk.join("include" ).join(&version); |
540 | tool.include.push(sdk_include.join("um" )); |
541 | tool.include.push(sdk_include.join("cppwinrt" )); |
542 | tool.include.push(sdk_include.join("winrt" )); |
543 | tool.include.push(sdk_include.join("shared" )); |
544 | } else if let Some(sdk) = get_sdk81_dir() { |
545 | tool.path.push(sdk.join("bin" ).join(host)); |
546 | let sdk_lib = sdk.join("lib" ).join("winv6.3" ); |
547 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
548 | let sdk_include = sdk.join("include" ); |
549 | tool.include.push(sdk_include.join("um" )); |
550 | tool.include.push(sdk_include.join("winrt" )); |
551 | tool.include.push(sdk_include.join("shared" )); |
552 | } |
553 | |
554 | Some(()) |
555 | } |
556 | |
557 | // For MSVC 12 we need to find the Windows 8.1 SDK. |
558 | pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { |
559 | let vcdir = get_vc_dir("12.0" )?; |
560 | let mut tool = get_tool(tool, &vcdir, target)?; |
561 | let sub = lib_subdir(target)?; |
562 | let sdk81 = get_sdk81_dir()?; |
563 | tool.path.push(sdk81.join("bin" ).join(sub)); |
564 | let sdk_lib = sdk81.join("lib" ).join("winv6.3" ); |
565 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
566 | let sdk_include = sdk81.join("include" ); |
567 | tool.include.push(sdk_include.join("shared" )); |
568 | tool.include.push(sdk_include.join("um" )); |
569 | tool.include.push(sdk_include.join("winrt" )); |
570 | Some(tool.into_tool()) |
571 | } |
572 | |
573 | // For MSVC 11 we need to find the Windows 8 SDK. |
574 | pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { |
575 | let vcdir = get_vc_dir("11.0" )?; |
576 | let mut tool = get_tool(tool, &vcdir, target)?; |
577 | let sub = lib_subdir(target)?; |
578 | let sdk8 = get_sdk8_dir()?; |
579 | tool.path.push(sdk8.join("bin" ).join(sub)); |
580 | let sdk_lib = sdk8.join("lib" ).join("win8" ); |
581 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
582 | let sdk_include = sdk8.join("include" ); |
583 | tool.include.push(sdk_include.join("shared" )); |
584 | tool.include.push(sdk_include.join("um" )); |
585 | tool.include.push(sdk_include.join("winrt" )); |
586 | Some(tool.into_tool()) |
587 | } |
588 | |
589 | fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) { |
590 | let prev = env::var_os(env).unwrap_or(OsString::new()); |
591 | let prev = env::split_paths(&prev); |
592 | let new = paths.into_iter().chain(prev); |
593 | tool.env |
594 | .push((env.to_string().into(), env::join_paths(new).unwrap())); |
595 | } |
596 | |
597 | // Given a possible MSVC installation directory, we look for the linker and |
598 | // then add the MSVC library path. |
599 | fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> { |
600 | bin_subdir(target) |
601 | .into_iter() |
602 | .map(|(sub, host)| { |
603 | ( |
604 | path.join("bin" ).join(sub).join(tool), |
605 | path.join("bin" ).join(host), |
606 | ) |
607 | }) |
608 | .filter(|&(ref path, _)| path.is_file()) |
609 | .map(|(path, host)| { |
610 | let mut tool = MsvcTool::new(path); |
611 | tool.path.push(host); |
612 | tool |
613 | }) |
614 | .filter_map(|mut tool| { |
615 | let sub = vc_lib_subdir(target)?; |
616 | tool.libs.push(path.join("lib" ).join(sub)); |
617 | tool.include.push(path.join("include" )); |
618 | let atlmfc_path = path.join("atlmfc" ); |
619 | if atlmfc_path.exists() { |
620 | tool.libs.push(atlmfc_path.join("lib" ).join(sub)); |
621 | tool.include.push(atlmfc_path.join("include" )); |
622 | } |
623 | Some(tool) |
624 | }) |
625 | .next() |
626 | } |
627 | |
628 | // To find MSVC we look in a specific registry key for the version we are |
629 | // trying to find. |
630 | fn get_vc_dir(ver: &str) -> Option<PathBuf> { |
631 | let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7" ; |
632 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
633 | let path = key.query_str(ver).ok()?; |
634 | Some(path.into()) |
635 | } |
636 | |
637 | // To find the Universal CRT we look in a specific registry key for where |
638 | // all the Universal CRTs are located and then sort them asciibetically to |
639 | // find the newest version. While this sort of sorting isn't ideal, it is |
640 | // what vcvars does so that's good enough for us. |
641 | // |
642 | // Returns a pair of (root, version) for the ucrt dir if found |
643 | fn get_ucrt_dir() -> Option<(PathBuf, String)> { |
644 | let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots" ; |
645 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
646 | let root = key.query_str("KitsRoot10" ).ok()?; |
647 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
648 | let max_libdir = readdir |
649 | .filter_map(|dir| dir.ok()) |
650 | .map(|dir| dir.path()) |
651 | .filter(|dir| { |
652 | dir.components() |
653 | .last() |
654 | .and_then(|c| c.as_os_str().to_str()) |
655 | .map(|c| c.starts_with("10." ) && dir.join("ucrt" ).is_dir()) |
656 | .unwrap_or(false) |
657 | }) |
658 | .max()?; |
659 | let version = max_libdir.components().last().unwrap(); |
660 | let version = version.as_os_str().to_str().unwrap().to_string(); |
661 | Some((root.into(), version)) |
662 | } |
663 | |
664 | // Vcvars finds the correct version of the Windows 10 SDK by looking |
665 | // for the include `um\Windows.h` because sometimes a given version will |
666 | // only have UCRT bits without the rest of the SDK. Since we only care about |
667 | // libraries and not includes, we instead look for `um\x64\kernel32.lib`. |
668 | // Since the 32-bit and 64-bit libraries are always installed together we |
669 | // only need to bother checking x64, making this code a tiny bit simpler. |
670 | // Like we do for the Universal CRT, we sort the possibilities |
671 | // asciibetically to find the newest one as that is what vcvars does. |
672 | // Before doing that, we check the "WindowsSdkDir" and "WindowsSDKVersion" |
673 | // environment variables set by vcvars to use the environment sdk version |
674 | // if one is already configured. |
675 | fn get_sdk10_dir() -> Option<(PathBuf, String)> { |
676 | if let (Ok(root), Ok(version)) = (env::var("WindowsSdkDir" ), env::var("WindowsSDKVersion" )) |
677 | { |
678 | return Some((root.into(), version.trim_end_matches(' \\' ).to_string())); |
679 | } |
680 | |
681 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0" ; |
682 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
683 | let root = key.query_str("InstallationFolder" ).ok()?; |
684 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
685 | let mut dirs = readdir |
686 | .filter_map(|dir| dir.ok()) |
687 | .map(|dir| dir.path()) |
688 | .collect::<Vec<_>>(); |
689 | dirs.sort(); |
690 | let dir = dirs |
691 | .into_iter() |
692 | .rev() |
693 | .filter(|dir| dir.join("um" ).join("x64" ).join("kernel32.lib" ).is_file()) |
694 | .next()?; |
695 | let version = dir.components().last().unwrap(); |
696 | let version = version.as_os_str().to_str().unwrap().to_string(); |
697 | Some((root.into(), version)) |
698 | } |
699 | |
700 | // Interestingly there are several subdirectories, `win7` `win8` and |
701 | // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same |
702 | // applies to us. Note that if we were targeting kernel mode drivers |
703 | // instead of user mode applications, we would care. |
704 | fn get_sdk81_dir() -> Option<PathBuf> { |
705 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1" ; |
706 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
707 | let root = key.query_str("InstallationFolder" ).ok()?; |
708 | Some(root.into()) |
709 | } |
710 | |
711 | fn get_sdk8_dir() -> Option<PathBuf> { |
712 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0" ; |
713 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
714 | let root = key.query_str("InstallationFolder" ).ok()?; |
715 | Some(root.into()) |
716 | } |
717 | |
718 | const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; |
719 | const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; |
720 | const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; |
721 | const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL; |
722 | const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64; |
723 | const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64; |
724 | |
725 | // When choosing the tool to use, we have to choose the one which matches |
726 | // the target architecture. Otherwise we end up in situations where someone |
727 | // on 32-bit Windows is trying to cross compile to 64-bit and it tries to |
728 | // invoke the native 64-bit compiler which won't work. |
729 | // |
730 | // For the return value of this function, the first member of the tuple is |
731 | // the folder of the tool we will be invoking, while the second member is |
732 | // the folder of the host toolchain for that tool which is essential when |
733 | // using a cross linker. We return a Vec since on x64 there are often two |
734 | // linkers that can target the architecture we desire. The 64-bit host |
735 | // linker is preferred, and hence first, due to 64-bit allowing it more |
736 | // address space to work with and potentially being faster. |
737 | fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> { |
738 | let arch = target.split('-' ).next().unwrap(); |
739 | match (arch, host_arch()) { |
740 | ("i586" , X86) | ("i686" , X86) => vec![("" , "" )], |
741 | ("i586" , X86_64) | ("i686" , X86_64) => vec![("amd64_x86" , "amd64" ), ("" , "" )], |
742 | ("x86_64" , X86) => vec![("x86_amd64" , "" )], |
743 | ("x86_64" , X86_64) => vec![("amd64" , "amd64" ), ("x86_amd64" , "" )], |
744 | ("arm" , X86) | ("thumbv7a" , X86) => vec![("x86_arm" , "" )], |
745 | ("arm" , X86_64) | ("thumbv7a" , X86_64) => vec![("amd64_arm" , "amd64" ), ("x86_arm" , "" )], |
746 | _ => vec![], |
747 | } |
748 | } |
749 | |
750 | fn lib_subdir(target: &str) -> Option<&'static str> { |
751 | let arch = target.split('-' ).next().unwrap(); |
752 | match arch { |
753 | "i586" | "i686" => Some("x86" ), |
754 | "x86_64" => Some("x64" ), |
755 | "arm" | "thumbv7a" => Some("arm" ), |
756 | "aarch64" => Some("arm64" ), |
757 | _ => None, |
758 | } |
759 | } |
760 | |
761 | // MSVC's x86 libraries are not in a subfolder |
762 | fn vc_lib_subdir(target: &str) -> Option<&'static str> { |
763 | let arch = target.split('-' ).next().unwrap(); |
764 | match arch { |
765 | "i586" | "i686" => Some("" ), |
766 | "x86_64" => Some("amd64" ), |
767 | "arm" | "thumbv7a" => Some("arm" ), |
768 | "aarch64" => Some("arm64" ), |
769 | _ => None, |
770 | } |
771 | } |
772 | |
773 | #[allow (bad_style)] |
774 | fn host_arch() -> u16 { |
775 | type DWORD = u32; |
776 | type WORD = u16; |
777 | type LPVOID = *mut u8; |
778 | type DWORD_PTR = usize; |
779 | |
780 | #[repr (C)] |
781 | struct SYSTEM_INFO { |
782 | wProcessorArchitecture: WORD, |
783 | _wReserved: WORD, |
784 | _dwPageSize: DWORD, |
785 | _lpMinimumApplicationAddress: LPVOID, |
786 | _lpMaximumApplicationAddress: LPVOID, |
787 | _dwActiveProcessorMask: DWORD_PTR, |
788 | _dwNumberOfProcessors: DWORD, |
789 | _dwProcessorType: DWORD, |
790 | _dwAllocationGranularity: DWORD, |
791 | _wProcessorLevel: WORD, |
792 | _wProcessorRevision: WORD, |
793 | } |
794 | |
795 | extern "system" { |
796 | fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); |
797 | } |
798 | |
799 | unsafe { |
800 | let mut info = mem::zeroed(); |
801 | GetNativeSystemInfo(&mut info); |
802 | info.wProcessorArchitecture |
803 | } |
804 | } |
805 | |
806 | // Given a registry key, look at all the sub keys and find the one which has |
807 | // the maximal numeric value. |
808 | // |
809 | // Returns the name of the maximal key as well as the opened maximal key. |
810 | fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { |
811 | let mut max_vers = 0; |
812 | let mut max_key = None; |
813 | for subkey in key.iter().filter_map(|k| k.ok()) { |
814 | let val = subkey |
815 | .to_str() |
816 | .and_then(|s| s.trim_left_matches("v" ).replace("." , "" ).parse().ok()); |
817 | let val = match val { |
818 | Some(s) => s, |
819 | None => continue, |
820 | }; |
821 | if val > max_vers { |
822 | if let Ok(k) = key.open(&subkey) { |
823 | max_vers = val; |
824 | max_key = Some((subkey, k)); |
825 | } |
826 | } |
827 | } |
828 | max_key |
829 | } |
830 | |
831 | pub fn has_msbuild_version(version: &str) -> bool { |
832 | match version { |
833 | "17.0" => { |
834 | find_msbuild_vs17("x86_64-pc-windows-msvc" ).is_some() |
835 | || find_msbuild_vs17("i686-pc-windows-msvc" ).is_some() |
836 | || find_msbuild_vs17("aarch64-pc-windows-msvc" ).is_some() |
837 | } |
838 | "16.0" => { |
839 | find_msbuild_vs16("x86_64-pc-windows-msvc" ).is_some() |
840 | || find_msbuild_vs16("i686-pc-windows-msvc" ).is_some() |
841 | || find_msbuild_vs16("aarch64-pc-windows-msvc" ).is_some() |
842 | } |
843 | "15.0" => { |
844 | find_msbuild_vs15("x86_64-pc-windows-msvc" ).is_some() |
845 | || find_msbuild_vs15("i686-pc-windows-msvc" ).is_some() |
846 | || find_msbuild_vs15("aarch64-pc-windows-msvc" ).is_some() |
847 | } |
848 | "12.0" | "14.0" => LOCAL_MACHINE |
849 | .open(&OsString::from(format!( |
850 | "SOFTWARE \\Microsoft \\MSBuild \\ToolsVersions \\{}" , |
851 | version |
852 | ))) |
853 | .is_ok(), |
854 | _ => false, |
855 | } |
856 | } |
857 | |
858 | pub fn find_devenv(target: &str) -> Option<Tool> { |
859 | find_devenv_vs15(&target) |
860 | } |
861 | |
862 | fn find_devenv_vs15(target: &str) -> Option<Tool> { |
863 | find_tool_in_vs15_path(r"Common7\IDE\devenv.exe" , target) |
864 | } |
865 | |
866 | // see http://stackoverflow.com/questions/328017/path-to-msbuild |
867 | pub fn find_msbuild(target: &str) -> Option<Tool> { |
868 | // VS 15 (2017) changed how to locate msbuild |
869 | if let Some(r) = find_msbuild_vs17(target) { |
870 | return Some(r); |
871 | } else if let Some(r) = find_msbuild_vs16(target) { |
872 | return Some(r); |
873 | } else if let Some(r) = find_msbuild_vs15(target) { |
874 | return Some(r); |
875 | } else { |
876 | find_old_msbuild(target) |
877 | } |
878 | } |
879 | |
880 | fn find_msbuild_vs15(target: &str) -> Option<Tool> { |
881 | find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe" , target) |
882 | } |
883 | |
884 | fn find_old_msbuild(target: &str) -> Option<Tool> { |
885 | let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions" ; |
886 | LOCAL_MACHINE |
887 | .open(key.as_ref()) |
888 | .ok() |
889 | .and_then(|key| { |
890 | max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath" ).ok()) |
891 | }) |
892 | .map(|path| { |
893 | let mut path = PathBuf::from(path); |
894 | path.push("MSBuild.exe" ); |
895 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
896 | if target.contains("x86_64" ) { |
897 | tool.env.push(("Platform" .into(), "X64" .into())); |
898 | } |
899 | tool |
900 | }) |
901 | } |
902 | } |
903 | |