cmVSSetupHelper.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmVSSetupHelper.h"
  4. #include "cmSystemTools.h"
  5. #include "cmsys/Encoding.hxx"
  6. #ifndef VSSetupConstants
  7. #define VSSetupConstants
  8. /* clang-format off */
  9. const IID IID_ISetupConfiguration = {
  10. 0x42843719, 0xDB4C, 0x46C2,
  11. { 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B }
  12. };
  13. const IID IID_ISetupConfiguration2 = {
  14. 0x26AAB78C, 0x4A60, 0x49D6,
  15. { 0xAF, 0x3B, 0x3C, 0x35, 0xBC, 0x93, 0x36, 0x5D }
  16. };
  17. const IID IID_ISetupPackageReference = {
  18. 0xda8d8a16, 0xb2b6, 0x4487,
  19. { 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5 }
  20. };
  21. const IID IID_ISetupHelper = {
  22. 0x42b21b78, 0x6192, 0x463e,
  23. { 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c }
  24. };
  25. const IID IID_IEnumSetupInstances = {
  26. 0x6380BCFF, 0x41D3, 0x4B2E,
  27. { 0x8B, 0x2E, 0xBF, 0x8A, 0x68, 0x10, 0xC8, 0x48 }
  28. };
  29. const IID IID_ISetupInstance2 = {
  30. 0x89143C9A, 0x05AF, 0x49B0,
  31. { 0xB7, 0x17, 0x72, 0xE2, 0x18, 0xA2, 0x18, 0x5C }
  32. };
  33. const IID IID_ISetupInstance = {
  34. 0xB41463C3, 0x8866, 0x43B5,
  35. { 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E }
  36. };
  37. const CLSID CLSID_SetupConfiguration = {
  38. 0x177F0C4A, 0x1CD3, 0x4DE7,
  39. { 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D }
  40. };
  41. /* clang-format on */
  42. #endif
  43. const WCHAR* VCToolsetComponent =
  44. L"Microsoft.VisualStudio.Component.VC.Tools.x86.x64";
  45. const WCHAR* Win10SDKComponent =
  46. L"Microsoft.VisualStudio.Component.Windows10SDK";
  47. const WCHAR* Win81SDKComponent =
  48. L"Microsoft.VisualStudio.Component.Windows81SDK";
  49. const WCHAR* ComponentType = L"Component";
  50. std::string VSInstanceInfo::GetInstallLocation() const
  51. {
  52. std::string loc = cmsys::Encoding::ToNarrow(this->VSInstallLocation);
  53. cmSystemTools::ConvertToUnixSlashes(loc);
  54. return loc;
  55. }
  56. cmVSSetupAPIHelper::cmVSSetupAPIHelper()
  57. : setupConfig(NULL)
  58. , setupConfig2(NULL)
  59. , setupHelper(NULL)
  60. , initializationFailure(false)
  61. {
  62. comInitialized = CoInitializeEx(NULL, 0);
  63. if (SUCCEEDED(comInitialized)) {
  64. Initialize();
  65. } else {
  66. initializationFailure = true;
  67. }
  68. }
  69. cmVSSetupAPIHelper::~cmVSSetupAPIHelper()
  70. {
  71. setupHelper = NULL;
  72. setupConfig2 = NULL;
  73. setupConfig = NULL;
  74. if (SUCCEEDED(comInitialized))
  75. CoUninitialize();
  76. }
  77. bool cmVSSetupAPIHelper::IsVS2017Installed()
  78. {
  79. return this->EnumerateAndChooseVSInstance();
  80. }
  81. bool cmVSSetupAPIHelper::IsWin10SDKInstalled()
  82. {
  83. return (this->EnumerateAndChooseVSInstance() &&
  84. chosenInstanceInfo.IsWin10SDKInstalled);
  85. }
  86. bool cmVSSetupAPIHelper::IsWin81SDKInstalled()
  87. {
  88. return (this->EnumerateAndChooseVSInstance() &&
  89. chosenInstanceInfo.IsWin81SDKInstalled);
  90. }
  91. bool cmVSSetupAPIHelper::CheckInstalledComponent(
  92. SmartCOMPtr<ISetupPackageReference> package, bool& bVCToolset,
  93. bool& bWin10SDK, bool& bWin81SDK)
  94. {
  95. bool ret = false;
  96. bVCToolset = bWin10SDK = bWin81SDK = false;
  97. SmartBSTR bstrId;
  98. if (FAILED(package->GetId(&bstrId))) {
  99. return ret;
  100. }
  101. SmartBSTR bstrType;
  102. if (FAILED(package->GetType(&bstrType))) {
  103. return ret;
  104. }
  105. std::wstring id = std::wstring(bstrId);
  106. std::wstring type = std::wstring(bstrType);
  107. if (id.compare(VCToolsetComponent) == 0 &&
  108. type.compare(ComponentType) == 0) {
  109. bVCToolset = true;
  110. ret = true;
  111. }
  112. // Checks for any version of Win10 SDK. The version is appended at the end of
  113. // the
  114. // component name ex: Microsoft.VisualStudio.Component.Windows10SDK.10240
  115. if (id.find(Win10SDKComponent) != std::wstring::npos &&
  116. type.compare(ComponentType) == 0) {
  117. bWin10SDK = true;
  118. ret = true;
  119. }
  120. if (id.compare(Win81SDKComponent) == 0 && type.compare(ComponentType) == 0) {
  121. bWin81SDK = true;
  122. ret = true;
  123. }
  124. return ret;
  125. }
  126. // Gather additional info such as if VCToolset, WinSDKs are installed, location
  127. // of VS and version information.
  128. bool cmVSSetupAPIHelper::GetVSInstanceInfo(
  129. SmartCOMPtr<ISetupInstance2> pInstance, VSInstanceInfo& vsInstanceInfo)
  130. {
  131. bool isVCToolSetInstalled = false;
  132. if (pInstance == NULL)
  133. return false;
  134. SmartBSTR bstrId;
  135. if (SUCCEEDED(pInstance->GetInstanceId(&bstrId))) {
  136. vsInstanceInfo.InstanceId = std::wstring(bstrId);
  137. } else {
  138. return false;
  139. }
  140. InstanceState state;
  141. if (FAILED(pInstance->GetState(&state))) {
  142. return false;
  143. }
  144. ULONGLONG ullVersion = 0;
  145. SmartBSTR bstrVersion;
  146. if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) {
  147. return false;
  148. } else {
  149. vsInstanceInfo.Version = std::wstring(bstrVersion);
  150. if (FAILED(setupHelper->ParseVersion(bstrVersion, &ullVersion))) {
  151. vsInstanceInfo.ullVersion = 0;
  152. } else {
  153. vsInstanceInfo.ullVersion = ullVersion;
  154. }
  155. }
  156. // Reboot may have been required before the installation path was created.
  157. SmartBSTR bstrInstallationPath;
  158. if ((eLocal & state) == eLocal) {
  159. if (FAILED(pInstance->GetInstallationPath(&bstrInstallationPath))) {
  160. return false;
  161. } else {
  162. vsInstanceInfo.VSInstallLocation = std::wstring(bstrInstallationPath);
  163. }
  164. }
  165. // Reboot may have been required before the product package was registered
  166. // (last).
  167. if ((eRegistered & state) == eRegistered) {
  168. SmartCOMPtr<ISetupPackageReference> product;
  169. if (FAILED(pInstance->GetProduct(&product)) || !product) {
  170. return false;
  171. }
  172. LPSAFEARRAY lpsaPackages;
  173. if (FAILED(pInstance->GetPackages(&lpsaPackages)) ||
  174. lpsaPackages == NULL) {
  175. return false;
  176. }
  177. int lower = lpsaPackages->rgsabound[0].lLbound;
  178. int upper = lpsaPackages->rgsabound[0].cElements + lower;
  179. IUnknown** ppData = (IUnknown**)lpsaPackages->pvData;
  180. for (int i = lower; i < upper; i++) {
  181. SmartCOMPtr<ISetupPackageReference> package = NULL;
  182. if (FAILED(ppData[i]->QueryInterface(IID_ISetupPackageReference,
  183. (void**)&package)) ||
  184. package == NULL)
  185. continue;
  186. bool vcToolsetInstalled = false, win10SDKInstalled = false,
  187. win81SDkInstalled = false;
  188. bool ret = CheckInstalledComponent(package, vcToolsetInstalled,
  189. win10SDKInstalled, win81SDkInstalled);
  190. if (ret) {
  191. isVCToolSetInstalled |= vcToolsetInstalled;
  192. vsInstanceInfo.IsWin10SDKInstalled |= win10SDKInstalled;
  193. vsInstanceInfo.IsWin81SDKInstalled |= win81SDkInstalled;
  194. }
  195. }
  196. SafeArrayDestroy(lpsaPackages);
  197. }
  198. return isVCToolSetInstalled;
  199. }
  200. bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation)
  201. {
  202. vsInstallLocation.clear();
  203. bool isInstalled = this->EnumerateAndChooseVSInstance();
  204. if (isInstalled) {
  205. vsInstallLocation = chosenInstanceInfo.GetInstallLocation();
  206. }
  207. return isInstalled;
  208. }
  209. bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
  210. {
  211. bool isVSInstanceExists = false;
  212. if (chosenInstanceInfo.VSInstallLocation.compare(L"") != 0) {
  213. return true;
  214. }
  215. if (initializationFailure || setupConfig == NULL || setupConfig2 == NULL ||
  216. setupHelper == NULL)
  217. return false;
  218. std::string envVSCommonToolsDir;
  219. // FIXME: When we support VS versions beyond 2017, the version
  220. // to choose will be passed in by the caller. We need to map that
  221. // to a per-version name of this environment variable.
  222. if (cmSystemTools::GetEnv("VS150COMNTOOLS", envVSCommonToolsDir)) {
  223. cmSystemTools::ConvertToUnixSlashes(envVSCommonToolsDir);
  224. }
  225. // FIXME: If the environment variable value changes between runs
  226. // of CMake within a given build tree the results are not defined.
  227. // Instead we should save a CMAKE_GENERATOR_INSTANCE value in the cache
  228. // (similar to CMAKE_GENERATOR_TOOLSET) to hold it persistently.
  229. // Unfortunately doing so will require refactoring elsewhere in
  230. // order to make sure the value is available in time to create
  231. // the generator.
  232. std::vector<VSInstanceInfo> vecVSInstances;
  233. SmartCOMPtr<IEnumSetupInstances> enumInstances = NULL;
  234. if (FAILED(
  235. setupConfig2->EnumInstances((IEnumSetupInstances**)&enumInstances)) ||
  236. !enumInstances) {
  237. return false;
  238. }
  239. SmartCOMPtr<ISetupInstance> instance;
  240. while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) {
  241. SmartCOMPtr<ISetupInstance2> instance2 = NULL;
  242. if (FAILED(
  243. instance->QueryInterface(IID_ISetupInstance2, (void**)&instance2)) ||
  244. !instance2) {
  245. instance = NULL;
  246. continue;
  247. }
  248. VSInstanceInfo instanceInfo;
  249. bool isInstalled = GetVSInstanceInfo(instance2, instanceInfo);
  250. instance = instance2 = NULL;
  251. if (isInstalled) {
  252. if (!envVSCommonToolsDir.empty()) {
  253. std::string currentVSLocation = instanceInfo.GetInstallLocation();
  254. currentVSLocation += "/Common7/Tools";
  255. if (cmSystemTools::ComparePath(currentVSLocation,
  256. envVSCommonToolsDir)) {
  257. chosenInstanceInfo = instanceInfo;
  258. return true;
  259. }
  260. }
  261. vecVSInstances.push_back(instanceInfo);
  262. }
  263. }
  264. if (vecVSInstances.size() > 0) {
  265. isVSInstanceExists = true;
  266. int index = ChooseVSInstance(vecVSInstances);
  267. chosenInstanceInfo = vecVSInstances[index];
  268. }
  269. return isVSInstanceExists;
  270. }
  271. int cmVSSetupAPIHelper::ChooseVSInstance(
  272. const std::vector<VSInstanceInfo>& vecVSInstances)
  273. {
  274. if (vecVSInstances.size() == 0)
  275. return -1;
  276. if (vecVSInstances.size() == 1)
  277. return 0;
  278. unsigned int chosenIndex = 0;
  279. for (unsigned int i = 1; i < vecVSInstances.size(); i++) {
  280. // If the current has Win10 SDK but not the chosen one, then choose the
  281. // current VS instance
  282. if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
  283. vecVSInstances[i].IsWin10SDKInstalled) {
  284. chosenIndex = i;
  285. continue;
  286. }
  287. // If the chosen one has Win10 SDK but the current one is not, then look at
  288. // the next VS instance even the current
  289. // instance version may be higher
  290. if (vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
  291. !vecVSInstances[i].IsWin10SDKInstalled) {
  292. continue;
  293. }
  294. // If both chosen one and current one doesn't have Win10 SDK but the
  295. // current one has Win8.1 SDK installed,
  296. // then choose the current one
  297. if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
  298. !vecVSInstances[i].IsWin10SDKInstalled &&
  299. !vecVSInstances[chosenIndex].IsWin81SDKInstalled &&
  300. vecVSInstances[i].IsWin81SDKInstalled) {
  301. chosenIndex = i;
  302. continue;
  303. }
  304. // If there is no difference in WinSDKs then look for the highest version
  305. // of installed VS
  306. if ((vecVSInstances[chosenIndex].IsWin10SDKInstalled ==
  307. vecVSInstances[i].IsWin10SDKInstalled) &&
  308. (vecVSInstances[chosenIndex].IsWin81SDKInstalled ==
  309. vecVSInstances[i].IsWin81SDKInstalled) &&
  310. vecVSInstances[chosenIndex].Version < vecVSInstances[i].Version) {
  311. chosenIndex = i;
  312. continue;
  313. }
  314. }
  315. return chosenIndex;
  316. }
  317. bool cmVSSetupAPIHelper::Initialize()
  318. {
  319. if (initializationFailure)
  320. return false;
  321. if (FAILED(comInitialized)) {
  322. initializationFailure = true;
  323. return false;
  324. }
  325. if (FAILED(setupConfig.CoCreateInstance(CLSID_SetupConfiguration, NULL,
  326. IID_ISetupConfiguration,
  327. CLSCTX_INPROC_SERVER)) ||
  328. setupConfig == NULL) {
  329. initializationFailure = true;
  330. return false;
  331. }
  332. if (FAILED(setupConfig.QueryInterface(IID_ISetupConfiguration2,
  333. (void**)&setupConfig2)) ||
  334. setupConfig2 == NULL) {
  335. initializationFailure = true;
  336. return false;
  337. }
  338. if (FAILED(
  339. setupConfig.QueryInterface(IID_ISetupHelper, (void**)&setupHelper)) ||
  340. setupHelper == NULL) {
  341. initializationFailure = true;
  342. return false;
  343. }
  344. initializationFailure = false;
  345. return true;
  346. }