find_winsdk/
lib.rs

1// Copyright (c) 2018 FaultyRAM
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the
5// MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at
6// your option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Provides support for detecting Windows SDK installations.
10
11#![cfg(target_os = "windows")]
12#![forbid(warnings)]
13#![forbid(future_incompatible)]
14#![deny(unused)]
15#![forbid(box_pointers)]
16#![forbid(missing_copy_implementations)]
17#![forbid(missing_debug_implementations)]
18#![forbid(missing_docs)]
19#![forbid(trivial_casts)]
20#![forbid(trivial_numeric_casts)]
21#![forbid(unsafe_code)]
22#![forbid(unused_import_braces)]
23#![deny(unused_qualifications)]
24#![forbid(unused_results)]
25#![forbid(variant_size_differences)]
26#![cfg_attr(feature = "cargo-clippy", forbid(clippy))]
27#![cfg_attr(feature = "cargo-clippy", forbid(clippy_pedantic))]
28#![cfg_attr(feature = "cargo-clippy", forbid(clippy_complexity))]
29#![cfg_attr(feature = "cargo-clippy", deny(clippy_correctness))]
30#![cfg_attr(feature = "cargo-clippy", forbid(clippy_perf))]
31#![cfg_attr(feature = "cargo-clippy", forbid(clippy_style))]
32
33#[macro_use]
34extern crate serde_derive;
35extern crate winreg;
36
37use std::env;
38use std::ffi::OsStr;
39use std::io::{self, ErrorKind};
40use std::path::{Path, PathBuf};
41use winreg::enums::{KEY_WOW64_32KEY, HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE};
42use winreg::RegKey;
43
44const V10_0_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
45const V8_1A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1A";
46const V8_1_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
47const V8_0A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0A";
48const V8_0_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
49const V7_1A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1A";
50const V7_1_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1";
51const V7_0A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0a";
52const V7_0_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0";
53const V6_1A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1a";
54const V6_1_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1";
55const V6_0A_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0a";
56const V6_0_REG_KEY: &str = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0";
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59/// Windows SDK versions.
60///
61/// Note that prior to v10.0, each Windows SDK came in two flavours; one with an `A` suffix in its
62/// version number, and one without. For convenience, this crate detects both, preferring the `A`
63/// suffix version if present.
64pub enum SdkVersion {
65    /// Any Windows SDK version.
66    ///
67    /// This is either one specified by environment variables or, if that is not available, the
68    /// latest version found in the registry.
69    Any,
70    /// A Windows SDK installation specified by environment variables.
71    Env,
72    /// The Windows 10.0 SDK.
73    V10_0,
74    /// The Windows 8.1 SDK.
75    V8_1,
76    /// The Windows 8.0 SDK.
77    V8_0,
78    /// The Windows 7.1 SDK.
79    V7_1,
80    /// The Windows 7.0 SDK.
81    V7_0,
82    /// The Windows 6.1 SDK.
83    V6_1,
84    /// The Windows 6.0 SDK.
85    V6_0,
86}
87
88#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
89#[serde(rename_all = "PascalCase")]
90/// Information about a Windows SDK installation.
91pub struct SdkInfo {
92    installation_folder: PathBuf,
93    product_name: Option<String>,
94    product_version: String,
95}
96
97impl SdkInfo {
98    /// Returns installation information for a Windows SDK installation.
99    ///
100    /// If `SdkVersion::Any` is specified, this method will first query environment variables, then
101    /// search the registry for the latest Windows SDK recognised by this crate. If a specific
102    /// version is specified, this method will only look for that version before giving up.
103    pub fn find(version: SdkVersion) -> io::Result<Option<Self>> {
104        match version {
105            SdkVersion::Any => {
106                use SdkVersion::*;
107                let vers = [Env, V10_0, V8_1, V8_0, V7_1, V7_0, V6_1, V6_0];
108                for res in vers.iter().map(|v| Self::find(*v)) {
109                    match res {
110                        Ok(None) => (),
111                        _ => return res,
112                    }
113                }
114                Ok(None)
115            }
116            SdkVersion::Env => Ok(Self::query_env()),
117            SdkVersion::V10_0 => Self::query_reg(V10_0_REG_KEY),
118            SdkVersion::V8_1 => Self::find_double_release((V8_1A_REG_KEY, V8_1_REG_KEY)),
119            SdkVersion::V8_0 => Self::find_double_release((V8_0A_REG_KEY, V8_0_REG_KEY)),
120            SdkVersion::V7_1 => Self::find_double_release((V7_1A_REG_KEY, V7_1_REG_KEY)),
121            SdkVersion::V7_0 => Self::find_double_release((V7_0A_REG_KEY, V7_0_REG_KEY)),
122            SdkVersion::V6_1 => Self::find_double_release((V6_1A_REG_KEY, V6_1_REG_KEY)),
123            SdkVersion::V6_0 => Self::find_double_release((V6_0A_REG_KEY, V6_0_REG_KEY)),
124        }
125    }
126
127    fn find_double_release(keys: (&str, &str)) -> io::Result<Option<Self>> {
128        let res = Self::query_reg(keys.0);
129        match res {
130            Ok(None) => Self::query_reg(keys.1),
131            _ => res,
132        }
133    }
134
135    /// Returns installation information for a Windows SDK from environment variables, if present.
136    fn query_env() -> Option<Self> {
137        env::var_os("WindowsSdkDir")
138            .and_then(|install_dir| {
139                env::var_os("WindowsSdkVersion").map(|version| (install_dir, version))
140            })
141            .map(|(install_dir, version)| {
142                let ver = version
143                    .into_string()
144                    .map(|s| {
145                        s.split(r".0\")
146                            .next()
147                            .expect("`str::split` failed")
148                            .to_owned()
149                    })
150                    .expect("`WindowsSdkVersion` was not valid UTF-8");
151                Self {
152                    installation_folder: Path::new(&install_dir).to_owned(),
153                    product_name: None,
154                    product_version: ver,
155                }
156            })
157    }
158
159    fn query_reg<P: AsRef<OsStr>>(subkey: P) -> io::Result<Option<Self>> {
160        RegKey::predef(HKEY_LOCAL_MACHINE)
161            .open_subkey_with_flags(subkey, KEY_QUERY_VALUE | KEY_WOW64_32KEY)
162            .map(|key| {
163                // If deserialization fails, the key might not have been deleted correctly.
164                if let Ok(info) = key.decode() {
165                    Some(info)
166                } else {
167                    None
168                }
169            })
170            .or_else(|e| {
171                if e.kind() == ErrorKind::NotFound {
172                    Ok(None)
173                } else {
174                    Err(e)
175                }
176            })
177    }
178
179    /// Returns the filesystem path to where a Windows SDK instance is installed.
180    pub fn installation_folder(&self) -> &Path {
181        &self.installation_folder
182    }
183
184    /// Returns the human-readable name of a Windows SDK instance.
185    pub fn product_name(&self) -> Option<&str> {
186        self.product_name.as_ref().map(|s| s.as_ref())
187    }
188
189    /// Returns the version number of a Windows SDK instance.
190    pub fn product_version(&self) -> &str {
191        &self.product_version
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use {SdkInfo, SdkVersion};
198
199    #[test]
200    fn any() {
201        let _ = SdkInfo::find(SdkVersion::Any)
202            .expect("could not retrieve Windows SDK info from registry")
203            .expect("Windows SDK is not installed");
204    }
205
206    #[test]
207    fn winsdk_env() {
208        let _ = SdkInfo::find(SdkVersion::Env)
209            .unwrap_or_else(|_| unreachable!())
210            .expect("environment does not specify a Windows SDK installation");
211    }
212
213    #[test]
214    fn winsdk_10_0() {
215        let _ = SdkInfo::find(SdkVersion::V10_0)
216            .expect("could not retrieve Windows 10 SDK info from registry")
217            .expect("Windows 10 SDK is not installed");
218    }
219
220    #[test]
221    fn winsdk_8_1() {
222        let _ = SdkInfo::find(SdkVersion::V8_1)
223            .expect("could not retrieve Windows 8.1 SDK info from registry")
224            .expect("Windows 8.1 SDK is not installed");
225    }
226}