|
|
|
/*============================================================================
|
|
|
|
CMake - Cross Platform Makefile Generator
|
|
|
|
Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
|
|
|
|
|
|
|
|
Distributed under the OSI-approved BSD License (the "License");
|
|
|
|
see accompanying file Copyright.txt for details.
|
|
|
|
|
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
|
|
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the License for more information.
|
|
|
|
============================================================================*/
|
|
|
|
|
|
|
|
#include "cmCallVisualStudioMacro.h"
|
|
|
|
#include "cmSystemTools.h"
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
|
|
#define HAVE_COMDEF_H
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// Just for this file:
|
|
|
|
//
|
|
|
|
static bool LogErrorsAsMessages;
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(HAVE_COMDEF_H)
|
|
|
|
|
|
|
|
|
|
|
|
#include <comdef.h>
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Copied from a correct comdef.h to avoid problems with deficient versions
|
|
|
|
// of comdef.h that exist in the wild... Fixes issue #7533.
|
|
|
|
//
|
|
|
|
#ifdef _NATIVE_WCHAR_T_DEFINED
|
|
|
|
# ifdef _DEBUG
|
|
|
|
# pragma comment(lib, "comsuppwd.lib")
|
|
|
|
# else
|
|
|
|
# pragma comment(lib, "comsuppw.lib")
|
|
|
|
# endif
|
|
|
|
#else
|
|
|
|
# ifdef _DEBUG
|
|
|
|
# pragma comment(lib, "comsuppd.lib")
|
|
|
|
# else
|
|
|
|
# pragma comment(lib, "comsupp.lib")
|
|
|
|
# endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
///! Use ReportHRESULT to make a cmSystemTools::Message after calling
|
|
|
|
///! a COM method that may have failed.
|
|
|
|
#define ReportHRESULT(hr, context) \
|
|
|
|
if (FAILED(hr)) \
|
|
|
|
{ \
|
|
|
|
if (LogErrorsAsMessages) \
|
|
|
|
{ \
|
|
|
|
std::ostringstream _hresult_oss; \
|
|
|
|
_hresult_oss.flags(std::ios::hex); \
|
|
|
|
_hresult_oss << context << " failed HRESULT, hr = 0x" \
|
|
|
|
<< hr << std::endl; \
|
|
|
|
_hresult_oss.flags(std::ios::dec); \
|
|
|
|
_hresult_oss << __FILE__ << "(" << __LINE__ << ")"; \
|
|
|
|
cmSystemTools::Message(_hresult_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);
|
|
|
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << std::endl;
|
|
|
|
oss << "Invoke(ExecuteCommand)" << std::endl;
|
|
|
|
oss << " Macro: " << macro.c_str() << std::endl;
|
|
|
|
oss << " Args: " << args.c_str() << std::endl;
|
|
|
|
|
|
|
|
if (DISP_E_EXCEPTION == hr)
|
|
|
|
{
|
|
|
|
oss << "DISP_E_EXCEPTION EXCEPINFO:" << excep.wCode << std::endl;
|
|
|
|
oss << " wCode: " << excep.wCode << std::endl;
|
|
|
|
oss << " wReserved: " << excep.wReserved << std::endl;
|
|
|
|
if (excep.bstrSource)
|
|
|
|
{
|
|
|
|
oss << " bstrSource: " <<
|
|
|
|
(const char*)(_bstr_t)excep.bstrSource << std::endl;
|
|
|
|
}
|
|
|
|
if (excep.bstrDescription)
|
|
|
|
{
|
|
|
|
oss << " bstrDescription: " <<
|
|
|
|
(const char*)(_bstr_t)excep.bstrDescription << std::endl;
|
|
|
|
}
|
|
|
|
if (excep.bstrHelpFile)
|
|
|
|
{
|
|
|
|
oss << " bstrHelpFile: " <<
|
|
|
|
(const char*)(_bstr_t)excep.bstrHelpFile << std::endl;
|
|
|
|
}
|
|
|
|
oss << " dwHelpContext: " << excep.dwHelpContext << std::endl;
|
|
|
|
oss << " pvReserved: " << excep.pvReserved << std::endl;
|
|
|
|
oss << " pfnDeferredFillIn: " << excep.pfnDeferredFillIn << std::endl;
|
|
|
|
oss << " scode: " << excep.scode << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string exstr(oss.str());
|
|
|
|
ReportHRESULT(hr, exstr.c_str());
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
LogErrorsAsMessages = false;
|
|
|
|
|
|
|
|
#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 = static_cast<int>(instances.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Force release all COM pointers before CoUninitialize:
|
|
|
|
instances.clear();
|
|
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
(void)slnFile;
|
|
|
|
#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,
|
|
|
|
const bool logErrorsAsMessages)
|
|
|
|
{
|
|
|
|
int err = 1; // no comdef.h
|
|
|
|
|
|
|
|
LogErrorsAsMessages = logErrorsAsMessages;
|
|
|
|
|
|
|
|
#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
|
|
|
|
(void)slnFile;
|
|
|
|
(void)macro;
|
|
|
|
(void)args;
|
|
|
|
if (LogErrorsAsMessages)
|
|
|
|
{
|
|
|
|
cmSystemTools::Message("cmCallVisualStudioMacro::CallMacro is not "
|
|
|
|
"supported on this platform");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (err && LogErrorsAsMessages)
|
|
|
|
{
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << "cmCallVisualStudioMacro::CallMacro failed, err = " << err;
|
|
|
|
cmSystemTools::Message(oss.str().c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|