use std::path::PathBuf; use super::prelude::*; pub struct Msvc; impl PlatformDetails for Msvc { fn uses_freetype(&self, _config: &BuildConfiguration) -> bool { false } fn gn_args(&self, config: &BuildConfiguration, builder: &mut GnArgsBuilder) { if let Some(win_vc) = resolve_vc() { builder.arg( "win_vc", quote( win_vc .to_str() .expect("Failed to convert Windows compiler path to UTF-8"), ), ); } // Tell Skia's build system where LLVM is supposed to be located. if let Some(llvm_home) = llvm::find_home() { let clang_version_dir = llvm::clang_version_dir(&llvm_home) .unwrap_or_else(|| panic!("Unable to locate Clang's version directory")); builder.arg("clang_win", quote(&llvm_home)); builder.arg("clang_win_version", quote(&clang_version_dir)); } else { panic!("Unable to locate LLVM installation"); } // Setting `target_cpu` to `i686` or `x86`, nightly builds would lead to // > 'C:/Program' is not recognized as an internal or external command // // Without it, the executables pop out just fine. See the GH job // `supplemental-builds/windows-x86`. { let arch = &config.target.architecture; if arch != "i686" { builder.arg("target_cpu", quote(clang::target_arch(arch))); } } // Disable `[[clang::trivial_abi]]` because it leads to ABI mismatches if the // bindings are compiled with a compiler other than clang (e.g. MSVC). // (see ) builder.arg("is_trivial_abi", no()); // Code on MSVC needs to be compiled differently (e.g. with /MT or /MD) // depending on the runtime being linked. (See // ) // When static feature is enabled (target-feature=+crt-static) the C runtime // should be statically linked and the compiler has to place the library name // LIBCMT.lib into the .obj See // let runtime_library = if cargo::target_crt_static() { "/MT" } else { // otherwise the C runtime should be linked dynamically "/MD" }; builder.cflag(runtime_library); } fn link_libraries(&self, features: &Features) -> Vec { generic_link_libraries(features) } } pub struct Generic; impl PlatformDetails for Generic { fn uses_freetype(&self, _config: &BuildConfiguration) -> bool { false } fn gn_args(&self, _config: &BuildConfiguration, builder: &mut GnArgsBuilder) { builder.target_os_and_default_cpu("win"); } fn link_libraries(&self, features: &Features) -> Vec { generic_link_libraries(features) } } fn generic_link_libraries(features: &Features) -> Vec { let mut libs = vec!["usp10", "ole32", "user32", "gdi32", "fontsub"]; if features.gl { libs.push("opengl32"); } if features.d3d { libs.extend(["d3d12", "dxgi", "d3dcompiler"]); } libs.iter().map(|l| l.to_string()).collect() } /// Visual Studio VC detection support /// TODO: sophisticate: fn resolve_vc() -> Option { if let Some(install_dir) = cargo::env_var("VCINSTALLDIR") { // vcvars.bat may end up setting VCINSTALLDIR to a path with trailing backslash, we // invoke GN as win_vc="the path", and we end up with "foo\", which erroneously // escapes the quote instead of closing. return Some(PathBuf::from(install_dir.trim_end_matches('\\'))); } let releases = [("Program Files", "2022"), ("Program Files (x86)", "2019")]; let editions = ["BuildTools", "Enterprise", "Professional", "Community"]; releases .iter() .flat_map(|r| editions.iter().map(move |e| (r, e))) .map(|((rp, r), ed)| format!("C:\\{rp}\\Microsoft Visual Studio\\{r}\\{ed}\\VC")) .map(PathBuf::from) .find(|pb| pb.exists()) } mod llvm { use crate::build_support::cargo; use std::{fs, path::PathBuf}; /// Locate the LLVM installation which can be used to build Skia. /// If the environment variable `LLVM_HOME` is present it will /// be used. Otherwise we search a set of common paths: /// - C:\Program Files\LLVM /// - C:\LLVM /// - %USERPROFILE%\scoop\apps\llvm\current pub fn find_home() -> Option { if let Some(llvm_home) = cargo::env_var("LLVM_HOME") { validate_home(&llvm_home) } else { // USERPROFILE *should* equate to HOME and always be defined. // TODO: If this isn't defined, should we try c:\Users\%USERNAME%? let userprofile = cargo::env_var("USERPROFILE").expect("Unable to resolve %USERPROFILE%"); let common_roots = [ "C:\\Program Files\\LLVM".into(), "C:\\LLVM".into(), format!("{userprofile}\\scoop\\apps\\llvm\\current"), ]; for root in &common_roots { let root = validate_home(root); if root.is_some() { return root; } } None } } /// Return the clang's highest version directory by scanning the directories in /// `LLVM_HOME\lib\clang\*`. pub fn clang_version_dir(home: &str) -> Option { let path: PathBuf = [home, "lib", "clang"].into_iter().collect(); let mut highest_version = None; let mut highest_version_path = None; for entry in fs::read_dir(path).ok()? { let entry = entry.ok()?; let path = entry.path(); if path.is_dir() { if let Some(v) = parse_version(path.file_name()?.to_str()?) { if Some(v) > highest_version { highest_version = Some(v); highest_version_path = Some(path); } } } } let path = highest_version_path?; Some(path.to_str()?.to_string()) } fn validate_home(home: &str) -> Option { let clang_cl: PathBuf = [home, "bin", "clang-cl.exe"].into_iter().collect(); eprintln!("Checking for {clang_cl:?}"); clang_cl.exists().then(|| home.to_string()) } fn parse_version(s: &str) -> Option<(usize, usize, usize)> { let v: Result, _> = s.split('.').map(|s| s.parse()).collect(); let v = v.ok()?; match v.len() { 1 => Some((v[0], 0, 0)), 2 => Some((v[0], v[1], 0)), 3 => Some((v[0], v[1], v[2])), _ => None, } } }