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