diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 86c457988..d4ec11aca 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 000000000..635e264a0 --- /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 + + +//---------------------------------------------------------------------------- +///! 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& 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& instances) +{ + std::map mrot; + + HRESULT hr = GetRunningInstances(mrot); + ReportHRESULT(hr, "GetRunningInstances"); + + if(SUCCEEDED(hr)) + { + std::map::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 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 instances; + hr = FindVisualStudioInstances(slnFile, instances); + ReportHRESULT(hr, "FindVisualStudioInstances"); + + if(SUCCEEDED(hr)) + { + err = 0; // no error + + std::vector::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 000000000..ea3cc104d --- /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 660d9d5ef..1bd8669d2 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 9d718e181..2dfeaf357 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 5299a1c90..7acd92bc2 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& 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 160c728d0..8e1bf12e1 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -230,6 +230,11 @@ public: const std::map >& GetProjectMap() const {return this->ProjectMap;} + + // track files replaced during a Generate + void FileReplacedDuringGenerate(const std::string& filename); + void GetFilesReplacedDuringGenerate(std::vector& filenames); + protected: void SetLanguageEnabledFlag(const char* l, cmMakefile* mf); void SetLanguageEnabledMaps(const char* l, cmMakefile* mf); @@ -287,6 +292,9 @@ private: std::map > TargetDependencies; cmExternalMakefileProjectGenerator* ExtraGenerator; + + // track files replaced during a Generate + std::vector FilesReplacedDuringGenerate; }; #endif diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 513677941..3556d7939 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 >::iterator it; for(it = this->ProjectMap.begin(); it!= this->ProjectMap.end(); ++it) { diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index 5013a6824..3ea9f75c0 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 38963e358..b7250a13e 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 c0d31e6da..06d959b60 100644 --- a/Source/cmGlobalVisualStudio9Generator.cxx +++ b/Source/cmGlobalVisualStudio9Generator.cxx @@ -53,9 +53,36 @@ void cmGlobalVisualStudio9Generator entry.Full = ""; } +//---------------------------------------------------------------------------- void cmGlobalVisualStudio9Generator ::EnableLanguage(std::vectorconst & 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 565d7624b..03c422a6f 100644 --- a/Source/cmGlobalVisualStudio9Generator.h +++ b/Source/cmGlobalVisualStudio9Generator.h @@ -51,5 +51,12 @@ public: virtual void EnableLanguage(std::vectorconst& 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 658516c3e..9e6817e53 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 filenames; + this->GetFilesReplacedDuringGenerate(filenames); + if (filenames.size() > 0) + { + // Convert vector to semi-colon delimited string of filenames: + std::string projects; + std::vector::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 + +//---------------------------------------------------------------------------- +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 92acb69ba..575910ded 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 33181f432..bb3668c1d 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 dad2980b1..bec904d76 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& 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 0f837f78c..37562b1a9 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 000000000..bfb60dc63 Binary files /dev/null and b/Templates/CMakeVSMacros1.vsmacros differ