Skip to content

Commit 92ffa53

Browse files
authored
Support finding Visual Studio instances using vswhere.exe (#597)
* Use SetupInstance COM object in single place only * Implement searching for VS instances using vswhere * Respect target arch when using vswhere * Only use the latest VS version found by vswhere * Rename vswhere.rs to vs_instances.rs * Add a check if vswhere.exe exists * Use static string slice as an error * Rename tests, add a test for insufficient output * Use explicit type identifier for compatibility * WORKAROUND: update Rustup during Test run
1 parent 608ad0c commit 92ffa53

File tree

4 files changed

+288
-36
lines changed

4 files changed

+288
-36
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ jobs:
6161
target: x86_64-pc-windows-msvc
6262
steps:
6363
- uses: actions/checkout@master
64+
- name: Update Rustup (temporary workaround)
65+
run: rustup self update
66+
shell: bash
67+
if: startsWith(matrix.os, 'windows')
6468
- name: Install Rust (rustup)
6569
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
6670
shell: bash

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ mod winapi;
7878
mod com;
7979
#[cfg(windows)]
8080
mod setup_config;
81+
#[cfg(windows)]
82+
mod vs_instances;
8183

8284
pub mod windows_registry;
8385

src/vs_instances.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use std::borrow::Cow;
2+
use std::collections::HashMap;
3+
use std::convert::TryFrom;
4+
use std::io::BufRead;
5+
use std::path::PathBuf;
6+
7+
use crate::setup_config::{EnumSetupInstances, SetupInstance};
8+
9+
pub enum VsInstance {
10+
Com(SetupInstance),
11+
Vswhere(VswhereInstance),
12+
}
13+
14+
impl VsInstance {
15+
pub fn installation_name(&self) -> Option<Cow<str>> {
16+
match self {
17+
VsInstance::Com(s) => s
18+
.installation_name()
19+
.ok()
20+
.and_then(|s| s.into_string().ok())
21+
.map(Cow::from),
22+
VsInstance::Vswhere(v) => v.map.get("installationName").map(Cow::from),
23+
}
24+
}
25+
26+
pub fn installation_path(&self) -> Option<PathBuf> {
27+
match self {
28+
VsInstance::Com(s) => s.installation_path().ok().map(PathBuf::from),
29+
VsInstance::Vswhere(v) => v.map.get("installationPath").map(PathBuf::from),
30+
}
31+
}
32+
33+
pub fn installation_version(&self) -> Option<Cow<str>> {
34+
match self {
35+
VsInstance::Com(s) => s
36+
.installation_version()
37+
.ok()
38+
.and_then(|s| s.into_string().ok())
39+
.map(Cow::from),
40+
VsInstance::Vswhere(v) => v.map.get("installationVersion").map(Cow::from),
41+
}
42+
}
43+
}
44+
45+
pub enum VsInstances {
46+
ComBased(EnumSetupInstances),
47+
VswhereBased(VswhereInstance),
48+
}
49+
50+
impl IntoIterator for VsInstances {
51+
type Item = VsInstance;
52+
#[allow(bare_trait_objects)]
53+
type IntoIter = Box<Iterator<Item = Self::Item>>;
54+
55+
fn into_iter(self) -> Self::IntoIter {
56+
match self {
57+
VsInstances::ComBased(e) => {
58+
Box::new(e.into_iter().filter_map(Result::ok).map(VsInstance::Com))
59+
}
60+
VsInstances::VswhereBased(v) => Box::new(std::iter::once(VsInstance::Vswhere(v))),
61+
}
62+
}
63+
}
64+
65+
#[derive(Debug)]
66+
pub struct VswhereInstance {
67+
map: HashMap<String, String>,
68+
}
69+
70+
impl TryFrom<&Vec<u8>> for VswhereInstance {
71+
type Error = &'static str;
72+
73+
fn try_from(output: &Vec<u8>) -> Result<Self, Self::Error> {
74+
let map: HashMap<_, _> = output
75+
.lines()
76+
.filter_map(Result::ok)
77+
.filter_map(|s| {
78+
let mut splitn = s.splitn(2, ": ");
79+
Some((splitn.next()?.to_owned(), splitn.next()?.to_owned()))
80+
})
81+
.collect();
82+
83+
if !map.contains_key("installationName")
84+
|| !map.contains_key("installationPath")
85+
|| !map.contains_key("installationVersion")
86+
{
87+
return Err("required properties not found");
88+
}
89+
90+
Ok(Self { map })
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests_ {
96+
use std::borrow::Cow;
97+
use std::convert::TryFrom;
98+
use std::path::PathBuf;
99+
100+
#[test]
101+
fn it_parses_vswhere_output_correctly() {
102+
let output = br"instanceId: 58104422
103+
installDate: 21/02/2021 21:50:33
104+
installationName: VisualStudio/16.9.2+31112.23
105+
installationPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools
106+
installationVersion: 16.9.31112.23
107+
productId: Microsoft.VisualStudio.Product.BuildTools
108+
productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat
109+
state: 4294967295
110+
isComplete: 1
111+
isLaunchable: 1
112+
isPrerelease: 0
113+
isRebootRequired: 0
114+
displayName: Visual Studio Build Tools 2019
115+
description: The Visual Studio Build Tools allows you to build native and managed MSBuild-based applications without requiring the Visual Studio IDE. There are options to install the Visual C++ compilers and libraries, MFC, ATL, and C++/CLI support.
116+
channelId: VisualStudio.16.Release
117+
channelUri: https://aka.ms/vs/16/release/channel
118+
enginePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service
119+
releaseNotes: https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-v16.9#16.9.2
120+
thirdPartyNotices: https://go.microsoft.com/fwlink/?LinkId=660909
121+
updateDate: 2021-03-17T21:16:46.5963702Z
122+
catalog_buildBranch: d16.9
123+
catalog_buildVersion: 16.9.31112.23
124+
catalog_id: VisualStudio/16.9.2+31112.23
125+
catalog_localBuild: build-lab
126+
catalog_manifestName: VisualStudio
127+
catalog_manifestType: installer
128+
catalog_productDisplayVersion: 16.9.2
129+
catalog_productLine: Dev16
130+
catalog_productLineVersion: 2019
131+
catalog_productMilestone: RTW
132+
catalog_productMilestoneIsPreRelease: False
133+
catalog_productName: Visual Studio
134+
catalog_productPatchVersion: 2
135+
catalog_productPreReleaseMilestoneSuffix: 1.0
136+
catalog_productSemanticVersion: 16.9.2+31112.23
137+
catalog_requiredEngineVersion: 2.9.3365.38425
138+
properties_campaignId: 156063665.1613940062
139+
properties_channelManifestId: VisualStudio.16.Release/16.9.2+31112.23
140+
properties_nickname:
141+
properties_setupEngineFilePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installershell.exe
142+
"
143+
.to_vec();
144+
145+
let vswhere_instance = super::VswhereInstance::try_from(&output);
146+
assert!(vswhere_instance.is_ok());
147+
148+
let vs_instance = super::VsInstance::Vswhere(vswhere_instance.unwrap());
149+
assert_eq!(
150+
vs_instance.installation_name(),
151+
Some(Cow::from("VisualStudio/16.9.2+31112.23"))
152+
);
153+
assert_eq!(
154+
vs_instance.installation_path(),
155+
Some(PathBuf::from(
156+
r"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools"
157+
))
158+
);
159+
assert_eq!(
160+
vs_instance.installation_version(),
161+
Some(Cow::from("16.9.31112.23"))
162+
);
163+
}
164+
165+
#[test]
166+
fn it_returns_an_error_for_empty_output() {
167+
let output = b"".to_vec();
168+
169+
let vswhere_instance = super::VswhereInstance::try_from(&output);
170+
171+
assert!(vswhere_instance.is_err());
172+
}
173+
174+
#[test]
175+
fn it_returns_an_error_for_output_consisting_of_empty_lines() {
176+
let output = br"
177+
178+
"
179+
.to_vec();
180+
181+
let vswhere_instance = super::VswhereInstance::try_from(&output);
182+
183+
assert!(vswhere_instance.is_err());
184+
}
185+
186+
#[test]
187+
fn it_returns_an_error_for_output_without_required_properties() {
188+
let output = br"instanceId: 58104422
189+
installDate: 21/02/2021 21:50:33
190+
productId: Microsoft.VisualStudio.Product.BuildTools
191+
productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat
192+
"
193+
.to_vec();
194+
195+
let vswhere_instance = super::VswhereInstance::try_from(&output);
196+
197+
assert!(vswhere_instance.is_err());
198+
}
199+
}

0 commit comments

Comments
 (0)