tugger_windows/
vc_redistributable.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use {
6    anyhow::{anyhow, Result},
7    once_cell::sync::Lazy,
8    std::{
9        fmt::{Display, Formatter},
10        path::PathBuf,
11    },
12    tugger_common::http::RemoteContent,
13};
14
15#[cfg(windows)]
16use {crate::find_vswhere, std::collections::BTreeMap};
17
18// Latest versions of the VC++ Redistributable can be found at
19// https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads.
20// The download URL will redirect to a deterministic artifact, which is what we
21// record here.
22
23pub static VC_REDIST_X86: Lazy<RemoteContent> = Lazy::new(|| {
24    RemoteContent {
25        name: "VC_REDIST_X86".to_string(),
26        url: "https://download.visualstudio.microsoft.com/download/pr/888b4c07-c602-499a-9efb-411188496ce7/F3A86393234099BEDD558FD35AB538A6E4D9D4F99AD5ADFA13F603D4FF8A42DC/VC_redist.x86.exe".to_string(),
27        sha256: "f3a86393234099bedd558fd35ab538a6e4d9d4f99ad5adfa13f603d4ff8a42dc".to_string(),
28    }
29});
30
31pub static VC_REDIST_X64: Lazy<RemoteContent> = Lazy::new(|| {
32    RemoteContent {
33        name: "VC_REDIST_X64".to_string(),
34        url: "https://download.visualstudio.microsoft.com/download/pr/36e45907-8554-4390-ba70-9f6306924167/97CC5066EB3C7246CF89B735AE0F5A5304A7EE33DC087D65D9DFF3A1A73FE803/VC_redist.x64.exe".to_string(),
35        sha256: "97cc5066eb3c7246cf89b735ae0f5a5304a7ee33dc087d65d9dff3a1a73fe803".to_string(),
36    }
37});
38
39pub static VC_REDIST_ARM64: Lazy<RemoteContent> = Lazy::new(|| {
40    RemoteContent {
41        name: "VC_REDIST_ARM64".to_string(),
42        url: "https://download.visualstudio.microsoft.com/download/pr/888b4c07-c602-499a-9efb-411188496ce7/B76EF09CD8B114148EADDDFC6846EF178E6B7797F590191E22CEE29A20B51692/VC_redist.arm64.exe".to_string(),
43        sha256: "b76ef09cd8b114148eadddfc6846ef178e6b7797f590191e22cee29a20b51692".to_string(),
44    }
45});
46
47/// Available VC++ Redistributable platforms we can add to the bundle.
48#[derive(Debug, PartialEq, Eq)]
49pub enum VcRedistributablePlatform {
50    X86,
51    X64,
52    Arm64,
53}
54
55impl Display for VcRedistributablePlatform {
56    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
57        f.write_str(match self {
58            Self::X86 => "x86",
59            Self::X64 => "x64",
60            Self::Arm64 => "arm64",
61        })
62    }
63}
64
65impl TryFrom<&str> for VcRedistributablePlatform {
66    type Error = anyhow::Error;
67
68    fn try_from(value: &str) -> anyhow::Result<Self, Self::Error> {
69        match value {
70            "x86" => Ok(Self::X86),
71            "x64" => Ok(Self::X64),
72            "arm64" => Ok(Self::Arm64),
73            _ => Err(anyhow!(
74                "{} is not a valid platform; use 'x86', 'x64', or 'arm64'",
75                value
76            )),
77        }
78    }
79}
80
81/// Find the paths to the Visual C++ Redistributable DLLs.
82///
83/// `redist_version` is the version number of the redistributable. Version `14`
84/// is the version for VS2015, 2017, and 2019, which all share the same version.
85///
86/// The returned paths should have names like `vcruntime140.dll`. Some installs
87/// have multiple DLLs.
88#[cfg(windows)]
89pub fn find_visual_cpp_redistributable(
90    redist_version: &str,
91    platform: VcRedistributablePlatform,
92) -> Result<Vec<PathBuf>> {
93    let vswhere_exe = find_vswhere()?;
94
95    let cmd = duct::cmd(
96        vswhere_exe,
97        vec![
98            "-products".to_string(),
99            "*".to_string(),
100            "-requires".to_string(),
101            format!("Microsoft.VisualCPP.Redist.{}.Latest", redist_version),
102            "-latest".to_string(),
103            "-property".to_string(),
104            "installationPath".to_string(),
105            "-utf8".to_string(),
106        ],
107    )
108    .stdout_capture()
109    .stderr_capture()
110    .run()?;
111
112    let install_path = PathBuf::from(
113        String::from_utf8(cmd.stdout)?
114            .strip_suffix("\r\n")
115            .ok_or_else(|| anyhow!("unable to strip string"))?,
116    );
117
118    // This gets us the path to the Visual Studio installation root. The vcruntimeXXX.dll
119    // files are under a path like: VC\Redist\MSVC\<version>\<arch>\Microsoft.VCXXX.CRT\vcruntimeXXX.dll.
120
121    let paths = glob::glob(
122        &install_path
123            .join(format!(
124                "VC/Redist/MSVC/{}.*/{}/Microsoft.VC*.CRT/vcruntime*.dll",
125                redist_version, platform
126            ))
127            .display()
128            .to_string(),
129    )?
130    .collect::<Vec<_>>()
131    .into_iter()
132    .map(|r| r.map_err(|e| anyhow!("glob error: {}", e)))
133    .collect::<Result<Vec<PathBuf>>>()?;
134
135    let mut paths_by_version: BTreeMap<semver::Version, Vec<PathBuf>> = BTreeMap::new();
136
137    for path in paths {
138        let stripped = path.strip_prefix(install_path.join("VC").join("Redist").join("MSVC"))?;
139        // First path component now is the version number.
140
141        let mut components = stripped.components();
142        let version_path = components.next().ok_or_else(|| {
143            anyhow!("unable to determine version component (this should not happen)")
144        })?;
145
146        paths_by_version
147            .entry(semver::Version::parse(
148                version_path.as_os_str().to_string_lossy().as_ref(),
149            )?)
150            .or_insert_with(Vec::new)
151            .push(path);
152    }
153
154    Ok(paths_by_version
155        .into_iter()
156        .last()
157        .ok_or_else(|| anyhow!("unable to find install VC++ Redistributable"))?
158        .1)
159}
160
161#[cfg(unix)]
162pub fn find_visual_cpp_redistributable(
163    _version: &str,
164    _platform: VcRedistributablePlatform,
165) -> Result<Vec<PathBuf>> {
166    // TODO we could potentially reference these files at a URL and download them or something.
167    Err(anyhow!(
168        "Finding the Visual C++ Redistributable is not supported outside of Windows"
169    ))
170}
171
172#[cfg(test)]
173mod tests {
174    use {
175        super::*,
176        tugger_common::{http::download_to_path, testutil::*},
177    };
178
179    #[test]
180    fn test_vcredist_download() -> Result<()> {
181        download_to_path(
182            &VC_REDIST_X86,
183            DEFAULT_DOWNLOAD_DIR.join("vc_redist.x86.exe"),
184        )?;
185        download_to_path(
186            &VC_REDIST_X64,
187            DEFAULT_DOWNLOAD_DIR.join("vc_redist.x64.exe"),
188        )?;
189        download_to_path(
190            &VC_REDIST_ARM64,
191            DEFAULT_DOWNLOAD_DIR.join("vc_redist.arm64.exe"),
192        )?;
193
194        Ok(())
195    }
196
197    #[test]
198    fn test_find_visual_cpp_redistributable_14() {
199        let platforms = vec![
200            VcRedistributablePlatform::X86,
201            VcRedistributablePlatform::X64,
202            VcRedistributablePlatform::Arm64,
203        ];
204
205        for platform in platforms {
206            let res = find_visual_cpp_redistributable("14", platform);
207
208            if cfg!(windows) {
209                if res.is_ok() {
210                    println!("found vcruntime files: {:?}", res.unwrap());
211                }
212            } else {
213                assert!(res.is_err());
214            }
215        }
216    }
217}