diff options
author | David Cole <david.cole@kitware.com> | 2007-11-16 12:01:58 (GMT) |
---|---|---|
committer | David Cole <david.cole@kitware.com> | 2007-11-16 12:01:58 (GMT) |
commit | 867de7fc670ffd67c9cb8dcffae42f87de7023ed (patch) | |
tree | febaeb64e474158b5289f31853b20cdcb396fe43 | |
parent | 0a001f1567dff13bb3441728b313b9fa7b2b1b11 (diff) | |
download | CMake-867de7fc670ffd67c9cb8dcffae42f87de7023ed.zip CMake-867de7fc670ffd67c9cb8dcffae42f87de7023ed.tar.gz CMake-867de7fc670ffd67c9cb8dcffae42f87de7023ed.tar.bz2 |
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.
-rw-r--r-- | Source/CMakeLists.txt | 5 | ||||
-rw-r--r-- | Source/cmCallVisualStudioMacro.cxx | 464 | ||||
-rw-r--r-- | Source/cmCallVisualStudioMacro.h | 49 | ||||
-rw-r--r-- | Source/cmGeneratedFileStream.cxx | 14 | ||||
-rw-r--r-- | Source/cmGeneratedFileStream.h | 4 | ||||
-rw-r--r-- | Source/cmGlobalGenerator.cxx | 18 | ||||
-rw-r--r-- | Source/cmGlobalGenerator.h | 8 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudio7Generator.cxx | 17 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudio8Generator.cxx | 28 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudio8Generator.h | 8 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudio9Generator.cxx | 27 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudio9Generator.h | 7 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudioGenerator.cxx | 355 | ||||
-rw-r--r-- | Source/cmGlobalVisualStudioGenerator.h | 20 | ||||
-rw-r--r-- | Source/cmLocalVisualStudio7Generator.cxx | 4 | ||||
-rw-r--r-- | Source/cmake.cxx | 29 | ||||
-rw-r--r-- | Source/kwsys/SystemTools.cxx | 22 | ||||
-rw-r--r-- | Templates/CMakeVSMacros1.vsmacros | bin | 0 -> 88064 bytes |
18 files changed, 1064 insertions, 15 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 86c4579..d4ec11a 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 0000000..635e264 --- /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 0000000..ea3cc10 --- /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 660d9d5..1bd8669 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 9d718e1..2dfeaf3 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 5299a1c..7acd92b 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 160c728..8e1bf12 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 5136779..3556d79 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 5013a68..3ea9f75 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -72,6 +72,34 @@ void cmGlobalVisualStudio8Generator::Configure() } //---------------------------------------------------------------------------- +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() { // Add a special target on which all other targets depend that diff --git a/Source/cmGlobalVisualStudio8Generator.h b/Source/cmGlobalVisualStudio8Generator.h index 38963e3..b7250a1 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 c0d31e6..06d959b 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 565d762..03c422a 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 658516c..9e6817e 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,11 +58,100 @@ 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() { // For VS versions before 8: @@ -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 92acb69..575910d 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 33181f4..bb3668c 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 dad2980..bec904d 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 0f837f7..37562b1 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 Binary files differnew file mode 100644 index 0000000..bfb60dc --- /dev/null +++ b/Templates/CMakeVSMacros1.vsmacros |