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