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 {
installation_name(&self) -> Option<Cow<str>>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 
installation_path(&self) -> Option<PathBuf>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 
installation_version(&self) -> Option<Cow<str>>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 
into_iter(self) -> Self::IntoIter55     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 
try_from(output: &Vec<u8>) -> Result<Self, Self::Error>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]
it_parses_vswhere_output_correctly()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]
it_returns_an_error_for_empty_output()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]
it_returns_an_error_for_output_consisting_of_empty_lines()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]
it_returns_an_error_for_output_without_required_properties()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 }
200