
Previously the `CMAKE_GENERATOR_INSTANCE` value was used only to filter the instances reported by the Visual Studio Installer tool. If the specified install location is not known to the VS Installer, but the user provided a `version=` field, check for the installation directly on disk. Fixes: #21639, #22197
536 lines
16 KiB
C++
536 lines
16 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmVSSetupHelper.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "cmsys/Encoding.hxx"
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
|
|
#ifndef VSSetupConstants
|
|
# define VSSetupConstants
|
|
/* clang-format off */
|
|
const IID IID_ISetupConfiguration = {
|
|
0x42843719, 0xDB4C, 0x46C2,
|
|
{ 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B }
|
|
};
|
|
const IID IID_ISetupConfiguration2 = {
|
|
0x26AAB78C, 0x4A60, 0x49D6,
|
|
{ 0xAF, 0x3B, 0x3C, 0x35, 0xBC, 0x93, 0x36, 0x5D }
|
|
};
|
|
const IID IID_ISetupPackageReference = {
|
|
0xda8d8a16, 0xb2b6, 0x4487,
|
|
{ 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5 }
|
|
};
|
|
const IID IID_ISetupHelper = {
|
|
0x42b21b78, 0x6192, 0x463e,
|
|
{ 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c }
|
|
};
|
|
const IID IID_IEnumSetupInstances = {
|
|
0x6380BCFF, 0x41D3, 0x4B2E,
|
|
{ 0x8B, 0x2E, 0xBF, 0x8A, 0x68, 0x10, 0xC8, 0x48 }
|
|
};
|
|
const IID IID_ISetupInstance2 = {
|
|
0x89143C9A, 0x05AF, 0x49B0,
|
|
{ 0xB7, 0x17, 0x72, 0xE2, 0x18, 0xA2, 0x18, 0x5C }
|
|
};
|
|
const IID IID_ISetupInstance = {
|
|
0xB41463C3, 0x8866, 0x43B5,
|
|
{ 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E }
|
|
};
|
|
const CLSID CLSID_SetupConfiguration = {
|
|
0x177F0C4A, 0x1CD3, 0x4DE7,
|
|
{ 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D }
|
|
};
|
|
/* clang-format on */
|
|
#endif
|
|
|
|
namespace {
|
|
const WCHAR* Win10SDKComponent =
|
|
L"Microsoft.VisualStudio.Component.Windows10SDK";
|
|
const WCHAR* Win81SDKComponent =
|
|
L"Microsoft.VisualStudio.Component.Windows81SDK";
|
|
const WCHAR* ComponentType = L"Component";
|
|
|
|
bool LoadVSInstanceVCToolsetVersion(VSInstanceInfo& vsInstanceInfo)
|
|
{
|
|
std::string const vcRoot = vsInstanceInfo.GetInstallLocation();
|
|
std::string vcToolsVersionFile =
|
|
vcRoot + "/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt";
|
|
std::string vcToolsVersion;
|
|
cmsys::ifstream fin(vcToolsVersionFile.c_str());
|
|
if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) {
|
|
return false;
|
|
}
|
|
vcToolsVersion = cmTrimWhitespace(vcToolsVersion);
|
|
std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion;
|
|
if (!cmSystemTools::FileIsDirectory(vcToolsDir)) {
|
|
return false;
|
|
}
|
|
vsInstanceInfo.VCToolsetVersion = vcToolsVersion;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::string VSInstanceInfo::GetInstallLocation() const
|
|
{
|
|
return this->VSInstallLocation;
|
|
}
|
|
|
|
cmVSSetupAPIHelper::cmVSSetupAPIHelper(unsigned int version)
|
|
: Version(version)
|
|
, setupConfig(NULL)
|
|
, setupConfig2(NULL)
|
|
, setupHelper(NULL)
|
|
, initializationFailure(false)
|
|
{
|
|
comInitialized = CoInitializeEx(NULL, 0);
|
|
if (SUCCEEDED(comInitialized)) {
|
|
Initialize();
|
|
} else {
|
|
initializationFailure = true;
|
|
}
|
|
}
|
|
|
|
cmVSSetupAPIHelper::~cmVSSetupAPIHelper()
|
|
{
|
|
setupHelper = NULL;
|
|
setupConfig2 = NULL;
|
|
setupConfig = NULL;
|
|
if (SUCCEEDED(comInitialized))
|
|
CoUninitialize();
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::SetVSInstance(std::string const& vsInstallLocation,
|
|
std::string const& vsInstallVersion)
|
|
{
|
|
this->SpecifiedVSInstallLocation = vsInstallLocation;
|
|
cmSystemTools::ConvertToUnixSlashes(this->SpecifiedVSInstallLocation);
|
|
this->SpecifiedVSInstallVersion = vsInstallVersion;
|
|
chosenInstanceInfo = VSInstanceInfo();
|
|
return this->EnumerateAndChooseVSInstance();
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::IsVSInstalled()
|
|
{
|
|
return this->EnumerateAndChooseVSInstance();
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::IsWin10SDKInstalled()
|
|
{
|
|
return (this->EnumerateAndChooseVSInstance() &&
|
|
chosenInstanceInfo.IsWin10SDKInstalled);
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::IsWin81SDKInstalled()
|
|
{
|
|
return (this->EnumerateAndChooseVSInstance() &&
|
|
chosenInstanceInfo.IsWin81SDKInstalled);
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::CheckInstalledComponent(
|
|
SmartCOMPtr<ISetupPackageReference> package, bool& bWin10SDK,
|
|
bool& bWin81SDK)
|
|
{
|
|
bool ret = false;
|
|
bWin10SDK = bWin81SDK = false;
|
|
SmartBSTR bstrId;
|
|
if (FAILED(package->GetId(&bstrId))) {
|
|
return ret;
|
|
}
|
|
|
|
SmartBSTR bstrType;
|
|
if (FAILED(package->GetType(&bstrType))) {
|
|
return ret;
|
|
}
|
|
|
|
std::wstring id = std::wstring(bstrId);
|
|
std::wstring type = std::wstring(bstrType);
|
|
|
|
// Checks for any version of Win10 SDK. The version is appended at the end of
|
|
// the
|
|
// component name ex: Microsoft.VisualStudio.Component.Windows10SDK.10240
|
|
if (id.find(Win10SDKComponent) != std::wstring::npos &&
|
|
type.compare(ComponentType) == 0) {
|
|
bWin10SDK = true;
|
|
ret = true;
|
|
}
|
|
|
|
if (id.compare(Win81SDKComponent) == 0 && type.compare(ComponentType) == 0) {
|
|
bWin81SDK = true;
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Gather additional info such as if VCToolset, WinSDKs are installed, location
|
|
// of VS and version information.
|
|
bool cmVSSetupAPIHelper::GetVSInstanceInfo(
|
|
SmartCOMPtr<ISetupInstance2> pInstance, VSInstanceInfo& vsInstanceInfo)
|
|
{
|
|
if (pInstance == NULL)
|
|
return false;
|
|
|
|
InstanceState state;
|
|
if (FAILED(pInstance->GetState(&state))) {
|
|
return false;
|
|
}
|
|
|
|
SmartBSTR bstrVersion;
|
|
if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) {
|
|
return false;
|
|
} else {
|
|
vsInstanceInfo.Version =
|
|
cmsys::Encoding::ToNarrow(std::wstring(bstrVersion));
|
|
}
|
|
|
|
// Reboot may have been required before the installation path was created.
|
|
SmartBSTR bstrInstallationPath;
|
|
if ((eLocal & state) == eLocal) {
|
|
if (FAILED(pInstance->GetInstallationPath(&bstrInstallationPath))) {
|
|
return false;
|
|
} else {
|
|
vsInstanceInfo.VSInstallLocation =
|
|
cmsys::Encoding::ToNarrow(std::wstring(bstrInstallationPath));
|
|
cmSystemTools::ConvertToUnixSlashes(vsInstanceInfo.VSInstallLocation);
|
|
}
|
|
}
|
|
|
|
// Check if a compiler is installed with this instance.
|
|
if (!LoadVSInstanceVCToolsetVersion(vsInstanceInfo)) {
|
|
return false;
|
|
}
|
|
|
|
// Reboot may have been required before the product package was registered
|
|
// (last).
|
|
if ((eRegistered & state) == eRegistered) {
|
|
SmartCOMPtr<ISetupPackageReference> product;
|
|
if (FAILED(pInstance->GetProduct(&product)) || !product) {
|
|
return false;
|
|
}
|
|
|
|
LPSAFEARRAY lpsaPackages;
|
|
if (FAILED(pInstance->GetPackages(&lpsaPackages)) ||
|
|
lpsaPackages == NULL) {
|
|
return false;
|
|
}
|
|
|
|
int lower = lpsaPackages->rgsabound[0].lLbound;
|
|
int upper = lpsaPackages->rgsabound[0].cElements + lower;
|
|
|
|
IUnknown** ppData = (IUnknown**)lpsaPackages->pvData;
|
|
for (int i = lower; i < upper; i++) {
|
|
SmartCOMPtr<ISetupPackageReference> package = NULL;
|
|
if (FAILED(ppData[i]->QueryInterface(IID_ISetupPackageReference,
|
|
(void**)&package)) ||
|
|
package == NULL)
|
|
continue;
|
|
|
|
bool win10SDKInstalled = false;
|
|
bool win81SDkInstalled = false;
|
|
bool ret =
|
|
CheckInstalledComponent(package, win10SDKInstalled, win81SDkInstalled);
|
|
if (ret) {
|
|
vsInstanceInfo.IsWin10SDKInstalled |= win10SDKInstalled;
|
|
vsInstanceInfo.IsWin81SDKInstalled |= win81SDkInstalled;
|
|
}
|
|
}
|
|
|
|
SafeArrayDestroy(lpsaPackages);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation)
|
|
{
|
|
vsInstallLocation.clear();
|
|
bool isInstalled = this->EnumerateAndChooseVSInstance();
|
|
|
|
if (isInstalled) {
|
|
vsInstallLocation = chosenInstanceInfo.GetInstallLocation();
|
|
}
|
|
|
|
return isInstalled;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::GetVSInstanceVersion(std::string& vsInstanceVersion)
|
|
{
|
|
vsInstanceVersion.clear();
|
|
bool isInstalled = this->EnumerateAndChooseVSInstance();
|
|
|
|
if (isInstalled) {
|
|
vsInstanceVersion = chosenInstanceInfo.Version;
|
|
}
|
|
|
|
return isInstalled;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::GetVCToolsetVersion(std::string& vsToolsetVersion)
|
|
{
|
|
vsToolsetVersion.clear();
|
|
bool isInstalled = this->EnumerateAndChooseVSInstance();
|
|
|
|
if (isInstalled) {
|
|
vsToolsetVersion = chosenInstanceInfo.VCToolsetVersion;
|
|
}
|
|
|
|
return isInstalled && !vsToolsetVersion.empty();
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::IsEWDKEnabled()
|
|
{
|
|
std::string envEnterpriseWDK, envDisableRegistryUse;
|
|
cmSystemTools::GetEnv("EnterpriseWDK", envEnterpriseWDK);
|
|
cmSystemTools::GetEnv("DisableRegistryUse", envDisableRegistryUse);
|
|
if (!cmSystemTools::Strucmp(envEnterpriseWDK.c_str(), "True") &&
|
|
!cmSystemTools::Strucmp(envDisableRegistryUse.c_str(), "True")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
|
|
{
|
|
bool isVSInstanceExists = false;
|
|
if (chosenInstanceInfo.VSInstallLocation.compare("") != 0) {
|
|
return true;
|
|
}
|
|
|
|
if (this->IsEWDKEnabled()) {
|
|
std::string envWindowsSdkDir81, envVSVersion, envVsInstallDir;
|
|
|
|
cmSystemTools::GetEnv("WindowsSdkDir_81", envWindowsSdkDir81);
|
|
cmSystemTools::GetEnv("VisualStudioVersion", envVSVersion);
|
|
cmSystemTools::GetEnv("VSINSTALLDIR", envVsInstallDir);
|
|
if (envVSVersion.empty() || envVsInstallDir.empty())
|
|
return false;
|
|
|
|
chosenInstanceInfo.VSInstallLocation = envVsInstallDir;
|
|
chosenInstanceInfo.Version = envVSVersion;
|
|
if (!LoadVSInstanceVCToolsetVersion(chosenInstanceInfo)) {
|
|
return false;
|
|
}
|
|
chosenInstanceInfo.IsWin10SDKInstalled = true;
|
|
chosenInstanceInfo.IsWin81SDKInstalled = !envWindowsSdkDir81.empty();
|
|
return true;
|
|
}
|
|
|
|
if (initializationFailure || setupConfig == NULL || setupConfig2 == NULL ||
|
|
setupHelper == NULL)
|
|
return false;
|
|
|
|
std::string envVSCommonToolsDir;
|
|
std::string envVSCommonToolsDirEnvName =
|
|
"VS" + std::to_string(this->Version) + "0COMNTOOLS";
|
|
|
|
if (cmSystemTools::GetEnv(envVSCommonToolsDirEnvName.c_str(),
|
|
envVSCommonToolsDir)) {
|
|
cmSystemTools::ConvertToUnixSlashes(envVSCommonToolsDir);
|
|
}
|
|
|
|
std::vector<VSInstanceInfo> vecVSInstances;
|
|
SmartCOMPtr<IEnumSetupInstances> enumInstances = NULL;
|
|
if (FAILED(
|
|
setupConfig2->EnumInstances((IEnumSetupInstances**)&enumInstances)) ||
|
|
!enumInstances) {
|
|
return false;
|
|
}
|
|
|
|
std::string const wantVersion = std::to_string(this->Version) + '.';
|
|
|
|
bool specifiedLocationNotSpecifiedVersion = false;
|
|
|
|
SmartCOMPtr<ISetupInstance> instance;
|
|
while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) {
|
|
SmartCOMPtr<ISetupInstance2> instance2 = NULL;
|
|
if (FAILED(
|
|
instance->QueryInterface(IID_ISetupInstance2, (void**)&instance2)) ||
|
|
!instance2) {
|
|
instance = NULL;
|
|
continue;
|
|
}
|
|
|
|
VSInstanceInfo instanceInfo;
|
|
bool isInstalled = GetVSInstanceInfo(instance2, instanceInfo);
|
|
instance = instance2 = NULL;
|
|
|
|
if (isInstalled) {
|
|
// We are looking for a specific major version.
|
|
if (instanceInfo.Version.size() < wantVersion.size() ||
|
|
instanceInfo.Version.substr(0, wantVersion.size()) != wantVersion) {
|
|
continue;
|
|
}
|
|
|
|
if (!this->SpecifiedVSInstallLocation.empty()) {
|
|
// We are looking for a specific instance.
|
|
std::string currentVSLocation = instanceInfo.GetInstallLocation();
|
|
if (cmSystemTools::ComparePath(currentVSLocation,
|
|
this->SpecifiedVSInstallLocation)) {
|
|
if (this->SpecifiedVSInstallVersion.empty() ||
|
|
instanceInfo.Version == this->SpecifiedVSInstallVersion) {
|
|
chosenInstanceInfo = instanceInfo;
|
|
return true;
|
|
}
|
|
specifiedLocationNotSpecifiedVersion = true;
|
|
}
|
|
} else if (!this->SpecifiedVSInstallVersion.empty()) {
|
|
// We are looking for a specific version.
|
|
if (instanceInfo.Version == this->SpecifiedVSInstallVersion) {
|
|
chosenInstanceInfo = instanceInfo;
|
|
return true;
|
|
}
|
|
} else {
|
|
// We are not looking for a specific instance.
|
|
// If we've been given a hint then use it.
|
|
if (!envVSCommonToolsDir.empty()) {
|
|
std::string currentVSLocation =
|
|
cmStrCat(instanceInfo.GetInstallLocation(), "/Common7/Tools");
|
|
if (cmSystemTools::ComparePath(currentVSLocation,
|
|
envVSCommonToolsDir)) {
|
|
chosenInstanceInfo = instanceInfo;
|
|
return true;
|
|
}
|
|
}
|
|
// Otherwise, add this to the list of candidates.
|
|
vecVSInstances.push_back(instanceInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!this->SpecifiedVSInstallLocation.empty() &&
|
|
!specifiedLocationNotSpecifiedVersion) {
|
|
// The VS Installer does not know about the specified location.
|
|
// Check for one directly on disk.
|
|
return this->LoadSpecifiedVSInstanceFromDisk();
|
|
}
|
|
|
|
if (vecVSInstances.size() > 0) {
|
|
isVSInstanceExists = true;
|
|
int index = ChooseVSInstance(vecVSInstances);
|
|
chosenInstanceInfo = vecVSInstances[index];
|
|
}
|
|
|
|
return isVSInstanceExists;
|
|
}
|
|
|
|
int cmVSSetupAPIHelper::ChooseVSInstance(
|
|
const std::vector<VSInstanceInfo>& vecVSInstances)
|
|
{
|
|
if (vecVSInstances.size() == 0)
|
|
return -1;
|
|
|
|
if (vecVSInstances.size() == 1)
|
|
return 0;
|
|
|
|
unsigned int chosenIndex = 0;
|
|
for (unsigned int i = 1; i < vecVSInstances.size(); i++) {
|
|
// If the current has Win10 SDK but not the chosen one, then choose the
|
|
// current VS instance
|
|
if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
|
|
vecVSInstances[i].IsWin10SDKInstalled) {
|
|
chosenIndex = i;
|
|
continue;
|
|
}
|
|
|
|
// If the chosen one has Win10 SDK but the current one is not, then look at
|
|
// the next VS instance even the current
|
|
// instance version may be higher
|
|
if (vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
|
|
!vecVSInstances[i].IsWin10SDKInstalled) {
|
|
continue;
|
|
}
|
|
|
|
// If both chosen one and current one doesn't have Win10 SDK but the
|
|
// current one has Win8.1 SDK installed,
|
|
// then choose the current one
|
|
if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
|
|
!vecVSInstances[i].IsWin10SDKInstalled &&
|
|
!vecVSInstances[chosenIndex].IsWin81SDKInstalled &&
|
|
vecVSInstances[i].IsWin81SDKInstalled) {
|
|
chosenIndex = i;
|
|
continue;
|
|
}
|
|
|
|
// If there is no difference in WinSDKs then look for the highest version
|
|
// of installed VS
|
|
if ((vecVSInstances[chosenIndex].IsWin10SDKInstalled ==
|
|
vecVSInstances[i].IsWin10SDKInstalled) &&
|
|
(vecVSInstances[chosenIndex].IsWin81SDKInstalled ==
|
|
vecVSInstances[i].IsWin81SDKInstalled) &&
|
|
vecVSInstances[chosenIndex].Version < vecVSInstances[i].Version) {
|
|
chosenIndex = i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return chosenIndex;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::LoadSpecifiedVSInstanceFromDisk()
|
|
{
|
|
if (!cmSystemTools::FileIsDirectory(this->SpecifiedVSInstallLocation)) {
|
|
return false;
|
|
}
|
|
VSInstanceInfo vsInstanceInfo;
|
|
vsInstanceInfo.VSInstallLocation = this->SpecifiedVSInstallLocation;
|
|
// FIXME: Is there a better way to get SDK information?
|
|
vsInstanceInfo.IsWin10SDKInstalled = true;
|
|
vsInstanceInfo.IsWin81SDKInstalled = false;
|
|
|
|
if (!this->SpecifiedVSInstallVersion.empty()) {
|
|
// Assume the version specified by the user is correct.
|
|
vsInstanceInfo.Version = this->SpecifiedVSInstallVersion;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (!LoadVSInstanceVCToolsetVersion(vsInstanceInfo)) {
|
|
return false;
|
|
}
|
|
|
|
chosenInstanceInfo = std::move(vsInstanceInfo);
|
|
return true;
|
|
}
|
|
|
|
bool cmVSSetupAPIHelper::Initialize()
|
|
{
|
|
if (initializationFailure)
|
|
return false;
|
|
|
|
if (FAILED(comInitialized)) {
|
|
initializationFailure = true;
|
|
return false;
|
|
}
|
|
|
|
if (FAILED(setupConfig.CoCreateInstance(CLSID_SetupConfiguration, NULL,
|
|
IID_ISetupConfiguration,
|
|
CLSCTX_INPROC_SERVER)) ||
|
|
setupConfig == NULL) {
|
|
initializationFailure = true;
|
|
return false;
|
|
}
|
|
|
|
if (FAILED(setupConfig.QueryInterface(IID_ISetupConfiguration2,
|
|
(void**)&setupConfig2)) ||
|
|
setupConfig2 == NULL) {
|
|
initializationFailure = true;
|
|
return false;
|
|
}
|
|
|
|
if (FAILED(
|
|
setupConfig.QueryInterface(IID_ISetupHelper, (void**)&setupHelper)) ||
|
|
setupHelper == NULL) {
|
|
initializationFailure = true;
|
|
return false;
|
|
}
|
|
|
|
initializationFailure = false;
|
|
return true;
|
|
}
|