Commit 195cdf17 authored by Brad King's avatar Brad King
Browse files

ENH: Split dependency scanning and checking into separate cmDepends superclass...

ENH: Split dependency scanning and checking into separate cmDepends superclass with language-specific subclasses such as cmDependsC.
parent ed9e0626
......@@ -46,6 +46,11 @@ SET(SRCS
cmake.h
cmakewizard.cxx
cmakewizard.h
cmDepends.h
cmDepends.cxx
cmDependsC.h
cmDependsC.cxx
)
......
/*=========================================================================
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 "cmDepends.h"
#include "cmGeneratedFileStream.h"
#include "cmSystemTools.h"
//----------------------------------------------------------------------------
cmDepends::cmDepends(const char* dir, const char* targetFile):
m_Directory(dir),
m_TargetFile(targetFile),
m_DependsMakeFile(dir),
m_DependsMarkFile(dir)
{
// Construct the path to the make and mark files. Append
// appropriate extensions to their names.
m_DependsMakeFile += "/";
m_DependsMarkFile += "/";
m_DependsMakeFile += m_TargetFile;
m_DependsMarkFile += m_TargetFile;
m_DependsMakeFile += ".depends.make";
m_DependsMarkFile += ".depends";
}
//----------------------------------------------------------------------------
cmDepends::~cmDepends()
{
}
//----------------------------------------------------------------------------
void cmDepends::Write()
{
// Try to generate dependencies for the target file.
cmGeneratedFileStream fout(m_DependsMakeFile.c_str());
fout << "# Dependencies for " << m_TargetFile.c_str() << std::endl;
if(this->WriteDependencies(fout) && fout)
{
// Dependencies were generated. Touch the mark file.
std::ofstream fmark(m_DependsMarkFile.c_str());
fmark << "Dependencies updated for " << m_TargetFile.c_str() << std::endl;
}
}
//----------------------------------------------------------------------------
void cmDepends::Check()
{
// Check whether dependencies must be regenerated.
std::ifstream fin(m_DependsMakeFile.c_str());
if(!(fin && this->CheckDependencies(fin)))
{
// Clear all dependencies so they will be regenerated.
this->Clear();
}
}
//----------------------------------------------------------------------------
void cmDepends::Clear()
{
// Remove the dependency mark file to be sure dependencies will be
// regenerated.
cmSystemTools::RemoveFile(m_DependsMarkFile.c_str());
// Write an empty dependency file.
cmGeneratedFileStream depFileStream(m_DependsMakeFile.c_str());
depFileStream
<< "# Empty dependencies file for " << m_TargetFile.c_str() << ".\n"
<< "# This may be replaced when dependencies are built." << std::endl;
}
//----------------------------------------------------------------------------
const char* cmDepends::GetMakeFileName()
{
// Skip over the directory part of the name.
return m_DependsMakeFile.c_str() + m_Directory.length() + 1;
}
//----------------------------------------------------------------------------
const char* cmDepends::GetMarkFileName()
{
// Skip over the directory part of the name.
return m_DependsMarkFile.c_str() + m_Directory.length() + 1;
}
/*=========================================================================
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 cmDepends_h
#define cmDepends_h
#include "cmStandardIncludes.h"
/** \class cmDepends
* \brief Dependency scanner superclass.
*
* This class is responsible for maintaining a .depends.make file in
* the build tree corresponding to an object file. Subclasses help it
* maintain dependencies for particular languages.
*/
class cmDepends
{
public:
/** Instances need to know the build directory name and the relative
path from the build directory to the target file. */
cmDepends(const char* dir, const char* targetFile);
/** Virtual destructor to cleanup subclasses properly. */
virtual ~cmDepends();
/** Write dependencies for the target file. */
void Write();
/** Check dependencies for the target file. */
void Check();
/** Clear dependencies for the target file so they will be regenerated. */
void Clear();
/** Get the name of the dependency make file. */
const char* GetMakeFileName();
/** Get the name of the dependency mark file. */
const char* GetMarkFileName();
protected:
// Write dependencies for the target file to the given stream.
// Return true for success and false for failure.
virtual bool WriteDependencies(std::ostream& os)=0;
// Check dependencies for the target file in the given stream.
// Return false if dependencies must be regenerated and true
// otherwise.
virtual bool CheckDependencies(std::istream& is)=0;
// The directory in which the build rule for the target file is executed.
std::string m_Directory;
// The name of the target file for which dependencies are maintained.
std::string m_TargetFile;
// The name of the .depends.make file corresponding to the target.
std::string m_DependsMakeFile;
// The name of the .depends file marking when dependencies were generated.
std::string m_DependsMarkFile;
private:
cmDepends(cmDepends const&); // Purposely not implemented.
void operator=(cmDepends const&); // Purposely not implemented.
};
#endif
/*=========================================================================
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 "cmDependsC.h"
#include "cmSystemTools.h"
//----------------------------------------------------------------------------
cmDependsC::cmDependsC(const char* dir, const char* targetFile):
cmDepends(dir, targetFile),
m_SourceFile(),
m_IncludePath(0),
m_IncludeLineRegex()
{
}
//----------------------------------------------------------------------------
cmDependsC::cmDependsC(const char* dir, const char* targetFile,
const char* sourceFile,
std::vector<std::string> const& includes):
cmDepends(dir, targetFile),
m_SourceFile(sourceFile),
m_IncludePath(&includes),
m_IncludeLineRegex("^[ \t]*#[ \t]*include[ \t]*[<\"]([^\">]+)[\">]")
{
}
//----------------------------------------------------------------------------
cmDependsC::~cmDependsC()
{
}
//----------------------------------------------------------------------------
bool cmDependsC::WriteDependencies(std::ostream& os)
{
// Make sure this is a scanning instance.
if(m_SourceFile == "")
{
cmSystemTools::Error("Cannot scan dependencies without an source file.");
return false;
}
if(!m_IncludePath)
{
cmSystemTools::Error("Cannot scan dependencies without an include path.");
return false;
}
// Walk the dependency graph starting with the source file.
bool first = true;
m_Unscanned.push(m_SourceFile);
m_Encountered.insert(m_SourceFile);
std::set<cmStdString> dependencies;
std::set<cmStdString> scanned;
while(!m_Unscanned.empty())
{
// Get the next file to scan.
std::string fname = m_Unscanned.front();
m_Unscanned.pop();
// If not a full path, find the file in the include path.
std::string fullName;
if(first || cmSystemTools::FileIsFullPath(fname.c_str()))
{
fullName = fname;
}
else
{
for(std::vector<std::string>::const_iterator i = m_IncludePath->begin();
i != m_IncludePath->end(); ++i)
{
std::string temp = *i;
temp += "/";
temp += fname;
if(cmSystemTools::FileExists(temp.c_str()))
{
fullName = temp;
break;
}
}
}
// Scan the file if it was found and has not been scanned already.
if(fullName.size() && (scanned.find(fullName) == scanned.end()))
{
// Record scanned files.
scanned.insert(fullName);
// Try to scan the file. Just leave it out if we cannot find
// it.
std::ifstream fin(fullName.c_str());
if(fin)
{
// Add this file as a dependency.
dependencies.insert(fullName);
// Scan this file for new dependencies.
this->Scan(fin);
}
}
first = false;
}
m_Encountered.clear();
// Write the dependencies to the output stream.
for(std::set<cmStdString>::iterator i=dependencies.begin();
i != dependencies.end(); ++i)
{
os << m_TargetFile.c_str() << ": "
<< cmSystemTools::ConvertToOutputPath(i->c_str()).c_str()
<< std::endl;
}
os << std::endl;
return true;
}
//----------------------------------------------------------------------------
bool cmDependsC::CheckDependencies(std::istream& is)
{
// Parse dependencies from the stream. If any dependee is missing
// or newer than the depender then dependencies should be
// regenerated.
bool okay = true;
std::string line;
std::string depender;
std::string dependee;
while(cmSystemTools::GetLineFromStream(is, line))
{
// Skip empty lines and comments.
std::string::size_type pos = line.find_first_not_of(" \t\r\n");
if(pos == std::string::npos || line[pos] == '#')
{
continue;
}
// Strip leading whitespace.
if(pos > 0)
{
line = line.substr(pos);
}
// Skip lines too short to have a dependency.
if(line.size() < 3)
{
continue;
}
// Find the colon on the line. Skip the first two characters to
// avoid finding the colon in a drive letter on Windows. Ignore
// the line if a colon cannot be found.
if((pos = line.find(':', 2)) == std::string::npos)
{
continue;
}
// Split the line into depender and dependee.
depender = line.substr(0, pos);
dependee = line.substr(pos+1);
// Strip whitespace from the dependee.
if((pos = dependee.find_first_not_of(" \t\r\n")) != std::string::npos &&
pos > 0)
{
dependee = dependee.substr(pos);
}
if((pos = dependee.find_last_not_of(" \t\r\n")) != std::string::npos)
{
dependee = dependee.substr(0, pos+1);
}
// Convert dependee to a full path.
if(!cmSystemTools::FileIsFullPath(dependee.c_str()))
{
dependee = cmSystemTools::CollapseFullPath(dependee.c_str(),
m_Directory.c_str());
}
// Strip whitespace from the depender.
if((pos = depender.find_last_not_of(" \t\r\n")) != std::string::npos)
{
depender = depender.substr(0, pos+1);
}
// Convert depender to a full path.
if(!cmSystemTools::FileIsFullPath(depender.c_str()))
{
depender = cmSystemTools::CollapseFullPath(depender.c_str(),
m_Directory.c_str());
}
// Dependencies must be regenerated if the dependee does not exist
// or if the depender exists and is older than the dependee.
int result = 0;
if(!cmSystemTools::FileExists(dependee.c_str()) ||
(cmSystemTools::FileExists(depender.c_str()) &&
(!cmSystemTools::FileTimeCompare(depender.c_str(), dependee.c_str(),
&result) || result < 0)))
{
// Dependencies must be regenerated.
okay = false;
// Remove the depender to be sure it is rebuilt.
cmSystemTools::RemoveFile(depender.c_str());
}
}
return okay;
}
//----------------------------------------------------------------------------
void cmDependsC::Scan(std::istream& is)
{
// Read one line at a time.
std::string line;
while(cmSystemTools::GetLineFromStream(is, line))
{
// Match include directives. TODO: Support include regex and
// ignore regex. Possibly also support directory-based inclusion
// in dependencies.
if(m_IncludeLineRegex.find(line.c_str()))
{
// Get the file being included.
std::string includeFile = m_IncludeLineRegex.match(1);
// Queue the file if it has not yet been encountered.
if(m_Encountered.find(includeFile) == m_Encountered.end())
{
m_Encountered.insert(includeFile);
m_Unscanned.push(includeFile);
}
}
}
}
/*=========================================================================
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 cmDependsC_h
#define cmDependsC_h
#include "cmDepends.h"
#include <cmsys/RegularExpression.hxx>
#include <queue>
/** \class cmDependsC
* \brief Dependency scanner for C and C++ object files.
*/
class cmDependsC: public cmDepends
{
public:
/** Checking instances need to know the build directory name and the
relative path from the build directory to the target file. */
cmDependsC(const char* dir, const char* targetFile);
/** Scanning need to know the build directory name, the relative
path from the build directory to the target file, the source
file from which to start scanning, and the include file search
path. */
cmDependsC(const char* dir, const char* targetFile,
const char* sourceFile, std::vector<std::string> const& includes);
/** Virtual destructor to cleanup subclasses properly. */
virtual ~cmDependsC();
protected:
// Implement writing/checking methods required by superclass.
virtual bool WriteDependencies(std::ostream& os);
virtual bool CheckDependencies(std::istream& is);
// Method to scan a single file.
void Scan(std::istream& is);
// The source file from which to start scanning.
std::string m_SourceFile;
// The include file search path.
std::vector<std::string> const* m_IncludePath;
// Regular expression to identify C preprocessor include directives.
cmsys::RegularExpression m_IncludeLineRegex;
// Data structures for dependency graph walk.
std::set<cmStdString> m_Encountered;
std::queue<cmStdString> m_Unscanned;
private:
cmDependsC(cmDependsC const&); // Purposely not implemented.
void operator=(cmDependsC const&); // Purposely not implemented.
};
#endif
......@@ -16,17 +16,20 @@
=========================================================================*/
#include "cmLocalUnixMakefileGenerator2.h"
#include "cmDepends.h"
#include "cmDependsC.h"
#include "cmGeneratedFileStream.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmSourceFile.h"
#include <memory> // auto_ptr
#include <queue>
#include <assert.h>
// Quick-switch for generating old makefiles.
#if 0
#if 1
# define CMLUMG_MAKEFILE_NAME "Makefile"
#else
# define CMLUMG_WRITE_OLD_MAKEFILE
......@@ -237,16 +240,33 @@ void cmLocalUnixMakefileGenerator2::GenerateCMakefile()
// Set the set of files to check for dependency integrity.
cmakefileStream
<< "# The set of files whose dependency integrity should be checked:\n"
<< "SET(CMAKE_DEPENDS_CHECK\n";
for(std::set<cmStdString>::const_iterator i = m_CheckDependFiles.begin();
i != m_CheckDependFiles.end(); ++i)
<< "# The set of files whose dependency integrity should be checked:\n";
cmakefileStream
<< "SET(CMAKE_DEPENDS_LANGUAGES\n";
for(std::map<cmStdString, IntegrityCheckSet>::const_iterator
l = m_CheckDependFiles.begin();
l != m_CheckDependFiles.end(); ++l)
{
cmakefileStream
<< " \"" << this->ConvertToRelativePath(i->c_str()).c_str() << "\"\n";
<< " \"" << l->first.c_str() << "\"\n";
}
cmakefileStream
<< " )\n";
for(std::map<cmStdString, IntegrityCheckSet>::const_iterator
l = m_CheckDependFiles.begin();
l != m_CheckDependFiles.end(); ++l)
{
cmakefileStream
<< "SET(CMAKE_DEPENDS_CHECK_" << l->first.c_str() << "\n";
for(std::set<cmStdString>::const_iterator i = l->second.begin();
i != l->second.end(); ++i)
{
cmakefileStream
<< " \"" << this->ConvertToRelativePath(i->c_str()).c_str() << "\"\n";
}
cmakefileStream
<< " )\n";
}
}
//----------------------------------------------------------------------------
......@@ -278,7 +298,6 @@ cmLocalUnixMakefileGenerator2
std::string depBase = dir;
depBase += "/";
depBase += target.GetName();
std::string depMakeFile = this->GenerateDependsMakeFile(depBase.c_str());
// Construct the rule file name.
std::string ruleFileName = dir;
......@@ -302,13 +321,6 @@ cmLocalUnixMakefileGenerator2
ruleFileStream
<< "# Rule file for target " << target.GetName() << ".\n\n";
// Include the dependencies for the target.
ruleFileStream
<< "# Include any dependencies generated for this rule.\n"
<< m_IncludeDirective << " "
<< this->ConvertToOutputForExisting(depMakeFile.c_str()).c_str()
<< "\n\n";
// Include the rule file for each object.
if(!objects.empty())
{
......@@ -371,19 +383,27 @@ cmLocalUnixMakefileGenerator2
// Get the full path name of the object file.
std::string obj = this->GetObjectFileName(target, source);
// Save this in the target's list of object files.
objects.push_back(obj);
// The object file should be checked for dependency integrity.
m_CheckDependFiles.insert(obj);
// Create the directory containing the object file. This may be a
// subdirectory under the target's directory.
std::string dir = cmSystemTools::GetFilenamePath(obj.c_str());
cmSystemTools::MakeDirectory(this->ConvertToFullPath(dir).c_str());
// Generate the build-time dependencies file for this object file.
std::string depMakeFile = this->GenerateDependsMakeFile(obj.c_str());
std::string depMakeFile;
std::string depMarkFile;
if(!this->GenerateDependsMakeFile(lang, obj.c_str(),
depMakeFile, depMarkFile))
{
cmSystemTools::Error("No dependency checker available for language \"",
lang, "\".");
return;
}
// Save this in the target's list of object files.
objects.push_back(obj);
// The object file should be checked for dependency integrity.
m_CheckDependFiles[lang].insert(obj);