From 867de7fc670ffd67c9cb8dcffae42f87de7023ed Mon Sep 17 00:00:00 2001 From: David Cole <david.cole@kitware.com> Date: Fri, 16 Nov 2007 07:01:58 -0500 Subject: [PATCH] ENH: Add ability to call Visual Studio macros from CMake. Add a CMake Visual Studio macro to reload a solution file automatically if CMake makes changes to .sln files or .vcproj files. Add code to call the macro automatically for any running Visual Studio instances with the .sln file open at the end of the Visual Studio Generate call. Only call the macro if some .sln or .vcproj file changed during Generate. Also, add handling for REG_EXPAND_SZ type to SystemTools::ReadRegistryValue - returned string has environment variable references expanded. --- Source/CMakeLists.txt | 5 + Source/cmCallVisualStudioMacro.cxx | 464 ++++++++++++++++++++++ Source/cmCallVisualStudioMacro.h | 49 +++ Source/cmGeneratedFileStream.cxx | 14 +- Source/cmGeneratedFileStream.h | 4 +- Source/cmGlobalGenerator.cxx | 18 + Source/cmGlobalGenerator.h | 8 + Source/cmGlobalVisualStudio7Generator.cxx | 17 +- Source/cmGlobalVisualStudio8Generator.cxx | 28 ++ Source/cmGlobalVisualStudio8Generator.h | 8 + Source/cmGlobalVisualStudio9Generator.cxx | 27 ++ Source/cmGlobalVisualStudio9Generator.h | 7 + Source/cmGlobalVisualStudioGenerator.cxx | 355 +++++++++++++++++ Source/cmGlobalVisualStudioGenerator.h | 20 + Source/cmLocalVisualStudio7Generator.cxx | 4 + Source/cmake.cxx | 29 ++ Source/kwsys/SystemTools.cxx | 22 +- Templates/CMakeVSMacros1.vsmacros | Bin 0 -> 88064 bytes 18 files changed, 1064 insertions(+), 15 deletions(-) create mode 100644 Source/cmCallVisualStudioMacro.cxx create mode 100644 Source/cmCallVisualStudioMacro.h create mode 100644 Templates/CMakeVSMacros1.vsmacros diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 86c4579883..d4ec11aca0 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -208,6 +208,11 @@ ENDIF(APPLE) IF (WIN32) + SET(SRCS ${SRCS} + cmCallVisualStudioMacro.cxx + cmCallVisualStudioMacro.h + ) + IF(NOT UNIX) SET(SRCS ${SRCS} cmGlobalBorlandMakefileGenerator.cxx diff --git a/Source/cmCallVisualStudioMacro.cxx b/Source/cmCallVisualStudioMacro.cxx new file mode 100644 index 0000000000..635e264a0e --- /dev/null +++ b/Source/cmCallVisualStudioMacro.cxx @@ -0,0 +1,464 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + +#include "cmCallVisualStudioMacro.h" +#include "cmSystemTools.h" + + +#if defined(_MSC_VER) +#define HAVE_COMDEF_H +#endif + + +#if defined(HAVE_COMDEF_H) + + +#include <comdef.h> + + +//---------------------------------------------------------------------------- +///! Use ReportHRESULT to make a cmSystemTools::Error after calling +///! a COM method that may have failed. +#define ReportHRESULT(hr, context) \ + if (FAILED(hr)) \ + { \ + std::ostringstream oss; \ + oss.flags(std::ios::hex); \ + oss << context << " failed HRESULT, hr = 0x" << hr << std::endl; \ + oss.flags(std::ios::dec); \ + oss << __FILE__ << "(" << __LINE__ << ")"; \ + cmSystemTools::Error(oss.str().c_str()); \ + } + + +//---------------------------------------------------------------------------- +///! Using the given instance of Visual Studio, call the named macro +HRESULT InstanceCallMacro( + IDispatch* vsIDE, + const std::string& macro, + const std::string& args) +{ + HRESULT hr = E_POINTER; + + _bstr_t macroName(macro.c_str()); + _bstr_t macroArgs(args.c_str()); + + if (0 != vsIDE) + { + DISPID dispid = (DISPID) -1; + OLECHAR *name = L"ExecuteCommand"; + + hr = vsIDE->GetIDsOfNames(IID_NULL, &name, 1, + LOCALE_USER_DEFAULT, &dispid); + ReportHRESULT(hr, "GetIDsOfNames(ExecuteCommand)"); + + if (SUCCEEDED(hr)) + { + VARIANTARG vargs[2]; + DISPPARAMS params; + VARIANT result; + EXCEPINFO excep; + UINT arg = (UINT) -1; + + // No VariantInit or VariantClear calls are necessary for + // these two vargs. They are both local _bstr_t variables + // that remain in scope for the duration of the Invoke call. + // + V_VT(&vargs[1]) = VT_BSTR; + V_BSTR(&vargs[1]) = macroName; + V_VT(&vargs[0]) = VT_BSTR; + V_BSTR(&vargs[0]) = macroArgs; + + params.rgvarg = &vargs[0]; + params.rgdispidNamedArgs = 0; + params.cArgs = sizeof(vargs)/sizeof(vargs[0]); + params.cNamedArgs = 0; + + VariantInit(&result); + + memset(&excep, 0, sizeof(excep)); + + hr = vsIDE->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_METHOD, ¶ms, &result, &excep, &arg); + ReportHRESULT(hr, "Invoke(ExecuteCommand)"); + + VariantClear(&result); + } + } + + return hr; +} + + +//---------------------------------------------------------------------------- +///! Get the Solution object from the IDE object +HRESULT GetSolutionObject( + IDispatch* vsIDE, + IDispatchPtr& vsSolution) +{ + HRESULT hr = E_POINTER; + + if (0 != vsIDE) + { + DISPID dispid = (DISPID) -1; + OLECHAR *name = L"Solution"; + + hr = vsIDE->GetIDsOfNames(IID_NULL, &name, 1, + LOCALE_USER_DEFAULT, &dispid); + ReportHRESULT(hr, "GetIDsOfNames(Solution)"); + + if (SUCCEEDED(hr)) + { + DISPPARAMS params; + VARIANT result; + EXCEPINFO excep; + UINT arg = (UINT) -1; + + params.rgvarg = 0; + params.rgdispidNamedArgs = 0; + params.cArgs = 0; + params.cNamedArgs = 0; + + VariantInit(&result); + + memset(&excep, 0, sizeof(excep)); + + hr = vsIDE->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_PROPERTYGET, ¶ms, &result, &excep, &arg); + ReportHRESULT(hr, "Invoke(Solution)"); + + if (SUCCEEDED(hr)) + { + vsSolution = V_DISPATCH(&result); + } + + VariantClear(&result); + } + } + + return hr; +} + + +//---------------------------------------------------------------------------- +///! Get the FullName property from the Solution object +HRESULT GetSolutionFullName( + IDispatch* vsSolution, + std::string& fullName) +{ + HRESULT hr = E_POINTER; + + if (0 != vsSolution) + { + DISPID dispid = (DISPID) -1; + OLECHAR *name = L"FullName"; + + hr = vsSolution->GetIDsOfNames(IID_NULL, &name, 1, + LOCALE_USER_DEFAULT, &dispid); + ReportHRESULT(hr, "GetIDsOfNames(FullName)"); + + if (SUCCEEDED(hr)) + { + DISPPARAMS params; + VARIANT result; + EXCEPINFO excep; + UINT arg = (UINT) -1; + + params.rgvarg = 0; + params.rgdispidNamedArgs = 0; + params.cArgs = 0; + params.cNamedArgs = 0; + + VariantInit(&result); + + memset(&excep, 0, sizeof(excep)); + + hr = vsSolution->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_PROPERTYGET, ¶ms, &result, &excep, &arg); + ReportHRESULT(hr, "Invoke(FullName)"); + + if (SUCCEEDED(hr)) + { + fullName = (std::string) (_bstr_t) V_BSTR(&result); + } + + VariantClear(&result); + } + } + + return hr; +} + + +//---------------------------------------------------------------------------- +///! Get the FullName property from the Solution object, given the IDE object +HRESULT GetIDESolutionFullName( + IDispatch* vsIDE, + std::string& fullName) +{ + IDispatchPtr vsSolution; + HRESULT hr = GetSolutionObject(vsIDE, vsSolution); + ReportHRESULT(hr, "GetSolutionObject"); + + if (SUCCEEDED(hr)) + { + GetSolutionFullName(vsSolution, fullName); + ReportHRESULT(hr, "GetSolutionFullName"); + } + + return hr; +} + + +//---------------------------------------------------------------------------- +///! Get all running objects from the Windows running object table. +///! Save them in a map by their display names. +HRESULT GetRunningInstances(std::map<std::string, IUnknownPtr>& mrot) +{ + // mrot == Map of the Running Object Table + + IRunningObjectTablePtr runningObjectTable; + IEnumMonikerPtr monikerEnumerator; + IMonikerPtr moniker; + ULONG numFetched = 0; + + HRESULT hr = GetRunningObjectTable(0, &runningObjectTable); + ReportHRESULT(hr, "GetRunningObjectTable"); + + if(SUCCEEDED(hr)) + { + hr = runningObjectTable->EnumRunning(&monikerEnumerator); + ReportHRESULT(hr, "EnumRunning"); + } + + if(SUCCEEDED(hr)) + { + hr = monikerEnumerator->Reset(); + ReportHRESULT(hr, "Reset"); + } + + if(SUCCEEDED(hr)) + { + while (S_OK == monikerEnumerator->Next(1, &moniker, &numFetched)) + { + std::string runningObjectName; + IUnknownPtr runningObjectVal; + IBindCtxPtr ctx; + + hr = CreateBindCtx(0, &ctx); + ReportHRESULT(hr, "CreateBindCtx"); + + if(SUCCEEDED(hr)) + { + LPOLESTR displayName = 0; + hr = moniker->GetDisplayName(ctx, 0, &displayName); + ReportHRESULT(hr, "GetDisplayName"); + if (displayName) + { + runningObjectName = (std::string) (_bstr_t) displayName; + CoTaskMemFree(displayName); + } + + hr = runningObjectTable->GetObject(moniker, &runningObjectVal); + ReportHRESULT(hr, "GetObject"); + if(SUCCEEDED(hr)) + { + mrot.insert(std::make_pair(runningObjectName, runningObjectVal)); + } + } + + numFetched = 0; + moniker = 0; + } + } + + return hr; +} + + +//---------------------------------------------------------------------------- +///! Do the two file names refer to the same Visual Studio solution? Or are +///! we perhaps looking for any and all solutions? +bool FilesSameSolution( + const std::string& slnFile, + const std::string& slnName) +{ + if (slnFile == "ALL" || slnName == "ALL") + { + return true; + } + + // Otherwise, make lowercase local copies, convert to Unix slashes, and + // see if the resulting strings are the same: + std::string s1 = cmSystemTools::LowerCase(slnFile); + std::string s2 = cmSystemTools::LowerCase(slnName); + cmSystemTools::ConvertToUnixSlashes(s1); + cmSystemTools::ConvertToUnixSlashes(s2); + + return s1 == s2; +} + + +//---------------------------------------------------------------------------- +///! Find instances of Visual Studio with the given solution file +///! open. Pass "ALL" for slnFile to gather all running instances +///! of Visual Studio. +HRESULT FindVisualStudioInstances( + const std::string& slnFile, + std::vector<IDispatchPtr>& instances) +{ + std::map<std::string, IUnknownPtr> mrot; + + HRESULT hr = GetRunningInstances(mrot); + ReportHRESULT(hr, "GetRunningInstances"); + + if(SUCCEEDED(hr)) + { + std::map<std::string, IUnknownPtr>::iterator it; + for(it = mrot.begin(); it != mrot.end(); ++it) + { + if (cmSystemTools::StringStartsWith(it->first.c_str(), + "!VisualStudio.DTE.")) + { + IDispatchPtr disp(it->second); + if (disp != (IDispatch*) 0) + { + std::string slnName; + hr = GetIDESolutionFullName(disp, slnName); + ReportHRESULT(hr, "GetIDESolutionFullName"); + + if (FilesSameSolution(slnFile, slnName)) + { + instances.push_back(disp); + + //std::cout << "Found Visual Studio instance." << std::endl; + //std::cout << " ROT entry name: " << it->first << std::endl; + //std::cout << " ROT entry object: " << (IUnknown*) it->second << std::endl; + //std::cout << " slnFile: " << slnFile << std::endl; + //std::cout << " slnName: " << slnName << std::endl; + } + } + } + } + } + + return hr; +} + + +#endif //defined(HAVE_COMDEF_H) + + +//---------------------------------------------------------------------------- +int cmCallVisualStudioMacro::GetNumberOfRunningVisualStudioInstances( + const std::string& slnFile) +{ + int count = 0; + +#if defined(HAVE_COMDEF_H) + HRESULT hr = CoInitialize(0); + ReportHRESULT(hr, "CoInitialize"); + + if(SUCCEEDED(hr)) + { + std::vector<IDispatchPtr> instances; + hr = FindVisualStudioInstances(slnFile, instances); + ReportHRESULT(hr, "FindVisualStudioInstances"); + + if(SUCCEEDED(hr)) + { + count = instances.size(); + } + + // Force release all COM pointers before CoUninitialize: + instances.clear(); + + CoUninitialize(); + } +#endif + + return count; +} + + +//---------------------------------------------------------------------------- +///! Get all running objects from the Windows running object table. +///! Save them in a map by their display names. +int cmCallVisualStudioMacro::CallMacro( + const std::string& slnFile, + const std::string& macro, + const std::string& args) +{ + int err = 1; // no comdef.h + +#if defined(HAVE_COMDEF_H) + err = 2; // error initializing + + HRESULT hr = CoInitialize(0); + ReportHRESULT(hr, "CoInitialize"); + + if(SUCCEEDED(hr)) + { + std::vector<IDispatchPtr> instances; + hr = FindVisualStudioInstances(slnFile, instances); + ReportHRESULT(hr, "FindVisualStudioInstances"); + + if(SUCCEEDED(hr)) + { + err = 0; // no error + + std::vector<IDispatchPtr>::iterator it; + for(it = instances.begin(); it != instances.end(); ++it) + { + hr = InstanceCallMacro(*it, macro, args); + ReportHRESULT(hr, "InstanceCallMacro"); + + if (FAILED(hr)) + { + err = 3; // error attempting to call the macro + } + } + + if(0 == instances.size()) + { + // no instances to call + + //cmSystemTools::Message( + // "cmCallVisualStudioMacro::CallMacro no instances found to call", + // "Warning"); + } + } + + // Force release all COM pointers before CoUninitialize: + instances.clear(); + + CoUninitialize(); + } +#else + cmSystemTools::Error("cmCallVisualStudioMacro::CallMacro is not " + "supported on this platform"); +#endif + + if (err) + { + std::ostringstream oss; + oss << "cmCallVisualStudioMacro::CallMacro failed, err = " << err; + cmSystemTools::Error(oss.str().c_str()); + } + + return err; +} diff --git a/Source/cmCallVisualStudioMacro.h b/Source/cmCallVisualStudioMacro.h new file mode 100644 index 0000000000..ea3cc104d6 --- /dev/null +++ b/Source/cmCallVisualStudioMacro.h @@ -0,0 +1,49 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmCallVisualStudioMacro_h +#define cmCallVisualStudioMacro_h + +#include "cmStandardIncludes.h" + +/** \class cmCallVisualStudioMacro + * \brief Control class for communicating with CMake's Visual Studio macros + * + * Find running instances of Visual Studio by full path solution name. + * Call a Visual Studio IDE macro in any of those instances. + */ +class cmCallVisualStudioMacro +{ +public: + ///! Call the named macro in instances of Visual Studio with the + ///! given solution file open. Pass "ALL" for slnFile to call the + ///! macro in each Visual Studio instance. + static int CallMacro(const std::string& slnFile, + const std::string& macro, + const std::string& args); + + ///! Count the number of running instances of Visual Studio with the + ///! given solution file open. Pass "ALL" for slnFile to count all + ///! running Visual Studio instances. + static int GetNumberOfRunningVisualStudioInstances( + const std::string& slnFile); + +protected: + +private: +}; + +#endif diff --git a/Source/cmGeneratedFileStream.cxx b/Source/cmGeneratedFileStream.cxx index 660d9d5ef7..1bd8669d26 100644 --- a/Source/cmGeneratedFileStream.cxx +++ b/Source/cmGeneratedFileStream.cxx @@ -90,7 +90,7 @@ cmGeneratedFileStream::Open(const char* name, bool quiet, bool binaryFlag) } //---------------------------------------------------------------------------- -cmGeneratedFileStream& +bool cmGeneratedFileStream::Close() { // Save whether the temporary output file is valid before closing. @@ -100,9 +100,7 @@ cmGeneratedFileStream::Close() this->Stream::close(); // Remove the temporary file (possibly by renaming to the real file). - this->cmGeneratedFileStreamBase::Close(); - - return *this; + return this->cmGeneratedFileStreamBase::Close(); } //---------------------------------------------------------------------------- @@ -170,8 +168,10 @@ void cmGeneratedFileStreamBase::Open(const char* name) } //---------------------------------------------------------------------------- -void cmGeneratedFileStreamBase::Close() +bool cmGeneratedFileStreamBase::Close() { + bool replaced = false; + std::string resname = this->Name; if ( this->Compress && this->CompressExtraExtension ) { @@ -200,12 +200,16 @@ void cmGeneratedFileStreamBase::Close() { this->RenameFile(this->TempName.c_str(), resname.c_str()); } + + replaced = true; } // Else, the destination was not replaced. // // Always delete the temporary file. We never want it to stay around. cmSystemTools::RemoveFile(this->TempName.c_str()); + + return replaced; } //---------------------------------------------------------------------------- diff --git a/Source/cmGeneratedFileStream.h b/Source/cmGeneratedFileStream.h index 9d718e1813..2dfeaf3571 100644 --- a/Source/cmGeneratedFileStream.h +++ b/Source/cmGeneratedFileStream.h @@ -44,7 +44,7 @@ protected: // after the real stream is closed and Okay is set to whether the // real stream was still valid for writing when it was closed. void Open(const char* name); - void Close(); + bool Close(); // Internal file replacement implementation. int RenameFile(const char* oldname, const char* newname); @@ -123,7 +123,7 @@ public: * destionation file if the stream is still valid when this method * is called. */ - cmGeneratedFileStream& Close(); + bool Close(); /** * Set whether copy-if-different is done. diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 5299a1c905..7acd92bc28 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -737,6 +737,10 @@ void cmGlobalGenerator::Configure() void cmGlobalGenerator::Generate() { + // Some generators track files replaced during the Generate. + // Start with an empty vector: + this->FilesReplacedDuringGenerate.clear(); + // For each existing cmLocalGenerator unsigned int i; @@ -1785,3 +1789,17 @@ const char* cmGlobalGenerator::GetExtraGeneratorName() const { return this->ExtraGenerator==0 ? 0 : this->ExtraGenerator->GetName(); } + +void cmGlobalGenerator::FileReplacedDuringGenerate(const std::string& filename) +{ + this->FilesReplacedDuringGenerate.push_back(filename); +} + +void cmGlobalGenerator::GetFilesReplacedDuringGenerate(std::vector<std::string>& filenames) +{ + filenames.clear(); + std::copy( + this->FilesReplacedDuringGenerate.begin(), + this->FilesReplacedDuringGenerate.end(), + std::back_inserter(filenames)); +} diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 160c728d04..8e1bf12e10 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -230,6 +230,11 @@ public: const std::map<cmStdString, std::vector<cmLocalGenerator*> >& GetProjectMap() const {return this->ProjectMap;} + + // track files replaced during a Generate + void FileReplacedDuringGenerate(const std::string& filename); + void GetFilesReplacedDuringGenerate(std::vector<std::string>& filenames); + protected: void SetLanguageEnabledFlag(const char* l, cmMakefile* mf); void SetLanguageEnabledMaps(const char* l, cmMakefile* mf); @@ -287,6 +292,9 @@ private: std::map<cmStdString, std::vector<cmTarget *> > TargetDependencies; cmExternalMakefileProjectGenerator* ExtraGenerator; + + // track files replaced during a Generate + std::vector<std::string> FilesReplacedDuringGenerate; }; #endif diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 513677941a..3556d79397 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -16,8 +16,8 @@ =========================================================================*/ #include "windows.h" // this must be first to define GetCurrentDirectory #include "cmGlobalVisualStudio7Generator.h" -#include "cmLocalVisualStudio7Generator.h" #include "cmGeneratedFileStream.h" +#include "cmLocalVisualStudio7Generator.h" #include "cmMakefile.h" #include "cmake.h" @@ -202,7 +202,7 @@ void cmGlobalVisualStudio7Generator::GenerateConfigurations(cmMakefile* mf) configs += ";"; configs += this->Configurations[i]; } - + mf->AddCacheDefinition( "CMAKE_CONFIGURATION_TYPES", configs.c_str(), @@ -219,6 +219,13 @@ void cmGlobalVisualStudio7Generator::Generate() // Now write out the DSW this->OutputSLNFile(); + + // If any solution or project files changed during the generation, + // tell Visual Studio to reload them... + if(!cmSystemTools::GetErrorOccuredFlag()) + { + this->CallVisualStudioReloadMacro(); + } } void cmGlobalVisualStudio7Generator @@ -241,11 +248,15 @@ void cmGlobalVisualStudio7Generator return; } this->WriteSLNFile(fout, root, generators); + if (fout.Close()) + { + this->FileReplacedDuringGenerate(fname); + } } // output the SLN file void cmGlobalVisualStudio7Generator::OutputSLNFile() -{ +{ std::map<cmStdString, std::vector<cmLocalGenerator*> >::iterator it; for(it = this->ProjectMap.begin(); it!= this->ProjectMap.end(); ++it) { diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index 5013a6824e..3ea9f75c0e 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -71,6 +71,34 @@ void cmGlobalVisualStudio8Generator::Configure() this->CreateGUID(CMAKE_CHECK_BUILD_SYSTEM_TARGET); } +//---------------------------------------------------------------------------- +std::string cmGlobalVisualStudio8Generator::GetUserMacrosDirectory() +{ + std::string base; + std::string path; + + // base begins with the VisualStudioProjectsLocation reg value... + if (cmSystemTools::ReadRegistryValue( + "HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\8.0;VisualStudioProjectsLocation", + base)) + { + cmSystemTools::ConvertToUnixSlashes(base); + + // 7.0 macros folder: + //path = base + "/VSMacros"; + + // 7.1 macros folder: + //path = base + "/VSMacros71"; + + // 8.0 macros folder: + path = base + "/VSMacros80"; + } + + // path is (correctly) still empty if we did not read the base value from + // the Registry value + return path; +} + //---------------------------------------------------------------------------- void cmGlobalVisualStudio8Generator::Generate() { diff --git a/Source/cmGlobalVisualStudio8Generator.h b/Source/cmGlobalVisualStudio8Generator.h index 38963e3589..b7250a13eb 100644 --- a/Source/cmGlobalVisualStudio8Generator.h +++ b/Source/cmGlobalVisualStudio8Generator.h @@ -49,6 +49,14 @@ public: */ virtual void Configure(); virtual void Generate(); + + /** + * Where does this version of Visual Studio look for macros for the + * current user? Returns the empty string if this version of Visual + * Studio does not implement support for VB macros. + */ + virtual std::string GetUserMacrosDirectory(); + protected: // Utility target fix is not needed for VS8. diff --git a/Source/cmGlobalVisualStudio9Generator.cxx b/Source/cmGlobalVisualStudio9Generator.cxx index c0d31e6dae..06d959b608 100644 --- a/Source/cmGlobalVisualStudio9Generator.cxx +++ b/Source/cmGlobalVisualStudio9Generator.cxx @@ -53,9 +53,36 @@ void cmGlobalVisualStudio9Generator entry.Full = ""; } +//---------------------------------------------------------------------------- void cmGlobalVisualStudio9Generator ::EnableLanguage(std::vector<std::string>const & lang, cmMakefile *mf, bool optional) { cmGlobalVisualStudio8Generator::EnableLanguage(lang, mf, optional); } + +//---------------------------------------------------------------------------- +std::string cmGlobalVisualStudio9Generator::GetUserMacrosDirectory() +{ + std::string base; + std::string path; + + // base begins with the VisualStudioProjectsLocation reg value... + if (cmSystemTools::ReadRegistryValue( + "HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\9.0;VisualStudioProjectsLocation", + base)) + { + cmSystemTools::ConvertToUnixSlashes(base); + + // 9.0 macros folder: + path = base + "/VSMacros80"; + // *NOT* a typo; right now in Visual Studio 2008 beta the macros + // folder is VSMacros80... They may change it to 90 before final + // release of 2008 or they may not... we'll have to keep our eyes + // on it + } + + // path is (correctly) still empty if we did not read the base value from + // the Registry value + return path; +} diff --git a/Source/cmGlobalVisualStudio9Generator.h b/Source/cmGlobalVisualStudio9Generator.h index 565d7624b4..03c422a6f4 100644 --- a/Source/cmGlobalVisualStudio9Generator.h +++ b/Source/cmGlobalVisualStudio9Generator.h @@ -51,5 +51,12 @@ public: virtual void EnableLanguage(std::vector<std::string>const& languages, cmMakefile *, bool optional); virtual void WriteSLNHeader(std::ostream& fout); + + /** + * Where does this version of Visual Studio look for macros for the + * current user? Returns the empty string if this version of Visual + * Studio does not implement support for VB macros. + */ + virtual std::string GetUserMacrosDirectory(); }; #endif diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx index 658516c3e7..9e6817e538 100644 --- a/Source/cmGlobalVisualStudioGenerator.cxx +++ b/Source/cmGlobalVisualStudioGenerator.cxx @@ -16,6 +16,7 @@ =========================================================================*/ #include "cmGlobalVisualStudioGenerator.h" +#include "cmCallVisualStudioMacro.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmTarget.h" @@ -57,10 +58,99 @@ void cmGlobalVisualStudioGenerator::Generate() // Fix utility dependencies to avoid linking to libraries. this->FixUtilityDepends(); + // Configure CMake Visual Studio macros, for this user on this version + // of Visual Studio. + this->ConfigureCMakeVisualStudioMacros(); + // Run all the local generators. this->cmGlobalGenerator::Generate(); } +//---------------------------------------------------------------------------- +void RegisterVisualStudioMacros(const std::string& macrosFile); + +//---------------------------------------------------------------------------- +#define CMAKE_VSMACROS_FILENAME \ + "CMakeVSMacros1.vsmacros" + +#define CMAKE_VSMACROS_RELOAD_MACRONAME \ + "Macros.CMakeVSMacros1.Macros.ReloadProjects" + +//---------------------------------------------------------------------------- +void cmGlobalVisualStudioGenerator::ConfigureCMakeVisualStudioMacros() +{ + cmMakefile* mf = this->LocalGenerators[0]->GetMakefile(); + std::string dir = this->GetUserMacrosDirectory(); + + if (mf != 0 && dir != "") + { + std::string src = mf->GetRequiredDefinition("CMAKE_ROOT"); + src += "/Templates/" CMAKE_VSMACROS_FILENAME; + + std::string dst = dir + "/CMakeMacros/" CMAKE_VSMACROS_FILENAME; + + // Only copy if dst does not already exist. Write this file initially, + // but never overwrite local mods. + if (!cmSystemTools::FileExists(dst.c_str())) + { + if (!cmSystemTools::CopyFileAlways(src.c_str(), dst.c_str())) + { + std::ostringstream oss; + oss << "Could not copy from: " << src << std::endl; + oss << " to: " << dst << std::endl; + cmSystemTools::Message(oss.str().c_str(), "Warning"); + } + } + + RegisterVisualStudioMacros(dst); + } +} + +//---------------------------------------------------------------------------- +void cmGlobalVisualStudioGenerator::CallVisualStudioReloadMacro() +{ + // If any solution or project files changed during the generation, + // tell Visual Studio to reload them... + cmMakefile* mf = this->LocalGenerators[0]->GetMakefile(); + std::string dir = this->GetUserMacrosDirectory(); + + if (mf != 0 && dir != "") + { + std::vector<std::string> filenames; + this->GetFilesReplacedDuringGenerate(filenames); + if (filenames.size() > 0) + { + // Convert vector to semi-colon delimited string of filenames: + std::string projects; + std::vector<std::string>::iterator it = filenames.begin(); + if (it != filenames.end()) + { + projects = *it; + ++it; + } + for (; it != filenames.end(); ++it) + { + projects += ";"; + projects += *it; + } + + std::string topLevelSlnName = mf->GetStartOutputDirectory(); + topLevelSlnName += "/"; + topLevelSlnName += mf->GetProjectName(); + topLevelSlnName += ".sln"; + + cmCallVisualStudioMacro::CallMacro(topLevelSlnName, + CMAKE_VSMACROS_RELOAD_MACRONAME, projects); + } + } +} + +//---------------------------------------------------------------------------- +std::string cmGlobalVisualStudioGenerator::GetUserMacrosDirectory() +{ + return ""; +} + //---------------------------------------------------------------------------- void cmGlobalVisualStudioGenerator::FixUtilityDepends() { @@ -224,3 +314,268 @@ cmGlobalVisualStudioGenerator::GetUtilityForTarget(cmTarget& target, // No special case. Just use the original dependency name. return name; } + +//---------------------------------------------------------------------------- +#include <windows.h> + +//---------------------------------------------------------------------------- +bool IsVisualStudioMacrosFileRegistered(const std::string& macrosFile, + 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 = NULL; + LONG result = ERROR_SUCCESS; + DWORD index = 0; + + keyname = "Software\\Microsoft\\VisualStudio\\8.0\\vsmacros\\OtherProjects7"; + hkey = NULL; + result = RegOpenKeyEx(HKEY_CURRENT_USER, keyname.c_str(), 0, KEY_READ, &hkey); + if (ERROR_SUCCESS == result) + { + // Iterate the subkeys and look for the values of interest in each subkey: + CHAR subkeyname[256]; + DWORD cch_subkeyname = sizeof(subkeyname)/sizeof(subkeyname[0]); + CHAR keyclass[256]; + DWORD cch_keyclass = sizeof(keyclass)/sizeof(keyclass[0]); + FILETIME lastWriteTime; + lastWriteTime.dwHighDateTime = 0; + lastWriteTime.dwLowDateTime = 0; + + while (ERROR_SUCCESS == RegEnumKeyEx(hkey, index, subkeyname, &cch_subkeyname, + 0, keyclass, &cch_keyclass, &lastWriteTime)) + { + // Open the subkey and query the values of interest: + HKEY hsubkey = NULL; + result = RegOpenKeyEx(hkey, subkeyname, 0, KEY_READ, &hsubkey); + if (ERROR_SUCCESS == result) + { + DWORD valueType = REG_SZ; + CHAR data1[256]; + DWORD cch_data1 = sizeof(data1)/sizeof(data1[0]); + RegQueryValueEx(hsubkey, "Path", 0, &valueType, (LPBYTE) &data1[0], &cch_data1); + + DWORD data2 = 0; + DWORD cch_data2 = sizeof(data2); + RegQueryValueEx(hsubkey, "Security", 0, &valueType, (LPBYTE) &data2, &cch_data2); + + DWORD data3 = 0; + DWORD cch_data3 = sizeof(data3); + RegQueryValueEx(hsubkey, "StorageFormat", 0, &valueType, (LPBYTE) &data3, &cch_data3); + + s2 = cmSystemTools::LowerCase(data1); + cmSystemTools::ConvertToUnixSlashes(s2); + if (s2 == s1) + { + macrosRegistered = true; + } + + std::string fullname(data1); + std::string filename; + std::string filepath; + std::string filepathname; + std::string filepathpath; + if (cmSystemTools::FileExists(fullname.c_str())) + { + 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: " << subkeyname << std::endl; + std::cout << std::endl; + } + + ++index; + cch_subkeyname = sizeof(subkeyname)/sizeof(subkeyname[0]); + cch_keyclass = sizeof(keyclass)/sizeof(keyclass[0]); + 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. + std::ostringstream ossNext; + ossNext << index; + nextAvailableSubKeyName = ossNext.str(); + + + keyname = "Software\\Microsoft\\VisualStudio\\8.0\\vsmacros\\RecordingProject7"; + hkey = NULL; + result = RegOpenKeyEx(HKEY_CURRENT_USER, keyname.c_str(), 0, KEY_READ, &hkey); + if (ERROR_SUCCESS == result) + { + DWORD valueType = REG_SZ; + CHAR data1[256]; + DWORD cch_data1 = sizeof(data1)/sizeof(data1[0]); + RegQueryValueEx(hkey, "Path", 0, &valueType, (LPBYTE) &data1[0], &cch_data1); + + DWORD data2 = 0; + DWORD cch_data2 = sizeof(data2); + RegQueryValueEx(hkey, "Security", 0, &valueType, (LPBYTE) &data2, &cch_data2); + + DWORD data3 = 0; + DWORD cch_data3 = sizeof(data3); + RegQueryValueEx(hkey, "StorageFormat", 0, &valueType, (LPBYTE) &data3, &cch_data3); + + s2 = cmSystemTools::LowerCase(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( + const std::string& nextAvailableSubKeyName, + const std::string& macrosFile) +{ + std::string keyname = "Software\\Microsoft\\VisualStudio\\8.0\\vsmacros\\OtherProjects7"; + HKEY hkey = NULL; + LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, keyname.c_str(), 0, + KEY_READ|KEY_WRITE, &hkey); + if (ERROR_SUCCESS == result) + { + // Create the subkey and set the values of interest: + HKEY hsubkey = NULL; + result = RegCreateKeyEx(hkey, nextAvailableSubKeyName.c_str(), 0, "", 0, + KEY_READ|KEY_WRITE, 0, &hsubkey, 0); + if (ERROR_SUCCESS == result) + { + DWORD dw = 0; + + std::string s(macrosFile); + cmSystemTools::ReplaceString(s, "/", "\\"); + + result = RegSetValueEx(hsubkey, "Path", 0, REG_SZ, (LPBYTE) s.c_str(), + strlen(s.c_str()) + 1); + 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 = RegSetValueEx(hsubkey, "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 = RegSetValueEx(hsubkey, "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(const std::string& macrosFile) +{ + bool macrosRegistered; + std::string nextAvailableSubKeyName; + + macrosRegistered = IsVisualStudioMacrosFileRegistered(macrosFile, + 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 instructing the user to re-run the CMake configure step + // after exiting all running Visual Studio instances... + // + if (0 == count) + { + WriteVSMacrosFileRegistryEntry(nextAvailableSubKeyName, macrosFile); + } + else + { + std::ostringstream oss; + oss << "Could not register Visual Studio macros file '" << macrosFile + << "' with instances of Visual Studio running. Please exit all" + << " running instances of Visual Studio and rerun this CMake" + << " configure to register CMake's Visual Studio macros file." + << std::endl; + cmSystemTools::Message(oss.str().c_str(), "Warning"); + } + } +} diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index 92acb69ba2..575910ded6 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -36,10 +36,30 @@ public: */ virtual void Generate(); + /** + * Configure CMake's Visual Studio macros file into the user's Visual + * Studio macros directory. + */ + virtual void ConfigureCMakeVisualStudioMacros(); + + /** + * Where does this version of Visual Studio look for macros for the + * current user? Returns the empty string if this version of Visual + * Studio does not implement support for VB macros. + */ + virtual std::string GetUserMacrosDirectory(); + + /** + * Call the ReloadProjects macro if necessary based on + * GetFilesReplacedDuringGenerate results. + */ + virtual void CallVisualStudioReloadMacro(); + protected: virtual void CreateGUID(const char*) {} virtual void FixUtilityDepends(); const char* GetUtilityForTarget(cmTarget& target, const char*); + private: void FixUtilityDependsForTarget(cmTarget& target); void CreateUtilityDependTarget(cmTarget& target); diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index 33181f4324..bb3668c1de 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -199,6 +199,10 @@ void cmLocalVisualStudio7Generator cmGeneratedFileStream fout(fname.c_str()); fout.SetCopyIfDifferent(true); this->WriteVCProjFile(fout,lname,target); + if (fout.Close()) + { + this->GlobalGenerator->FileReplacedDuringGenerate(fname); + } } //---------------------------------------------------------------------------- diff --git a/Source/cmake.cxx b/Source/cmake.cxx index dad2980b13..bec904d762 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -76,6 +76,10 @@ #endif #include "cmGlobalUnixMakefileGenerator3.h" +#if defined(_WIN32) +#include "cmCallVisualStudioMacro.h" +#endif + #if !defined(__CYGWIN__) && !defined(CMAKE_BOOT_MINGW) # include "cmExtraCodeBlocksGenerator.h" #endif @@ -1322,6 +1326,31 @@ int cmake::ExecuteCMakeCommand(std::vector<std::string>& args) return result; } +#if defined(_WIN32) + // Internal CMake support for calling Visual Studio macros. + else if (args[1] == "cmake_call_visual_studio_macro" && args.size() >= 4) + { + // args[2] = full path to .sln file or "ALL" + // args[3] = name of Visual Studio macro to call + // args[4..args.size()-1] = [optional] args for Visual Studio macro + + std::string macroArgs; + + if (args.size() > 4) + { + macroArgs = args[4]; + + for (size_t i = 5; i < args.size(); ++i) + { + macroArgs += " "; + macroArgs += args[i]; + } + } + + return cmCallVisualStudioMacro::CallMacro(args[2], args[3], macroArgs); + } +#endif + // Internal CMake dependency scanning support. else if (args[1] == "cmake_depends" && args.size() >= 6) { diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx index 0f837f78cc..37562b1a97 100644 --- a/Source/kwsys/SystemTools.cxx +++ b/Source/kwsys/SystemTools.cxx @@ -492,11 +492,11 @@ void SystemTools::ReplaceString(kwsys_stl::string& source, #if defined(_WIN32) && !defined(__CYGWIN__) bool SystemTools::ReadRegistryValue(const char *key, kwsys_stl::string &value) { - + bool valueset = false; kwsys_stl::string primary = key; kwsys_stl::string second; kwsys_stl::string valuename; - + size_t start = primary.find("\\"); if (start == kwsys_stl::string::npos) { @@ -558,12 +558,24 @@ bool SystemTools::ReadRegistryValue(const char *key, kwsys_stl::string &value) if (dwType == REG_SZ) { value = data; - RegCloseKey(hKey); - return true; + valueset = true; + } + else if (dwType == REG_EXPAND_SZ) + { + char expanded[1024]; + DWORD dwExpandedSize = sizeof(expanded)/sizeof(expanded[0]); + if(ExpandEnvironmentStrings(data, expanded, dwExpandedSize)) + { + value = expanded; + valueset = true; + } } } + + RegCloseKey(hKey); } - return false; + + return valueset; } #else bool SystemTools::ReadRegistryValue(const char *, kwsys_stl::string &) diff --git a/Templates/CMakeVSMacros1.vsmacros b/Templates/CMakeVSMacros1.vsmacros new file mode 100644 index 0000000000000000000000000000000000000000..bfb60dc63deb72fcfc3a33a54525294114186a33 GIT binary patch literal 88064 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 literal 0 HcmV?d00001 -- GitLab