/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmGlobalVisualStudioGenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmCallVisualStudioMacro.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmSourceFile.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmUuid.h" #include "cmake.h" namespace { std::string GetSLNFile(cmLocalGenerator const* root) { return cmStrCat(root->GetCurrentBinaryDirectory(), '/', root->GetProjectName(), ".sln"); } } cmGlobalVisualStudioGenerator::cmGlobalVisualStudioGenerator(cmake* cm) : cmGlobalGenerator(cm) { cm->GetState()->SetIsGeneratorMultiConfig(true); cm->GetState()->SetWindowsShell(true); cm->GetState()->SetWindowsVSIDE(true); this->DefaultPlatformName = "Win32"; } cmGlobalVisualStudioGenerator::~cmGlobalVisualStudioGenerator() = default; cmGlobalVisualStudioGenerator::VSVersion cmGlobalVisualStudioGenerator::GetVersion() const { return this->Version; } void cmGlobalVisualStudioGenerator::SetVersion(VSVersion v) { this->Version = v; } void cmGlobalVisualStudioGenerator::EnableLanguage( std::vector const& lang, cmMakefile* mf, bool optional) { mf->AddDefinition("CMAKE_VS_PLATFORM_NAME_DEFAULT", this->DefaultPlatformName); this->cmGlobalGenerator::EnableLanguage(lang, mf, optional); } bool cmGlobalVisualStudioGenerator::SetGeneratorPlatform(std::string const& p, cmMakefile* mf) { if (!this->InitializePlatform(mf)) { return false; } if (this->GetPlatformName() == "x64"_s) { mf->AddDefinition("CMAKE_FORCE_WIN64", "TRUE"); } else if (this->GetPlatformName() == "Itanium"_s) { mf->AddDefinition("CMAKE_FORCE_IA64", "TRUE"); } mf->AddDefinition("CMAKE_VS_PLATFORM_NAME", this->GetPlatformName()); return this->cmGlobalGenerator::SetGeneratorPlatform(p, mf); } bool cmGlobalVisualStudioGenerator::InitializePlatform(cmMakefile*) { return true; } cmValue cmGlobalVisualStudioGenerator::GetDebuggerWorkingDirectory( cmGeneratorTarget* gt) const { if (cmValue ret = gt->GetProperty("VS_DEBUGGER_WORKING_DIRECTORY")) { return ret; } else { return cmGlobalGenerator::GetDebuggerWorkingDirectory(gt); } } std::string const& cmGlobalVisualStudioGenerator::GetPlatformName() const { if (!this->GeneratorPlatform.empty()) { return this->GeneratorPlatform; } return this->DefaultPlatformName; } char const* cmGlobalVisualStudioGenerator::GetIDEVersion() const { switch (this->Version) { case cmGlobalVisualStudioGenerator::VSVersion::VS14: return "14.0"; case cmGlobalVisualStudioGenerator::VSVersion::VS15: return "15.0"; case cmGlobalVisualStudioGenerator::VSVersion::VS16: return "16.0"; case cmGlobalVisualStudioGenerator::VSVersion::VS17: return "17.0"; case cmGlobalVisualStudioGenerator::VSVersion::VS18: return "18.0"; } return ""; } std::string cmGlobalVisualStudioGenerator::GetRegistryBase() { return cmGlobalVisualStudioGenerator::GetRegistryBase(this->GetIDEVersion()); } std::string cmGlobalVisualStudioGenerator::GetRegistryBase(char const* version) { return cmStrCat(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\)", version); } void cmGlobalVisualStudioGenerator::AddExtraIDETargets() { // Add a special target that depends on ALL projects for easy build // of one configuration only. for (auto const& it : this->ProjectMap) { std::vector const& gen = it.second; // add the ALL_BUILD to the first local generator of each project if (!gen.empty()) { // Use no actual command lines so that the target itself is not // considered always out of date. auto cc = cm::make_unique(); cc->SetEscapeOldStyle(false); cc->SetComment("Build all projects"); cmTarget* allBuild = gen[0]->AddUtilityCommand("ALL_BUILD", true, std::move(cc)); gen[0]->AddGeneratorTarget( cm::make_unique(allBuild, gen[0])); // // Organize in the "predefined targets" folder: // if (this->UseFolderProperty()) { allBuild->SetProperty("FOLDER", this->GetPredefinedTargetsFolder()); } // Now make all targets depend on the ALL_BUILD target for (cmLocalGenerator const* i : gen) { for (auto const& tgt : i->GetGeneratorTargets()) { if (tgt->GetType() == cmStateEnums::GLOBAL_TARGET || tgt->IsImported()) { continue; } if (!this->IsExcluded(gen[0], tgt.get())) { allBuild->AddUtility(tgt->GetName(), false); } } } } } // Configure CMake Visual Studio macros, for this user on this version // of Visual Studio. this->ConfigureCMakeVisualStudioMacros(); } void cmGlobalVisualStudioGenerator::ComputeTargetObjectDirectory( cmGeneratorTarget* gt) const { std::string dir = cmStrCat(gt->GetSupportDirectory(), '/', this->GetCMakeCFGIntDir(), '/'); gt->ObjectDirectory = dir; } bool IsVisualStudioMacrosFileRegistered(std::string const& macrosFile, std::string const& regKeyBase, std::string& nextAvailableSubKeyName); void RegisterVisualStudioMacros(std::string const& macrosFile, std::string const& regKeyBase); #define CMAKE_VSMACROS_FILENAME "CMakeVSMacros2.vsmacros" #define CMAKE_VSMACROS_RELOAD_MACRONAME \ "Macros.CMakeVSMacros2.Macros.ReloadProjects" #define CMAKE_VSMACROS_STOP_MACRONAME "Macros.CMakeVSMacros2.Macros.StopBuild" void cmGlobalVisualStudioGenerator::ConfigureCMakeVisualStudioMacros() { std::string dir = this->GetUserMacrosDirectory(); if (!dir.empty()) { std::string src = cmStrCat(cmSystemTools::GetCMakeRoot(), "/Templates/" CMAKE_VSMACROS_FILENAME); std::string dst = cmStrCat(dir, "/CMakeMacros/" CMAKE_VSMACROS_FILENAME); // Copy the macros file to the user directory only if the // destination does not exist or the source location is newer. // This will allow the user to edit the macros for development // purposes but newer versions distributed with CMake will replace // older versions in user directories. int res; if (!cmSystemTools::FileTimeCompare(src, dst, &res) || res > 0) { if (!cmSystemTools::CopyFileAlways(src, dst)) { std::ostringstream oss; oss << "Could not copy from: " << src << std::endl << " to: " << dst << std::endl; cmSystemTools::Message(oss.str(), "Warning"); } } RegisterVisualStudioMacros(dst, this->GetUserMacrosRegKeyBase()); } } void cmGlobalVisualStudioGenerator::CallVisualStudioMacro( MacroName m, std::string const& vsSolutionFile) { // If any solution or project files changed during the generation, // tell Visual Studio to reload them... std::string dir = this->GetUserMacrosDirectory(); // Only really try to call the macro if: // - there is a UserMacrosDirectory // - the CMake vsmacros file exists // - the CMake vsmacros file is registered // - there were .sln/.vcproj files changed during generation // if (!dir.empty()) { std::string macrosFile = cmStrCat(dir, "/CMakeMacros/" CMAKE_VSMACROS_FILENAME); std::string nextSubkeyName; if (cmSystemTools::FileExists(macrosFile) && IsVisualStudioMacrosFileRegistered( macrosFile, this->GetUserMacrosRegKeyBase(), nextSubkeyName)) { if (m == MacroReload) { std::vector filenames; this->GetFilesReplacedDuringGenerate(filenames); if (!filenames.empty()) { std::string projects = cmJoin(filenames, ";"); cmCallVisualStudioMacro::CallMacro( vsSolutionFile, CMAKE_VSMACROS_RELOAD_MACRONAME, projects, this->GetCMakeInstance()->GetDebugOutput()); } } else if (m == MacroStop) { cmCallVisualStudioMacro::CallMacro( vsSolutionFile, CMAKE_VSMACROS_STOP_MACRONAME, "", this->GetCMakeInstance()->GetDebugOutput()); } } } } std::string cmGlobalVisualStudioGenerator::GetUserMacrosDirectory() { return ""; } std::string cmGlobalVisualStudioGenerator::GetUserMacrosRegKeyBase() { return ""; } bool cmGlobalVisualStudioGenerator::FindMakeProgram(cmMakefile* mf) { // Visual Studio generators know how to lookup their build tool // directly instead of needing a helper module to do it, so we // do not actually need to put CMAKE_MAKE_PROGRAM into the cache. if (mf->GetDefinition("CMAKE_MAKE_PROGRAM").IsOff()) { mf->AddDefinition("CMAKE_MAKE_PROGRAM", this->GetVSMakeProgram()); } return true; } std::string cmGlobalVisualStudioGenerator::GetStartupProjectName( cmLocalGenerator const* root) const { cmValue n = root->GetMakefile()->GetProperty("VS_STARTUP_PROJECT"); if (cmNonempty(n)) { std::string startup = *n; if (this->FindTarget(startup)) { return startup; } root->GetMakefile()->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat("Directory property VS_STARTUP_PROJECT specifies target " "'", startup, "' that does not exist. Ignoring.")); } // default, if not specified return this->GetAllTargetName(); } bool IsVisualStudioMacrosFileRegistered(std::string const& macrosFile, std::string const& regKeyBase, std::string& nextAvailableSubKeyName) { bool macrosRegistered = false; std::string s1; std::string s2; // Make lowercase local copies, convert to Unix slashes, and // see if the resulting strings are the same: s1 = cmSystemTools::LowerCase(macrosFile); cmSystemTools::ConvertToUnixSlashes(s1); std::string keyname; HKEY hkey = nullptr; LONG result = ERROR_SUCCESS; DWORD index = 0; keyname = cmStrCat(regKeyBase, "\\OtherProjects7"); hkey = nullptr; result = RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(), 0, KEY_READ, &hkey); if (ERROR_SUCCESS == result) { // Iterate the subkeys and look for the values of interest in each subkey: wchar_t subkeyname[256]; DWORD cch_subkeyname = cm::size(subkeyname); wchar_t keyclass[256]; DWORD cch_keyclass = cm::size(keyclass); FILETIME lastWriteTime; lastWriteTime.dwHighDateTime = 0; lastWriteTime.dwLowDateTime = 0; while (ERROR_SUCCESS == RegEnumKeyExW(hkey, index, subkeyname, &cch_subkeyname, 0, keyclass, &cch_keyclass, &lastWriteTime)) { // Open the subkey and query the values of interest: HKEY hsubkey = nullptr; result = RegOpenKeyExW(hkey, subkeyname, 0, KEY_READ, &hsubkey); if (ERROR_SUCCESS == result) { DWORD valueType = REG_SZ; wchar_t data1[256]; DWORD cch_data1 = sizeof(data1); RegQueryValueExW(hsubkey, L"Path", 0, &valueType, (LPBYTE)data1, &cch_data1); DWORD data2 = 0; DWORD cch_data2 = sizeof(data2); RegQueryValueExW(hsubkey, L"Security", 0, &valueType, (LPBYTE)&data2, &cch_data2); DWORD data3 = 0; DWORD cch_data3 = sizeof(data3); RegQueryValueExW(hsubkey, L"StorageFormat", 0, &valueType, (LPBYTE)&data3, &cch_data3); s2 = cmSystemTools::LowerCase(cmsys::Encoding::ToNarrow(data1)); cmSystemTools::ConvertToUnixSlashes(s2); if (s2 == s1) { macrosRegistered = true; } std::string fullname = cmsys::Encoding::ToNarrow(data1); std::string filename; std::string filepath; std::string filepathname; std::string filepathpath; if (cmSystemTools::FileExists(fullname)) { filename = cmSystemTools::GetFilenameName(fullname); filepath = cmSystemTools::GetFilenamePath(fullname); filepathname = cmSystemTools::GetFilenameName(filepath); filepathpath = cmSystemTools::GetFilenamePath(filepath); } // std::cout << keyname << "\\" << subkeyname << ":" << std::endl; // std::cout << " Path: " << data1 << std::endl; // std::cout << " Security: " << data2 << std::endl; // std::cout << " StorageFormat: " << data3 << std::endl; // std::cout << " filename: " << filename << std::endl; // std::cout << " filepath: " << filepath << std::endl; // std::cout << " filepathname: " << filepathname << std::endl; // std::cout << " filepathpath: " << filepathpath << std::endl; // std::cout << std::endl; RegCloseKey(hsubkey); } else { std::cout << "error opening subkey: " << cmsys::Encoding::ToNarrow(subkeyname) << std::endl; std::cout << std::endl; } ++index; cch_subkeyname = cm::size(subkeyname); cch_keyclass = cm::size(keyclass); lastWriteTime.dwHighDateTime = 0; lastWriteTime.dwLowDateTime = 0; } RegCloseKey(hkey); } else { std::cout << "error opening key: " << keyname << std::endl; std::cout << std::endl; } // Pass back next available sub key name, assuming sub keys always // follow the expected naming scheme. Expected naming scheme is that // the subkeys of OtherProjects7 is 0 to n-1, so it's ok to use "n" // as the name of the next subkey. nextAvailableSubKeyName = std::to_string(index); keyname = cmStrCat(regKeyBase, "\\RecordingProject7"); hkey = nullptr; result = RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(), 0, KEY_READ, &hkey); if (ERROR_SUCCESS == result) { DWORD valueType = REG_SZ; wchar_t data1[256]; DWORD cch_data1 = sizeof(data1); RegQueryValueExW(hkey, L"Path", 0, &valueType, (LPBYTE)data1, &cch_data1); DWORD data2 = 0; DWORD cch_data2 = sizeof(data2); RegQueryValueExW(hkey, L"Security", 0, &valueType, (LPBYTE)&data2, &cch_data2); DWORD data3 = 0; DWORD cch_data3 = sizeof(data3); RegQueryValueExW(hkey, L"StorageFormat", 0, &valueType, (LPBYTE)&data3, &cch_data3); s2 = cmSystemTools::LowerCase(cmsys::Encoding::ToNarrow(data1)); cmSystemTools::ConvertToUnixSlashes(s2); if (s2 == s1) { macrosRegistered = true; } // std::cout << keyname << ":" << std::endl; // std::cout << " Path: " << data1 << std::endl; // std::cout << " Security: " << data2 << std::endl; // std::cout << " StorageFormat: " << data3 << std::endl; // std::cout << std::endl; RegCloseKey(hkey); } else { std::cout << "error opening key: " << keyname << std::endl; std::cout << std::endl; } return macrosRegistered; } void WriteVSMacrosFileRegistryEntry(std::string const& nextAvailableSubKeyName, std::string const& macrosFile, std::string const& regKeyBase) { std::string keyname = cmStrCat(regKeyBase, "\\OtherProjects7"); HKEY hkey = nullptr; LONG result = RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(), 0, KEY_READ | KEY_WRITE, &hkey); if (ERROR_SUCCESS == result) { // Create the subkey and set the values of interest: HKEY hsubkey = nullptr; wchar_t lpClass[] = L""; result = RegCreateKeyExW( hkey, cmsys::Encoding::ToWide(nextAvailableSubKeyName).c_str(), 0, lpClass, 0, KEY_READ | KEY_WRITE, 0, &hsubkey, 0); if (ERROR_SUCCESS == result) { DWORD dw = 0; std::string s(macrosFile); std::replace(s.begin(), s.end(), '/', '\\'); std::wstring ws = cmsys::Encoding::ToWide(s); result = RegSetValueExW(hsubkey, L"Path", 0, REG_SZ, (LPBYTE)ws.c_str(), static_cast(ws.size() + 1) * sizeof(wchar_t)); if (ERROR_SUCCESS != result) { std::cout << "error result 1: " << result << std::endl; std::cout << std::endl; } // Security value is always "1" for sample macros files (seems to be "2" // if you put the file somewhere outside the standard VSMacros folder) dw = 1; result = RegSetValueExW(hsubkey, L"Security", 0, REG_DWORD, (LPBYTE)&dw, sizeof(DWORD)); if (ERROR_SUCCESS != result) { std::cout << "error result 2: " << result << std::endl; std::cout << std::endl; } // StorageFormat value is always "0" for sample macros files dw = 0; result = RegSetValueExW(hsubkey, L"StorageFormat", 0, REG_DWORD, (LPBYTE)&dw, sizeof(DWORD)); if (ERROR_SUCCESS != result) { std::cout << "error result 3: " << result << std::endl; std::cout << std::endl; } RegCloseKey(hsubkey); } else { std::cout << "error creating subkey: " << nextAvailableSubKeyName << std::endl; std::cout << std::endl; } RegCloseKey(hkey); } else { std::cout << "error opening key: " << keyname << std::endl; std::cout << std::endl; } } void RegisterVisualStudioMacros(std::string const& macrosFile, std::string const& regKeyBase) { bool macrosRegistered; std::string nextAvailableSubKeyName; macrosRegistered = IsVisualStudioMacrosFileRegistered( macrosFile, regKeyBase, nextAvailableSubKeyName); if (!macrosRegistered) { int count = cmCallVisualStudioMacro::GetNumberOfRunningVisualStudioInstances("ALL"); // Only register the macros file if there are *no* instances of Visual // Studio running. If we register it while one is running, first, it has // no effect on the running instance; second, and worse, Visual Studio // removes our newly added registration entry when it quits. Instead, // emit a warning asking the user to exit all running Visual Studio // instances... // if (0 != count) { std::ostringstream oss; oss << "Could not register CMake's Visual Studio macros file '" << CMAKE_VSMACROS_FILENAME "' while Visual Studio is running." << " Please exit all running instances of Visual Studio before" << " continuing." << std::endl << std::endl << "CMake needs to register Visual Studio macros when its macros" << " file is updated or when it detects that its current macros file" << " is no longer registered with Visual Studio." << std::endl; cmSystemTools::Message(oss.str(), "Warning"); // Count them again now that the warning is over. In the case of a GUI // warning, the user may have gone to close Visual Studio and then come // back to the CMake GUI and clicked ok on the above warning. If so, // then register the macros *now* if the count is *now* 0... // count = cmCallVisualStudioMacro::GetNumberOfRunningVisualStudioInstances( "ALL"); // Also re-get the nextAvailableSubKeyName in case Visual Studio // wrote out new registered macros information as it was exiting: // if (0 == count) { IsVisualStudioMacrosFileRegistered(macrosFile, regKeyBase, nextAvailableSubKeyName); } } // Do another if check - 'count' may have changed inside the above if: // if (0 == count) { WriteVSMacrosFileRegistryEntry(nextAvailableSubKeyName, macrosFile, regKeyBase); } } } bool cmGlobalVisualStudioGenerator::TargetIsFortranOnly( cmGeneratorTarget const* gt) const { // If there's only one source language, Fortran has to be used // in order for the sources to compile. std::set languages = gt->GetAllConfigCompileLanguages(); // Consider an explicit linker language property, but *not* the // computed linker language that may depend on linked targets. // This allows the project to control the language choice in // a target with none of its own sources, e.g. when also using // object libraries. cmValue linkLang = gt->GetProperty("LINKER_LANGUAGE"); if (cmNonempty(linkLang)) { languages.insert(*linkLang); } // Intel Fortran .vfproj files do support the resource compiler. languages.erase("RC"); return languages.size() == 1 && *languages.begin() == "Fortran"_s; } bool cmGlobalVisualStudioGenerator::IsInSolution( cmGeneratorTarget const* gt) const { return gt->IsInBuildSystem(); } bool cmGlobalVisualStudioGenerator::IsDepInSolution( std::string const& targetName) const { return !targetName.empty(); } bool cmGlobalVisualStudioGenerator::TargetCompare::operator()( cmGeneratorTarget const* l, cmGeneratorTarget const* r) const { // Make sure a given named target is ordered first, // e.g. to set ALL_BUILD as the default active project. // When the empty string is named this is a no-op. if (r->GetName() == this->First) { return false; } if (l->GetName() == this->First) { return true; } return l->GetName() < r->GetName(); } cmGlobalVisualStudioGenerator::OrderedTargetDependSet::OrderedTargetDependSet( TargetDependSet const& targets, std::string const& first) : derived(TargetCompare(first)) { this->insert(targets.begin(), targets.end()); } cmGlobalVisualStudioGenerator::OrderedTargetDependSet::OrderedTargetDependSet( TargetSet const& targets, std::string const& first) : derived(TargetCompare(first)) { for (cmGeneratorTarget const* it : targets) { this->insert(it); } } std::string cmGlobalVisualStudioGenerator::ExpandCFGIntDir( std::string const& str, std::string const& config) const { std::string replace = GetCMakeCFGIntDir(); std::string tmp = str; for (std::string::size_type i = tmp.find(replace); i != std::string::npos; i = tmp.find(replace, i)) { tmp.replace(i, replace.size(), config); i += config.size(); } return tmp; } void cmGlobalVisualStudioGenerator::AddSymbolExportCommand( cmGeneratorTarget* gt, std::vector& commands, std::string const& configName) { cmGeneratorTarget::ModuleDefinitionInfo const* mdi = gt->GetModuleDefinitionInfo(configName); if (!mdi || !mdi->DefFileGenerated) { return; } std::vector outputs; outputs.push_back(mdi->DefFile); std::vector empty; std::vector objectSources; gt->GetObjectSources(objectSources, configName); std::map mapping; for (cmSourceFile const* it : objectSources) { mapping[it]; } gt->LocalGenerator->ComputeObjectFilenames(mapping, configName, gt); std::string obj_dir = gt->ObjectDirectory; std::string cmakeCommand = cmSystemTools::GetCMakeCommand(); std::string obj_dir_expanded = obj_dir; cmSystemTools::ReplaceString(obj_dir_expanded, this->GetCMakeCFGIntDir(), configName.c_str()); cmSystemTools::MakeDirectory(obj_dir_expanded); std::string const objs_file = cmStrCat(obj_dir_expanded, "/objects.txt"); cmGeneratedFileStream fout(objs_file.c_str()); if (!fout) { cmSystemTools::Error(cmStrCat("could not open ", objs_file)); return; } auto const useShortPaths = this->UseShortObjectNames() ? cmObjectLocations::UseShortPath::Yes : cmObjectLocations::UseShortPath::No; if (mdi->WindowsExportAllSymbols) { std::vector objs; for (cmSourceFile const* it : objectSources) { // Find the object file name corresponding to this source file. // It must exist because we populated the mapping just above. auto const& locs = mapping[it]; std::string const& v = locs.GetPath(useShortPaths); assert(!v.empty()); std::string objFile = cmStrCat(obj_dir, v); objs.push_back(objFile); } std::vector externalObjectSources; gt->GetExternalObjects(externalObjectSources, configName); for (cmSourceFile const* it : externalObjectSources) { objs.push_back(it->GetFullPath()); } for (std::string const& it : objs) { std::string objFile = it; // replace $(ConfigurationName) in the object names cmSystemTools::ReplaceString(objFile, this->GetCMakeCFGIntDir(), configName); if (cmHasLiteralSuffix(objFile, ".obj")) { fout << objFile << "\n"; } } } for (cmSourceFile const* i : mdi->Sources) { fout << i->GetFullPath() << "\n"; } cmCustomCommandLines commandLines = cmMakeSingleCommandLine( { cmakeCommand, "-E", "__create_def", mdi->DefFile, objs_file }); cmCustomCommand command; command.SetOutputs(outputs); command.SetCommandLines(commandLines); command.SetComment("Auto build dll exports"); command.SetBacktrace(gt->Target->GetMakefile()->GetBacktrace()); command.SetWorkingDirectory("."); command.SetStdPipesUTF8(true); commands.push_back(std::move(command)); } static bool OpenSolution(std::string const& sln) { HRESULT comInitialized = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (FAILED(comInitialized)) { return false; } HINSTANCE hi = ShellExecuteA(nullptr, "open", sln.c_str(), nullptr, nullptr, SW_SHOWNORMAL); CoUninitialize(); return reinterpret_cast(hi) > 32; } bool cmGlobalVisualStudioGenerator::Open(std::string const& bindir, std::string const& projectName, bool dryRun) { std::string sln = cmStrCat(bindir, '/', projectName, ".sln"); if (dryRun) { return cmSystemTools::FileExists(sln, true); } sln = cmSystemTools::ConvertToOutputPath(sln); return std::async(std::launch::async, OpenSolution, sln).get(); } bool cmGlobalVisualStudioGenerator::IsDependedOn( TargetDependSet const& projectTargets, cmGeneratorTarget const* gtIn) const { return std::any_of(projectTargets.begin(), projectTargets.end(), [this, gtIn](cmTargetDepend const& l) { TargetDependSet const& tgtdeps = this->GetTargetDirectDepends(l); return tgtdeps.count(gtIn); }); } std::set cmGlobalVisualStudioGenerator::IsPartOfDefaultBuild( std::vector const& configs, TargetDependSet const& projectTargets, cmGeneratorTarget const* target) const { std::set activeConfigs; // if it is a utility target then only make it part of the // default build if another target depends on it int type = target->GetType(); if (type == cmStateEnums::GLOBAL_TARGET) { std::vector targetNames; targetNames.push_back("INSTALL"); targetNames.push_back("PACKAGE"); for (std::string const& t : targetNames) { // check if target is part of default build if (target->GetName() == t) { std::string const propertyName = cmStrCat("CMAKE_VS_INCLUDE_", t, "_TO_DEFAULT_BUILD"); // inspect CMAKE_VS_INCLUDE__TO_DEFAULT_BUILD properties for (std::string const& i : configs) { cmValue propertyValue = target->Target->GetMakefile()->GetDefinition(propertyName); if (propertyValue && cmIsOn(cmGeneratorExpression::Evaluate( *propertyValue, target->GetLocalGenerator(), i))) { activeConfigs.insert(i); } } } } return activeConfigs; } if (type == cmStateEnums::UTILITY && !this->IsDependedOn(projectTargets, target)) { return activeConfigs; } // inspect EXCLUDE_FROM_DEFAULT_BUILD[_] properties for (std::string const& i : configs) { if (target->GetFeature("EXCLUDE_FROM_DEFAULT_BUILD", i).IsOff()) { activeConfigs.insert(i); } } return activeConfigs; } std::string cmGlobalVisualStudioGenerator::GetGUID( std::string const& name) const { std::string const& guidStoreName = cmStrCat(name, "_GUID_CMAKE"); if (cmValue storedGUID = this->CMakeInstance->GetCacheDefinition(guidStoreName)) { return *storedGUID; } // Compute a GUID that is deterministic but unique to the build tree. std::string input = cmStrCat(this->CMakeInstance->GetState()->GetBinaryDirectory(), '|', name); cmUuid uuidGenerator; std::vector uuidNamespace; uuidGenerator.StringToBinary("ee30c4be-5192-4fb0-b335-722a2dffe760", uuidNamespace); std::string guid = uuidGenerator.FromMd5(uuidNamespace, input); return cmSystemTools::UpperCase(guid); } cm::VS::Solution::Folder* cmGlobalVisualStudioGenerator::CreateSolutionFolder( cm::VS::Solution& solution, cm::string_view rawName) const { cm::VS::Solution::Folder* folder = nullptr; std::string canonicalName; for (std::string::size_type cur = 0;;) { static std::string delims = "/\\"; cur = rawName.find_first_not_of(delims, cur); if (cur == std::string::npos) { break; } std::string::size_type end = rawName.find_first_of(delims, cur); cm::string_view f = end == std::string::npos ? rawName.substr(cur) : rawName.substr(cur, end - cur); canonicalName = canonicalName.empty() ? std::string(f) : cmStrCat(canonicalName, '/', f); cm::VS::Solution::Folder* nextFolder = solution.GetFolder(canonicalName); if (nextFolder->Id.empty()) { nextFolder->Id = this->GetGUID(cmStrCat("CMAKE_FOLDER_GUID_"_s, canonicalName)); if (folder) { folder->Folders.emplace_back(nextFolder); } solution.Folders.emplace_back(nextFolder); } folder = nextFolder; cur = end; } return folder; } cm::VS::Solution cmGlobalVisualStudioGenerator::CreateSolution( cmLocalGenerator const* root, TargetDependSet const& projectTargets) const { using namespace cm::VS; Solution solution; solution.VSVersion = this->Version; solution.VSExpress = this->ExpressEdition ? VersionExpress::Yes : VersionExpress::No; solution.Platform = this->GetPlatformName(); solution.Configs = root->GetMakefile()->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); solution.StartupProject = this->GetStartupProjectName(root); auto addProject = [this, useFolders = this->UseFolderProperty(), &solution](cmGeneratorTarget const* gt, Solution::Project const* p) { if (Solution::Folder* const folder = useFolders ? this->CreateSolutionFolder(solution, gt->GetEffectiveFolderName()) : nullptr) { folder->Projects.emplace_back(p); } else { solution.Projects.emplace_back(p); } }; for (cmTargetDepend const& projectTarget : projectTargets) { cmGeneratorTarget const* gt = projectTarget; if (!this->IsInSolution(gt)) { continue; } Solution::Project* project = solution.GetProject(gt->GetName()); project->Id = this->GetGUID(gt->GetName()); std::set const& includeConfigs = this->IsPartOfDefaultBuild(solution.Configs, projectTargets, gt); auto addProjectConfig = [this, project, gt, &includeConfigs](std::string const& solutionConfig, std::string const& projectConfig) { bool const build = includeConfigs.find(solutionConfig) != includeConfigs.end(); bool const deploy = this->NeedsDeploy(*gt, solutionConfig.c_str()); project->Configs.emplace_back( Solution::ProjectConfig{ projectConfig, build, deploy }); }; if (cmValue expath = gt->GetProperty("EXTERNAL_MSPROJECT")) { project->Path = *expath; cmValue const projectType = gt->GetProperty("VS_PROJECT_TYPE"); if (!projectType.IsEmpty()) { project->TypeId = *projectType; } else { project->TypeId = Solution::Project::TypeIdDefault; } for (std::string const& config : solution.Configs) { cmList mapConfig{ gt->GetProperty(cmStrCat( "MAP_IMPORTED_CONFIG_", cmSystemTools::UpperCase(config))) }; addProjectConfig(config, !mapConfig.empty() ? mapConfig[0] : config); } cmValue platformMapping = gt->GetProperty("VS_PLATFORM_MAPPING"); project->Platform = !platformMapping.IsEmpty() ? *platformMapping : solution.Platform; for (BT> const& i : gt->GetUtilities()) { std::string const& dep = i.Value.first; if (this->IsDepInSolution(dep)) { project->BuildDependencies.emplace_back(solution.GetProject(dep)); } } addProject(gt, project); continue; } cmValue vcprojName = gt->GetProperty("GENERATOR_FILE_NAME"); cmValue vcprojType = gt->GetProperty("GENERATOR_FILE_NAME_EXT"); if (vcprojName && vcprojType) { cmLocalGenerator* lg = gt->GetLocalGenerator(); std::string dir = root->MaybeRelativeToCurBinDir(lg->GetCurrentBinaryDirectory()); if (dir == "."_s) { dir.clear(); } else if (!cmHasLiteralSuffix(dir, "/")) { dir += "/"; } project->Path = cmStrCat(dir, *vcprojName, *vcprojType); if (this->TargetIsFortranOnly(gt)) { project->TypeId = Solution::Project::TypeIdFortran; } else if (gt->IsCSharpOnly()) { project->TypeId = Solution::Project::TypeIdCSharp; } else { project->TypeId = Solution::Project::TypeIdDefault; } project->Platform = // On VS 19 and above, always map .NET SDK projects to "Any CPU". (gt->IsDotNetSdkTarget() && this->Version >= VSVersion::VS16 && !cmGlobalVisualStudioGenerator::IsReservedTarget(gt->GetName())) ? "Any CPU" : solution.Platform; // Add solution-level dependencies. TargetDependSet const& depends = this->GetTargetDirectDepends(gt); for (cmTargetDepend const& dep : depends) { if (this->IsInSolution(dep)) { project->BuildDependencies.emplace_back( solution.GetProject(dep->GetName())); } } for (std::string const& config : solution.Configs) { addProjectConfig(config, config); } addProject(gt, project); continue; } } cmMakefile* mf = root->GetMakefile(); // Unfortunately we have to copy the source groups because // FindSourceGroup uses a regex which is modifying the group. std::vector sourceGroups = mf->GetSourceGroups(); std::vector items = cmList{ root->GetMakefile()->GetProperty("VS_SOLUTION_ITEMS") }; for (std::string item : items) { if (!cmSystemTools::FileIsFullPath(item)) { item = cmSystemTools::CollapseFullPath(item, mf->GetCurrentSourceDirectory()); } cmSourceGroup* sg = mf->FindSourceGroup(item, sourceGroups); std::string folderName = sg->GetFullName(); if (folderName.empty()) { folderName = "Solution Items"_s; } Solution::Folder* folder = this->CreateSolutionFolder(solution, folderName); folder->Files.emplace(std::move(item)); } Solution::PropertyGroup* pgExtensibilityGlobals = nullptr; Solution::PropertyGroup* pgExtensibilityAddIns = nullptr; std::vector const propKeys = root->GetMakefile()->GetPropertyKeys(); for (std::string const& it : propKeys) { if (!cmHasLiteralPrefix(it, "VS_GLOBAL_SECTION_")) { continue; } std::string name = it.substr(18); Solution::PropertyGroup::Load scope; if (cmHasLiteralPrefix(name, "PRE_")) { name = name.substr(4); scope = Solution::PropertyGroup::Load::Pre; } else if (cmHasLiteralPrefix(name, "POST_")) { name = name.substr(5); scope = Solution::PropertyGroup::Load::Post; } else { continue; } if (name.empty()) { continue; } Solution::PropertyGroup* pg = solution.GetPropertyGroup(name); solution.PropertyGroups.emplace_back(pg); pg->Scope = scope; cmList keyValuePairs{ root->GetMakefile()->GetProperty(it) }; for (std::string const& itPair : keyValuePairs) { std::string::size_type const posEqual = itPair.find('='); if (posEqual != std::string::npos) { std::string key = cmTrimWhitespace(itPair.substr(0, posEqual)); std::string value = cmTrimWhitespace(itPair.substr(posEqual + 1)); pg->Map.emplace(std::move(key), std::move(value)); } } if (name == "ExtensibilityGlobals"_s) { pgExtensibilityGlobals = pg; } else if (name == "ExtensibilityAddIns"_s) { pgExtensibilityAddIns = pg; } } if (!pgExtensibilityGlobals) { pgExtensibilityGlobals = solution.GetPropertyGroup("ExtensibilityGlobals"_s); solution.PropertyGroups.emplace_back(pgExtensibilityGlobals); } std::string const solutionGuid = this->GetGUID(cmStrCat(root->GetProjectName(), ".sln")); pgExtensibilityGlobals->Map.emplace("SolutionGuid", cmStrCat('{', solutionGuid, '}')); if (!pgExtensibilityAddIns) { pgExtensibilityAddIns = solution.GetPropertyGroup("ExtensibilityAddIns"_s); solution.PropertyGroups.emplace_back(pgExtensibilityAddIns); } solution.CanonicalizeOrder(); return solution; } void cmGlobalVisualStudioGenerator::Generate() { // first do the superclass method this->cmGlobalGenerator::Generate(); // Now write out the VS Solution files. for (auto& it : this->ProjectMap) { this->GenerateSolution(it.second[0], it.second); } // If any solution or project files changed during the generation, // tell Visual Studio to reload them... if (!cmSystemTools::GetErrorOccurredFlag() && !this->LocalGenerators.empty()) { this->CallVisualStudioMacro(MacroReload, GetSLNFile(this->LocalGenerators[0].get())); } if (this->Version == VSVersion::VS14 && !this->CMakeInstance->GetIsInTryCompile()) { std::string cmakeWarnVS14; if (cmValue cached = this->CMakeInstance->GetState()->GetCacheEntryValue( "CMAKE_WARN_VS14")) { this->CMakeInstance->MarkCliAsUsed("CMAKE_WARN_VS14"); cmakeWarnVS14 = *cached; } else { cmSystemTools::GetEnv("CMAKE_WARN_VS14", cmakeWarnVS14); } if (cmakeWarnVS14.empty() || !cmIsOff(cmakeWarnVS14)) { this->CMakeInstance->IssueMessage( MessageType::WARNING, "The \"Visual Studio 14 2015\" generator is deprecated " "and will be removed in a future version of CMake." "\n" "Add CMAKE_WARN_VS14=OFF to the cache to disable this warning."); } } } void cmGlobalVisualStudioGenerator::GenerateSolution( cmLocalGenerator const* root, std::vector const& generators) { if (generators.empty()) { return; } // Collect all targets under this root generator and the transitive // closure of their dependencies. TargetDependSet const projectTargets = this->GetTargetsForProject(root, generators); std::string fname = GetSLNFile(root); cmGeneratedFileStream fout(fname); fout.SetCopyIfDifferent(true); if (!fout) { return; } cm::VS::Solution const solution = this->CreateSolution(root, projectTargets); WriteSln(fout, solution); if (fout.Close()) { this->FileReplacedDuringGenerate(fname); } }