use std::process::Command;
use Tool;
#[cfg(windows)]
macro_rules! otry {
($expr:expr) => (match $expr {
Some(val) => val,
None => return None,
})
}
pub fn find(target: &str, tool: &str) -> Option<Command> {
find_tool(target, tool).map(|c| c.to_command())
}
#[cfg(not(windows))]
pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
None
}
#[cfg(windows)]
pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
use std::env;
if !target.contains("msvc") {
return None;
}
if tool.contains("msbuild") {
return impl_::find_msbuild(target);
}
if env::var_os("VCINSTALLDIR").is_some() {
return env::var_os("PATH")
.and_then(|path| env::split_paths(&path).map(|p| p.join(tool)).find(|p| p.exists()))
.map(|path| Tool::new(path.into()));
}
return impl_::find_msvc_15(tool, target)
.or_else(|| impl_::find_msvc_14(tool, target))
.or_else(|| impl_::find_msvc_12(tool, target))
.or_else(|| impl_::find_msvc_11(tool, target));
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum VsVers {
Vs12,
Vs14,
Vs15,
#[doc(hidden)]
#[allow(bad_style)]
__Nonexhaustive_do_not_match_this_or_your_code_will_break,
}
#[cfg(not(windows))]
pub fn find_vs_version() -> Result<VsVers, String> {
Err(format!("not windows"))
}
#[cfg(windows)]
pub fn find_vs_version() -> Result<VsVers, String> {
use std::env;
match env::var("VisualStudioVersion") {
Ok(version) => {
match &version[..] {
"15.0" => Ok(VsVers::Vs15),
"14.0" => Ok(VsVers::Vs14),
"12.0" => Ok(VsVers::Vs12),
vers => Err(format!("\n\n\
unsupported or unknown VisualStudio version: {}\n\
if another version is installed consider running \
the appropriate vcvars script before building this \
crate\n\
", vers)),
}
}
_ => {
if impl_::has_msbuild_version("15.0") {
Ok(VsVers::Vs15)
} else if impl_::has_msbuild_version("14.0") {
Ok(VsVers::Vs14)
} else if impl_::has_msbuild_version("12.0") {
Ok(VsVers::Vs12)
} else {
Err(format!("\n\n\
couldn't determine visual studio generator\n\
if VisualStudio is installed, however, consider \
running the appropriate vcvars script before building \
this crate\n\
"))
}
}
}
}
#[cfg(windows)]
mod impl_ {
use std::env;
use std::ffi::OsString;
use std::mem;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Read;
use registry::{RegistryKey, LOCAL_MACHINE};
use com;
use setup_config::{SetupConfiguration, SetupInstance};
use Tool;
struct MsvcTool {
tool: PathBuf,
libs: Vec<PathBuf>,
path: Vec<PathBuf>,
include: Vec<PathBuf>,
}
impl MsvcTool {
fn new(tool: PathBuf) -> MsvcTool {
MsvcTool {
tool: tool,
libs: Vec::new(),
path: Vec::new(),
include: Vec::new(),
}
}
fn into_tool(self) -> Tool {
let MsvcTool { tool, libs, path, include } = self;
let mut tool = Tool::new(tool.into());
add_env(&mut tool, "LIB", libs);
add_env(&mut tool, "PATH", path);
add_env(&mut tool, "INCLUDE", include);
tool
}
}
pub fn find_msvc_15(tool: &str, target: &str) -> Option<Tool> {
otry!(com::initialize().ok());
let config = otry!(SetupConfiguration::new().ok());
let iter = otry!(config.enum_all_instances().ok());
for instance in iter {
let instance = otry!(instance.ok());
let tool = tool_from_vs15_instance(tool, target, &instance);
if tool.is_some() {
return tool;
}
}
None
}
fn tool_from_vs15_instance(tool: &str, target: &str,
instance: &SetupInstance) -> Option<Tool> {
let (bin_path, host_dylib_path, lib_path, include_path) = otry!(vs15_vc_paths(target, instance));
let tool_path = bin_path.join(tool);
if !tool_path.exists() { return None };
let mut tool = MsvcTool::new(tool_path);
tool.path.push(host_dylib_path);
tool.libs.push(lib_path);
tool.include.push(include_path);
if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &bin_path) {
tool.libs.push(atl_lib_path);
tool.include.push(atl_include_path);
}
otry!(add_sdks(&mut tool, target));
Some(tool.into_tool())
}
fn vs15_vc_paths(target: &str, instance: &SetupInstance) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)> {
let instance_path: PathBuf = otry!(instance.installation_path().ok()).into();
let version_path = instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
let mut version_file = otry!(File::open(version_path).ok());
let mut version = String::new();
otry!(version_file.read_to_string(&mut version).ok());
let version = version.trim();
let host = match host_arch() {
X86 => "X86",
X86_64 => "X64",
_ => return None,
};
let target = otry!(lib_subdir(target));
let path = instance_path.join(r"VC\Tools\MSVC").join(version);
let bin_path = path.join("bin").join(&format!("Host{}", host)).join(&target);
let host_dylib_path = path.join("bin").join(&format!("Host{}", host)).join(&host.to_lowercase());
let lib_path = path.join("lib").join(&target);
let include_path = path.join("include");
Some((bin_path, host_dylib_path, lib_path, include_path))
}
fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> {
let atl_path = path.join("atlfmc");
let sub = otry!(lib_subdir(target));
if atl_path.exists() {
Some((atl_path.join("lib").join(sub), atl_path.join("include")))
} else {
None
}
}
pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir("14.0"));
let mut tool = otry!(get_tool(tool, &vcdir, target));
otry!(add_sdks(&mut tool, target));
Some(tool.into_tool())
}
fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> {
let sub = otry!(lib_subdir(target));
let (ucrt, ucrt_version) = otry!(get_ucrt_dir());
tool.path.push(ucrt.join("bin").join(&ucrt_version).join(sub));
let ucrt_include = ucrt.join("include").join(&ucrt_version);
tool.include.push(ucrt_include.join("ucrt"));
let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
tool.libs.push(ucrt_lib.join("ucrt").join(sub));
if let Some((sdk, version)) = get_sdk10_dir() {
tool.path.push(sdk.join("bin").join(sub));
let sdk_lib = sdk.join("lib").join(&version);
tool.libs.push(sdk_lib.join("um").join(sub));
let sdk_include = sdk.join("include").join(&version);
tool.include.push(sdk_include.join("um"));
tool.include.push(sdk_include.join("winrt"));
tool.include.push(sdk_include.join("shared"));
} else if let Some(sdk) = get_sdk81_dir() {
tool.path.push(sdk.join("bin").join(sub));
let sdk_lib = sdk.join("lib").join("winv6.3");
tool.libs.push(sdk_lib.join("um").join(sub));
let sdk_include = sdk.join("include");
tool.include.push(sdk_include.join("um"));
tool.include.push(sdk_include.join("winrt"));
tool.include.push(sdk_include.join("shared"));
}
Some(())
}
pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir("12.0"));
let mut tool = otry!(get_tool(tool, &vcdir, target));
let sub = otry!(lib_subdir(target));
let sdk81 = otry!(get_sdk81_dir());
tool.path.push(sdk81.join("bin").join(sub));
let sdk_lib = sdk81.join("lib").join("winv6.3");
tool.libs.push(sdk_lib.join("um").join(sub));
let sdk_include = sdk81.join("include");
tool.include.push(sdk_include.join("shared"));
tool.include.push(sdk_include.join("um"));
tool.include.push(sdk_include.join("winrt"));
Some(tool.into_tool())
}
pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir("11.0"));
let mut tool = otry!(get_tool(tool, &vcdir, target));
let sub = otry!(lib_subdir(target));
let sdk8 = otry!(get_sdk8_dir());
tool.path.push(sdk8.join("bin").join(sub));
let sdk_lib = sdk8.join("lib").join("win8");
tool.libs.push(sdk_lib.join("um").join(sub));
let sdk_include = sdk8.join("include");
tool.include.push(sdk_include.join("shared"));
tool.include.push(sdk_include.join("um"));
tool.include.push(sdk_include.join("winrt"));
Some(tool.into_tool())
}
fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
let prev = env::var_os(env).unwrap_or(OsString::new());
let prev = env::split_paths(&prev);
let new = paths.into_iter().chain(prev);
tool.env.push((env.to_string().into(), env::join_paths(new).unwrap()));
}
fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
bin_subdir(target)
.into_iter()
.map(|(sub, host)| (path.join("bin").join(sub).join(tool), path.join("bin").join(host)))
.filter(|&(ref path, _)| path.is_file())
.map(|(path, host)| {
let mut tool = MsvcTool::new(path);
tool.path.push(host);
tool
})
.filter_map(|mut tool| {
let sub = otry!(vc_lib_subdir(target));
tool.libs.push(path.join("lib").join(sub));
tool.include.push(path.join("include"));
let atlmfc_path = path.join("atlmfc");
if atlmfc_path.exists() {
tool.libs.push(atlmfc_path.join("lib").join(sub));
tool.include.push(atlmfc_path.join("include"));
}
Some(tool)
})
.next()
}
fn get_vc_dir(ver: &str) -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let path = otry!(key.query_str(ver).ok());
Some(path.into())
}
fn get_ucrt_dir() -> Option<(PathBuf, String)> {
let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("KitsRoot10").ok());
let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
let max_libdir = otry!(readdir.filter_map(|dir| dir.ok())
.map(|dir| dir.path())
.filter(|dir| {
dir.components()
.last()
.and_then(|c| c.as_os_str().to_str())
.map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
.unwrap_or(false)
})
.max());
let version = max_libdir.components().last().unwrap();
let version = version.as_os_str().to_str().unwrap().to_string();
Some((root.into(), version))
}
fn get_sdk10_dir() -> Option<(PathBuf, String)> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
let mut dirs = readdir.filter_map(|dir| dir.ok())
.map(|dir| dir.path())
.collect::<Vec<_>>();
dirs.sort();
let dir = otry!(dirs.into_iter()
.rev()
.filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())
.next());
let version = dir.components().last().unwrap();
let version = version.as_os_str().to_str().unwrap().to_string();
Some((root.into(), version))
}
fn get_sdk81_dir() -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
Some(root.into())
}
fn get_sdk8_dir() -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
Some(root.into())
}
const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
let arch = target.split('-').next().unwrap();
match (arch, host_arch()) {
("i586", X86) | ("i686", X86) => vec![("", "")],
("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
("x86_64", X86) => vec![("x86_amd64", "")],
("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
("arm", X86) => vec![("x86_arm", "")],
("arm", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
_ => vec![],
}
}
fn lib_subdir(target: &str) -> Option<&'static str> {
let arch = target.split('-').next().unwrap();
match arch {
"i586" | "i686" => Some("x86"),
"x86_64" => Some("x64"),
"arm" => Some("arm"),
_ => None,
}
}
fn vc_lib_subdir(target: &str) -> Option<&'static str> {
let arch = target.split('-').next().unwrap();
match arch {
"i586" | "i686" => Some(""),
"x86_64" => Some("amd64"),
"arm" => Some("arm"),
_ => None,
}
}
#[allow(bad_style)]
fn host_arch() -> u16 {
type DWORD = u32;
type WORD = u16;
type LPVOID = *mut u8;
type DWORD_PTR = usize;
#[repr(C)]
struct SYSTEM_INFO {
wProcessorArchitecture: WORD,
_wReserved: WORD,
_dwPageSize: DWORD,
_lpMinimumApplicationAddress: LPVOID,
_lpMaximumApplicationAddress: LPVOID,
_dwActiveProcessorMask: DWORD_PTR,
_dwNumberOfProcessors: DWORD,
_dwProcessorType: DWORD,
_dwAllocationGranularity: DWORD,
_wProcessorLevel: WORD,
_wProcessorRevision: WORD,
}
extern "system" {
fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
}
unsafe {
let mut info = mem::zeroed();
GetNativeSystemInfo(&mut info);
info.wProcessorArchitecture
}
}
fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
let mut max_vers = 0;
let mut max_key = None;
for subkey in key.iter().filter_map(|k| k.ok()) {
let val = subkey.to_str()
.and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok());
let val = match val {
Some(s) => s,
None => continue,
};
if val > max_vers {
if let Ok(k) = key.open(&subkey) {
max_vers = val;
max_key = Some((subkey, k));
}
}
}
max_key
}
pub fn has_msbuild_version(version: &str) -> bool {
match version {
"15.0" => {
find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() ||
find_msbuild_vs15("i686-pc-windows-msvc").is_some()
}
"12.0" | "14.0" => {
LOCAL_MACHINE.open(
&OsString::from(format!("SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
version))).is_ok()
}
_ => false
}
}
pub fn find_msbuild(target: &str) -> Option<Tool> {
if let Some(r) = find_msbuild_vs15(target) {
return Some(r);
} else {
find_old_msbuild(target)
}
}
fn find_msbuild_vs15(target: &str) -> Option<Tool> {
let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
LOCAL_MACHINE.open(key.as_ref())
.ok()
.and_then(|key| {
key.query_str("15.0").ok()
})
.map(|path| {
let path = PathBuf::from(path).join(r"MSBuild\15.0\Bin\MSBuild.exe");
let mut tool = Tool::new(path);
if target.contains("x86_64") {
tool.env.push(("Platform".into(), "X64".into()));
}
tool
})
}
fn find_old_msbuild(target: &str) -> Option<Tool> {
let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
LOCAL_MACHINE.open(key.as_ref())
.ok()
.and_then(|key| {
max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
})
.map(|path| {
let mut path = PathBuf::from(path);
path.push("MSBuild.exe");
let mut tool = Tool::new(path);
if target.contains("x86_64") {
tool.env.push(("Platform".into(), "X64".into()));
}
tool
})
}
}