/************************************************************************
*
* Description : CSolutionLintAnalyser - class for analysing Visual C++
* solutions (.sln) and workspaces (.dsw) using the
* PC-Lint code analysis tool.
*
* (c) Copyright 2004-2008 by Anna-Jayne Metcalfe (anna@riverblade.co.uk)
* and Beth Mackenzie (beth@riverblade.co.uk) / Riverblade Limited
*
* Licence Terms:
*
* This code may be freely reused, subject to the licence terms below.
* Please do let us know of any bugs you find or improvements you make,
* so that we can pass them on to the rest of the development community.
*
* This code is free software; you can redistribute it and/or
* modify it under the terms of the Code Project Open License (CPOL)
* version 1.0 (http://www.codeproject.com/info/cpol10.aspx).
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Code Project Open Licence for further details.
*
************************************************************************
* $Archive: /Projects/Applications/LintProject/1.4/SolutionLintAnalyser.cpp $
* $Revision: 25 $
* $Date: 23/10/08 13:12 $
* $Author: Anna $
*
* $Nokeywords: $
************************************************************************/
/// \file
/// \brief CSolutionLintAnalyser class implementation.
#include "StdAfx.h"
#include "Shared/ModuleVersion.h"
#include "Shared/StringUtils.h"
#include "Shared/FileUtils.h"
#include "Shared/ResourceUtils.h"
#include "Shared/BrowserUtils.h"
#include "Shared/SplitPath.h"
#include "Shared/SolutionFileReader.h"
#include "Resource.h"
#include "SolutionLintAnalyser.h"
using namespace std;
CSolutionLintAnalyser::CSolutionLintAnalyser( const CString& sSolutionFilePathName,
const CString& sResultsFileFolder,
const CString& sLintConfigFile,
const CString& sAdditionalProjectParams,
const CString& sAdditionalParams,
const CString& sConfiguration,
const std::set<CString>& setExcludedProjects)
: m_sLintFolder(_T("") ),
m_sSolutionFilePathName(sSolutionFilePathName),
m_sSolutionFileFolder(_T("") ),
m_sSolutionName(_T("") ),
m_sResultsFileFolder(sResultsFileFolder),
m_sResultsIndexFilePathName(sResultsFileFolder + _T("\\index.html") ),
m_sLintConfigFile(sLintConfigFile),
m_sAdditionalProjectParams(sAdditionalProjectParams),
m_sAdditionalParams(sAdditionalParams),
m_sConfiguration(sConfiguration),
m_setExcludedProjects(setExcludedProjects),
m_bShowHtml(false),
m_timeStarted(),
m_nWarningCount(0),
m_sElapsedTime(_T("") ),
m_arrayProjectResults()
{
m_timeStarted.SetStatus(COleDateTime::null);
// Strip leading/trailing quotes - just in case
m_sSolutionFilePathName.TrimLeft( _T("\"") );
m_sSolutionFilePathName.TrimRight( _T("\"") );
m_sResultsFileFolder.TrimLeft( _T("\"") );
m_sResultsFileFolder.TrimRight( _T("\"") );
Utils::CSplitPath splitSolution(m_sSolutionFilePathName);
m_sSolutionFileFolder = splitSolution.GetDrive() + splitSolution.GetDirectory();
m_sSolutionName = splitSolution.GetFileName();
}
CSolutionLintAnalyser::CSolutionLintAnalyser( const CString& sSolutionFilePathName,
const CString& sResultsFileFolder,
const CString& sLintConfigFile,
const CString& sAdditionalProjectParams,
const CString& sAdditionalParams,
const CString& sConfiguration)
: m_sLintFolder(_T("") ),
m_sSolutionFilePathName(sSolutionFilePathName),
m_sSolutionFileFolder(_T("") ),
m_sSolutionName(_T("") ),
m_sResultsFileFolder(sResultsFileFolder),
m_sResultsIndexFilePathName(sResultsFileFolder + _T("\\index.html") ),
m_sLintConfigFile(sLintConfigFile),
m_sAdditionalProjectParams(sAdditionalProjectParams),
m_sAdditionalParams(sAdditionalParams),
m_sConfiguration(sConfiguration),
m_setExcludedProjects(),
m_bShowHtml(false),
m_timeStarted(),
m_nWarningCount(0),
m_sElapsedTime(_T("") ),
m_arrayProjectResults()
{
m_timeStarted.SetStatus(COleDateTime::null);
// Strip leading/trailing quotes - just in case
m_sSolutionFilePathName.TrimLeft( _T("\"") );
m_sSolutionFilePathName.TrimRight( _T("\"") );
m_sResultsFileFolder.TrimLeft( _T("\"") );
m_sResultsFileFolder.TrimRight( _T("\"") );
Utils::CSplitPath splitSolution(m_sSolutionFilePathName);
m_sSolutionFileFolder = splitSolution.GetDrive() + splitSolution.GetDirectory();
m_sSolutionName = splitSolution.GetFileName();
}
CSolutionLintAnalyser::~CSolutionLintAnalyser(void)
{
}
INT_PTR CSolutionLintAnalyser::GetConfigurations(CAtlArray<CString>& rarrayConfigurations) const
{
ATLASSERT(!m_sSolutionFilePathName.IsEmpty() );
rarrayConfigurations.RemoveAll();
if (!m_sSolutionFilePathName.IsEmpty() )
{
if (Utils::FileExists(m_sSolutionFilePathName) )
{
AddInSolutionModel::CSolutionFileReader parser;
ATLVERIFY(parser.Read(m_sSolutionFilePathName) );
(void)parser.GetConfigurations(rarrayConfigurations);
return true;
}
else
{
CString sDesc;
sDesc.Format(_T("Unable to open solution file %s"), m_sSolutionFilePathName);
cout << CStringA(sDesc).GetString() << endl;
return false;
}
}
return false;
}
bool CSolutionLintAnalyser::SetLintFolder(const CString& sFolder)
{
m_sLintFolder = sFolder;
ATLASSERT(!m_sLintFolder.IsEmpty() );
return !m_sLintFolder.IsEmpty();
}
bool CSolutionLintAnalyser::ShowHtml(const bool bShowHtml)
{
m_bShowHtml = bShowHtml;
return true;
}
int CSolutionLintAnalyser::GetWarningCount(void) const
{
return m_nWarningCount;
}
CString CSolutionLintAnalyser::GetResultsIndexFilePathName(void) const
{
return m_sResultsIndexFilePathName;
}
bool CSolutionLintAnalyser::Analyse(void)
{
ATLASSERT(!m_sSolutionFilePathName.IsEmpty() );
if (!m_sSolutionFilePathName.IsEmpty() )
{
CString sErrorMsg;
if (!Utils::FileExists(m_sSolutionFilePathName) )
{
return false;
}
if (!Utils::FolderExists(m_sResultsFileFolder) )
{
// Create target and intermediate folders as required
(void)::SHCreateDirectoryEx(NULL, m_sResultsFileFolder, NULL);
}
AddInSolutionModel::CSolutionFileReader parser;
const bool bReadOk = parser.Read(m_sSolutionFilePathName);
ATLASSERT(bReadOk);
if (bReadOk)
{
// Set solution specific environment variables
ATLVERIFY(SetEnvironmentVariables() );
m_timeStarted = COleDateTime::GetCurrentTime();
CAtlArray<CString> arrayProjectPathNames;
(void)parser.GetProjectFilePathNames(arrayProjectPathNames);
// Prescan to add all projects, so they show as "pending"
for (size_t n = 0; n < arrayProjectPathNames.GetCount(); n++)
{
const CString sProjectFilePathName = arrayProjectPathNames[n];
ATLASSERT(!sProjectFilePathName.IsEmpty() );
const CString sExt = Utils::CSplitPath(sProjectFilePathName).GetExtension();
const bool bIsCppProject = ( (_T(".vcproj") == sExt) ||
(_T(".dsp") == sExt) ||
(_T(".vcp") == sExt) );
if (bIsCppProject)
{
Utils::CSplitPath splitprj(sProjectFilePathName);
const CString sProjectName = splitprj.GetFileName();
// !!! Check if sProjectName is in sSkipProjectParams
CString sProjectNameLC = sProjectName;
sProjectNameLC.MakeLower(); // Couldn't find the case independent traits class for std::set...
if (m_setExcludedProjects.end() != m_setExcludedProjects.find(sProjectNameLC) )
{
CString sExcludedProjectMsg;
sExcludedProjectMsg.Format( _T("Excluding project %s"), sProjectName);
cout << endl << CStringA(sExcludedProjectMsg).GetString() << endl <<endl;
continue;
}
CString sProjectResultsFileFolder = m_sResultsFileFolder;
sProjectResultsFileFolder.TrimRight( _T("\\") );
sProjectResultsFileFolder += _T("\\") + sProjectName;
const CString sProjectConfig = parser.GetProjectConfigurationFromPathName(sProjectFilePathName, m_sConfiguration);
CProjectLintAnalyser prj( this,
sProjectFilePathName,
m_sResultsFileFolder,
sProjectResultsFileFolder,
m_sSolutionFileFolder, // [Alex McCarthy 21.10.2008]
m_sLintConfigFile,
m_sAdditionalProjectParams,
m_sAdditionalParams,
sProjectConfig);
ATLVERIFY(prj.SetLintFolder(m_sLintFolder) );
m_arrayProjectResults.Add(prj);
}
}
ATLVERIFY(WriteResultsIndexFile() );
if (m_bShowHtml)
{
// Open the output file in a browser window
(void)::ShellExecute( NULL,
_T("open"),
m_sResultsIndexFilePathName,
NULL,
NULL,
SW_SHOWNORMAL);
}
// Now do the analysis
for (size_t n = 0; n < m_arrayProjectResults.GetCount(); n++)
{
CProjectLintAnalyser& rprj = m_arrayProjectResults.GetAt(n);
if (rprj.Analyse() )
{
ATLVERIFY(WriteResultsIndexFile() );
}
}
// Reset solution specific environment variables
ATLVERIFY(ResetEnvironmentVariables() );
CString sSummary;
sSummary.Format( _T("%d issues for solution %s"),
m_nWarningCount,
m_sSolutionName);
cout << endl << CStringA(sSummary).GetString() << endl <<endl;
return true;
}
}
return false;
}
bool CSolutionLintAnalyser::WriteResultsIndexFile(void)
{
if (m_timeStarted.m_dt > 0.0)
{
COleDateTimeSpan timeElapsed(COleDateTime::GetCurrentTime() - m_timeStarted);
m_sElapsedTime = timeElapsed.Format( _T("%H hours, %M minutes, %S seconds") );
}
m_nWarningCount = 0;
for (size_t n = 0; n < m_arrayProjectResults.GetCount(); n++)
{
CProjectLintAnalyser& rprj = m_arrayProjectResults.GetAt(n);
m_nWarningCount+= rprj.GetWarningCount();
}
const CString sXslTemplate = Utils::LoadXsl(_Module.GetResourceInstance(), IDR_LINT_SOLUTION_TEMPLATE_HTML);
ATLASSERT(!sXslTemplate.IsEmpty() );
Utils::CSplitPath split(m_sResultsIndexFilePathName);
const CString sXmlResultsFilePathName = split.GetDrive() +
split.GetDirectory() +
split.GetFileName() + _T(".xml");
ATLVERIFY(WriteXml(sXmlResultsFilePathName) );
CString sErrorMsg;
const bool bResult = WriteReport(m_sResultsIndexFilePathName, sXslTemplate, &sErrorMsg);
if ( !sErrorMsg.IsEmpty() )
{
cout << CStringA(sErrorMsg).GetString() << endl;
}
(void)PostWriteResultsFile(m_sResultsIndexFilePathName);
return bResult;
}
/// \brief Called when the results HTML file has been updated
///
/// Ideally, when this happens any browser windows with the file open should be refreshed
///
bool CSolutionLintAnalyser::PostWriteResultsFile(const CString& sResultsIndexFilePathName)
{
ATLASSERT(Utils::FileExists(sResultsIndexFilePathName) );
if (m_bShowHtml && Utils::FileExists(sResultsIndexFilePathName) )
{
return Utils::RefreshAllOpenBrowserWindows(sResultsIndexFilePathName);
}
return false;
} //lint !e1961 (Elective Note -- virtual member function could be made const --- Eff. C++ 3rd Ed. item 3)
/// Write an XML Report stream to the given file
///
/// \param sPathName The pathname of the output file
/// \param psMsg A pointer to a CString object to return error information. May be NULL
/// \return true if the file was written successfully, false otherwise
///
bool CSolutionLintAnalyser::WriteXml(const CString& sPathName, CString* psMsg /*= NULL*/)
{
ATLASSERT(!sPathName.IsEmpty() );
bool bResult = false;
CString sErrorMsg;
if (!sPathName.IsEmpty() )
{
try
{
MSXML2::IXMLDOMDocumentPtr ptrDOMDoc;
// Create empty DOM
HRESULT hr = ptrDOMDoc.CreateInstance("Msxml2.DOMDocument.3.0");
if (FAILED(hr) )
{
sErrorMsg = _T("Failed to create XML DOM");
throw _com_error(hr);
}
// tell our shape to save its state as XML (into ptrDOMDoc)
hr = GenerateXml(ptrDOMDoc, &sErrorMsg);
if (FAILED(hr) )
{
sErrorMsg = _T("Failed to save XML stream - ") + sErrorMsg;
throw _com_error(hr);
}
try
{
const _variant_t varPathName = static_cast<LPCTSTR>(sPathName);
ATLVERIFY(SUCCEEDED(ptrDOMDoc->save(varPathName) ) );
bResult = true;
}
catch (const _com_error& e)
{
sErrorMsg = static_cast<LPCTSTR>(e.Description() );
}
}
catch (const _com_error& e)
{
sErrorMsg = static_cast<LPCTSTR>(e.Description() );
}
}
if (!bResult)
{
if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
{
*psMsg = sErrorMsg;
}
ATLTRACE("ERROR in CXmlReportGenerator::WriteXml(): %s\n", sErrorMsg);
}
return bResult;
} //lint !e1762
/// \brief Generate a Report and write it to the given file
///
/// \param sPathName The pathname of the output file
/// \param sXslStyleSheetText The text of the XSLT stylesheet
/// \param psMsg A pointer to a CString object to return error information. May be NULL
/// \return true if the file was written sucessfully, false otherwise
///
bool CSolutionLintAnalyser::WriteReport(const CString& sPathName,
const CString& sXslStyleSheetText,
CString* psMsg /*= NULL*/)
{
ATLASSERT(!sPathName.IsEmpty() );
bool bResult = false;
CString sErrorMsg;
if (!sPathName.IsEmpty() )
{
const CString sReportText = GenerateReportText( sXslStyleSheetText, &sErrorMsg);
if (!sReportText.IsEmpty() )
{
bResult = SUCCEEDED(Utils::WriteTextFile(sPathName, sReportText) );
}
}
if (!bResult)
{
if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
{
*psMsg = sErrorMsg;
}
ATLTRACE( _T("ERROR in CSolutionLintAnalyser::WriteReport(): %s\n"), sErrorMsg);
}
return bResult;
} //lint !e1762
CString CSolutionLintAnalyser::GenerateReportText( const CString& sXslStyleSheetText,
CString* psMsg /*= NULL*/) const
{
USES_CONVERSION;
CString sReportText;
ATLASSERT(!sXslStyleSheetText.IsEmpty() );
if (!sXslStyleSheetText.IsEmpty() )
{
CString sErrorMsg;
try
{
MSXML2::IXMLDOMDocumentPtr ptrDOMDoc;
MSXML2::IXMLDOMDocumentPtr ptrXSLTDOMDoc;
// Create empty DOM
HRESULT hr = ptrDOMDoc.CreateInstance("Msxml2.DOMDocument.3.0");
if (FAILED(hr) )
{
sErrorMsg = _T("Failed to create XML DOM");
throw _com_error(hr);
}
hr = ptrXSLTDOMDoc.CreateInstance("Msxml2.DOMDocument.3.0");
if (FAILED(hr) )
{
sErrorMsg = _T("Failed to create XSLT DOM");
throw _com_error(hr);
}
ptrXSLTDOMDoc->async = false;
ATLVERIFY(ptrXSLTDOMDoc->loadXML(T2COLE(sXslStyleSheetText) ) );
sReportText = GenerateReportText( ptrDOMDoc,
ptrXSLTDOMDoc,
psMsg);
// Workaround for XSLT bug: replace <cr><cr><lf> sequences with <cr><lf>
sReportText.Replace( _T("\r\n"), _T("\n") );
}
catch (const _com_error& e)
{
const HRESULT hr = e.Error();
ATLTRACE( _T("Unable to display report. Error code 0x%08x"), hr);
if (NULL != psMsg)
{
psMsg->Format( _T("CSolutionLintAnalyser::GenerateReportText()\nException 0x%08x"), hr);
}
}
}
return sReportText;
}
/// \brief Generate a report using the given XML DOM document and XSLT stylesheet
///
/// \param ptrDOMDoc An interface pointer to the XML DOM document containing the stream
/// \param pXslStyleSheetDoc An interface pointer to an XML DOM document containing the XSLT template
/// \param psMsg A pointer to a CString object to return error information. May be NULL
/// \return A Cstring object containing the text of the report
///
CString CSolutionLintAnalyser::GenerateReportText( const MSXML2::IXMLDOMDocumentPtr& ptrXmlDoc,
const MSXML2::IXMLDOMDocumentPtr& ptrXslStyleSheetDoc,
CString* psMsg /*= NULL*/) const
{
USES_CONVERSION;
CString sErrorMsg;
CString sReportText;
try
{
// Generate the XML in ptrDOMDoc
const HRESULT hr = GenerateXml(ptrXmlDoc, &sErrorMsg);
if (FAILED(hr) )
{
sErrorMsg = _T("Failed to generate XML stream - ") + sErrorMsg;
throw _com_error(hr);
}
ptrXslStyleSheetDoc->async = false;
// Perform the transformation
sReportText = OLE2CT(ptrXmlDoc->transformNode(ptrXslStyleSheetDoc) );
}
catch (const _com_error& e)
{
// Nothing to do here - we already know what's wrong
UNREFERENCED_PARAMETER(e);
}
if (sReportText.IsEmpty())
{
if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
{
*psMsg = sErrorMsg;
}
ATLTRACE( _T("ERROR in CSolutionLintAnalyser::GenerateReportText(): %s\n"), sErrorMsg);
}
return sReportText;
}
/// \brief Generate the XML stream
///
/// \param pDOMDoc An interface pointer to the XML DOM document containing the stream
/// \param psMsg A pointer to a CString object to return error information. May be NULL
/// \return A Cstring object containing the text of the report
///
HRESULT CSolutionLintAnalyser::GenerateXml( const MSXML2::IXMLDOMDocumentPtr& ptrDOMDoc,
CString* psMsg /*= NULL*/) const
{
USES_CONVERSION;
HRESULT hr = S_OK;
try
{
// Add xml version node
const CString sAttribs = _T("version=\"1.0\" encoding=\"iso-8859-1\"");
MSXML2::IXMLDOMProcessingInstructionPtr ptrIns;
ptrIns = ptrDOMDoc->createProcessingInstruction(L"xml", T2COLE(sAttribs) );
(void)ptrDOMDoc->appendChild(ptrIns);
// Create root element
MSXML2::IXMLDOMElementPtr const ptrRoot = ptrDOMDoc->createElement(L"SolutionLintResults");
(void)ptrDOMDoc->appendChild(ptrRoot);
Utils::CModuleVersion ver;
ATLVERIFY(ver.GetFileVersionInfo() );
const CString sVersion = ver.GetValue( _T("FileVersion") );
MSXML2::IXMLDOMElementPtr const ptrApplication = ptrDOMDoc->createElement(L"Application");
ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Name", PRODUCT_NAME) ) );
ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Version", T2COLE(sVersion) ) ) );
ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Url", PRODUCT_URL) ) );
(void)ptrRoot->appendChild(ptrApplication);
ATLVERIFY(SUCCEEDED(GenerateXml(ptrDOMDoc,
ptrRoot,
psMsg) ) );
}
catch (const _com_error& e)
{
hr = e.Error();
if (NULL != psMsg)
{
psMsg->Format( _T("CSolutionLintAnalyser::GenerateXml()\nException 0x%08x"), hr);
}
}
return hr;
}
HRESULT CSolutionLintAnalyser::GenerateXml( const MSXML2::IXMLDOMDocumentPtr& ptrDOMDoc,
const MSXML2::IXMLDOMElementPtr& ptrParentElement,
CString* psMsg /*= NULL*/) const
{
USES_CONVERSION;
HRESULT hr = S_OK;
try
{
MSXML2::IXMLDOMElementPtr const ptrSolution = ptrDOMDoc->createElement(L"Solution");
MSXML2::IXMLDOMAttributePtr const ptrSolutionNameAttr = ptrDOMDoc->createAttribute(L"name");
ptrSolutionNameAttr->value = T2COLE(m_sSolutionName);
(void)ptrSolution->setAttributeNode(ptrSolutionNameAttr);
MSXML2::IXMLDOMAttributePtr const ptrConfigurationAttr = ptrDOMDoc->createAttribute(L"configuration");
ptrConfigurationAttr->value = T2COLE(m_sConfiguration);
(void)ptrSolution->setAttributeNode(ptrConfigurationAttr);
(void)ptrParentElement->appendChild(ptrSolution);
MSXML2::IXMLDOMElementPtr const ptrProjects = ptrDOMDoc->createElement(L"Projects");
(void)ptrSolution->appendChild(ptrProjects);
for (size_t n = 0; n < m_arrayProjectResults.GetCount(); n++)
{
const CProjectLintAnalyser& project = m_arrayProjectResults[n];
if (project.GetImplementationFileCount() > 0)
{
ATLVERIFY(SUCCEEDED(project.GenerateXml(ptrDOMDoc,
ptrProjects,
psMsg) ) );
}
}
MSXML2::IXMLDOMElementPtr const ptrSummary = ptrDOMDoc->createElement(L"Summary");
(void)ptrSolution->appendChild(ptrSummary);
MSXML2::IXMLDOMElementPtr const ptrWarnings = ptrDOMDoc->createElement(L"Warnings");
ptrWarnings->text = T2COLE(Utils::IntToStr(m_nWarningCount) );
(void)ptrSummary->appendChild(ptrWarnings);
MSXML2::IXMLDOMElementPtr const ptrStartTime = ptrDOMDoc->createElement(L"StartTime");
const CString sStartTime = m_timeStarted.Format( _T("%a %d %b %Y, %H:%M:%S") );
ptrStartTime->text = T2COLE(sStartTime);
(void)ptrSummary->appendChild(ptrStartTime);
MSXML2::IXMLDOMElementPtr const ptrElapsedTime = ptrDOMDoc->createElement(L"ElapsedTime");
ptrElapsedTime->text = T2COLE(m_sElapsedTime);
(void)ptrSummary->appendChild(ptrElapsedTime);
}
catch (const _com_error& e)
{
hr = e.Error();
if (NULL != psMsg)
{
psMsg->Format( _T("Exception 0x%08x in CSolutionLintAnalyser::GenerateXml()\n"), hr);
}
}
return hr;
}
/// \brief Set solution specific environment variables.
///
/// \return \em true if everything worked; \em false otherwise.
///
bool CSolutionLintAnalyser::SetEnvironmentVariables(void)
{
return (FALSE != ::SetEnvironmentVariable( _T("SolutionDir"), m_sSolutionFileFolder) );
} //lint !e1762 (Information -- Member function could be made const --- Eff. C++ 3rd Ed. item 3)
/// \brief Reset solution specific environment variables.
///
/// \return \em true if everything worked; \em false otherwise.
///
bool CSolutionLintAnalyser::ResetEnvironmentVariables(void)
{
return (FALSE != ::SetEnvironmentVariable( _T("SolutionDir"), NULL) );
} //lint !e1762 (Information -- Member function could be made const --- Eff. C++ 3rd Ed. item 3)