From 195cdf172e2eecdb1b7fb3c2154cfe34c14afd9f Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 18 Jan 2005 17:09:05 -0500 Subject: [PATCH] ENH: Split dependency scanning and checking into separate cmDepends superclass with language-specific subclasses such as cmDependsC. --- Source/CMakeLists.txt | 5 + Source/cmDepends.cxx | 96 ++++++ Source/cmDepends.h | 82 +++++ Source/cmDependsC.cxx | 247 ++++++++++++++ Source/cmDependsC.h | 70 ++++ Source/cmLocalUnixMakefileGenerator2.cxx | 416 ++++++----------------- Source/cmLocalUnixMakefileGenerator2.h | 25 +- Source/cmake.cxx | 5 +- 8 files changed, 617 insertions(+), 329 deletions(-) create mode 100644 Source/cmDepends.cxx create mode 100644 Source/cmDepends.h create mode 100644 Source/cmDependsC.cxx create mode 100644 Source/cmDependsC.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ff93eb714..4451e12f0 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -46,6 +46,11 @@ SET(SRCS cmake.h cmakewizard.cxx cmakewizard.h + + cmDepends.h + cmDepends.cxx + cmDependsC.h + cmDependsC.cxx ) diff --git a/Source/cmDepends.cxx b/Source/cmDepends.cxx new file mode 100644 index 000000000..4933f5f21 --- /dev/null +++ b/Source/cmDepends.cxx @@ -0,0 +1,96 @@ +/*========================================================================= + + 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; +} diff --git a/Source/cmDepends.h b/Source/cmDepends.h new file mode 100644 index 000000000..00d2daf12 --- /dev/null +++ b/Source/cmDepends.h @@ -0,0 +1,82 @@ +/*========================================================================= + + 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 diff --git a/Source/cmDependsC.cxx b/Source/cmDependsC.cxx new file mode 100644 index 000000000..a57c282b5 --- /dev/null +++ b/Source/cmDependsC.cxx @@ -0,0 +1,247 @@ +/*========================================================================= + + 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 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 dependencies; + std::set 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::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::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); + } + } + } +} diff --git a/Source/cmDependsC.h b/Source/cmDependsC.h new file mode 100644 index 000000000..70691b7ab --- /dev/null +++ b/Source/cmDependsC.h @@ -0,0 +1,70 @@ +/*========================================================================= + + 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 +#include + +/** \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 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 const* m_IncludePath; + + // Regular expression to identify C preprocessor include directives. + cmsys::RegularExpression m_IncludeLineRegex; + + // Data structures for dependency graph walk. + std::set m_Encountered; + std::queue m_Unscanned; + +private: + cmDependsC(cmDependsC const&); // Purposely not implemented. + void operator=(cmDependsC const&); // Purposely not implemented. +}; + +#endif diff --git a/Source/cmLocalUnixMakefileGenerator2.cxx b/Source/cmLocalUnixMakefileGenerator2.cxx index 73756f707..1fad7f56c 100644 --- a/Source/cmLocalUnixMakefileGenerator2.cxx +++ b/Source/cmLocalUnixMakefileGenerator2.cxx @@ -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 // auto_ptr #include #include // 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::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::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::const_iterator + l = m_CheckDependFiles.begin(); + l != m_CheckDependFiles.end(); ++l) + { + cmakefileStream + << "SET(CMAKE_DEPENDS_CHECK_" << l->first.c_str() << "\n"; + for(std::set::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); // Open the rule file for writing. This should be copy-if-different // because the rules may depend on this file itself. @@ -424,8 +444,6 @@ cmLocalUnixMakefileGenerator2 depends.push_back(ruleFileName); // Write the dependency generation rule. - std::string depTarget = obj; - depTarget += ".depends"; { std::string depEcho = "Scanning "; depEcho += lang; @@ -453,7 +471,7 @@ cmLocalUnixMakefileGenerator2 // Write the rule. this->WriteMakeRule(ruleFileStream, 0, depEcho.c_str(), - depTarget.c_str(), depends, commands); + depMarkFile.c_str(), depends, commands); } // Write the build rule. @@ -676,30 +694,28 @@ cmLocalUnixMakefileGenerator2 } //---------------------------------------------------------------------------- -std::string +bool cmLocalUnixMakefileGenerator2 -::GenerateDependsMakeFile(const char* file) +::GenerateDependsMakeFile(const std::string& lang, const char* objFile, + std::string& depMakeFile, std::string& depMarkFile) { - // Check if the build-time dependencies file exists. - std::string depMarkFile = file; - depMarkFile += ".depends"; - std::string depMakeFile = depMarkFile; - depMakeFile += ".make"; - std::string depMakeFileFull = this->ConvertToFullPath(depMakeFile); - if(cmSystemTools::FileExists(depMakeFileFull.c_str())) + // Construct a checker for the given language. + std::auto_ptr + checker(this->GetDependsChecker(lang, + m_Makefile->GetStartOutputDirectory(), + objFile)); + if(checker.get()) { - // The build-time dependencies file already exists. Check it. - this->CheckDependencies(m_Makefile->GetStartOutputDirectory(), file); + // Save the make and mark file names. + depMakeFile = checker->GetMakeFileName(); + depMarkFile = checker->GetMarkFileName(); + + // Check the dependencies. + checker->Check(); + + return true; } - else - { - // The build-time dependencies file does not exist. Create an - // empty one. - std::string depMarkFileFull = this->ConvertToFullPath(depMarkFile); - this->WriteEmptyDependMakeFile(file, depMarkFileFull.c_str(), - depMakeFileFull.c_str()); - } - return depMakeFile; + return false; } //---------------------------------------------------------------------------- @@ -2686,6 +2702,19 @@ cmLocalUnixMakefileGenerator2 } } +//---------------------------------------------------------------------------- +cmDepends* +cmLocalUnixMakefileGenerator2::GetDependsChecker(const std::string& lang, + const char* dir, + const char* objFile) +{ + if(lang == "C" || lang == "CXX" || lang == "RC") + { + return new cmDependsC(dir, objFile); + } + return 0; +} + //---------------------------------------------------------------------------- bool cmLocalUnixMakefileGenerator2 @@ -2718,292 +2747,49 @@ cmLocalUnixMakefileGenerator2 if(lang == "C" || lang == "CXX" || lang == "RC") { // TODO: Handle RC (resource files) dependencies correctly. - return cmLocalUnixMakefileGenerator2::ScanDependenciesC(objFile, srcFile, - includes); + cmDependsC scanner(".", objFile, srcFile, includes); + scanner.Write(); + return true; } return false; } //---------------------------------------------------------------------------- -void -cmLocalUnixMakefileGenerator2ScanDependenciesC( - std::ifstream& fin, - std::set& encountered, - std::queue& unscanned) +void cmLocalUnixMakefileGenerator2::CheckDependencies(cmMakefile* mf) { - // Regular expression to identify C preprocessor include directives. - cmsys::RegularExpression - includeLine("^[ \t]*#[ \t]*include[ \t]*[<\"]([^\">]+)[\">]"); - - // Read one line at a time. - std::string line; - while(cmSystemTools::GetLineFromStream(fin, line)) + // Get the list of languages that may have sources to check. + const char* langDef = mf->GetDefinition("CMAKE_DEPENDS_LANGUAGES"); + if(!langDef) { - // Match include directives. TODO: Support include regex and - // ignore regex. Possibly also support directory-based inclusion - // in dependencies. - if(includeLine.find(line.c_str())) - { - // Get the file being included. - std::string includeFile = includeLine.match(1); - - // Queue the file if it has not yet been encountered. - if(encountered.find(includeFile) == encountered.end()) - { - encountered.insert(includeFile); - unscanned.push(includeFile); - } - } + return; } -} + std::vector languages; + cmSystemTools::ExpandListArgument(langDef, languages); -//---------------------------------------------------------------------------- -bool -cmLocalUnixMakefileGenerator2 -::ScanDependenciesC(const char* objFile, const char* srcFile, - std::vector const& includes) -{ - // Walk the dependency graph starting with the source file. - std::set dependencies; - std::set encountered; - std::set scanned; - std::queue unscanned; - unscanned.push(srcFile); - encountered.insert(srcFile); - while(!unscanned.empty()) + // For each language get the set of files to check. + for(std::vector::iterator l = languages.begin(); + l != languages.end(); ++l) { - // Get the next file to scan. - std::string fname = unscanned.front(); - unscanned.pop(); - - // If not a full path, find the file in the include path. - std::string fullName; - if(cmSystemTools::FileIsFullPath(fname.c_str())) + std::string depCheck = "CMAKE_DEPENDS_CHECK_"; + depCheck += *l; + if(const char* fileDef = mf->GetDefinition(depCheck.c_str())) { - fullName = fname; - } - else - { - for(std::vector::const_iterator i = includes.begin(); - i != includes.end(); ++i) + // Check each file. The current working directory is already + // correct. + std::vector files; + cmSystemTools::ExpandListArgument(fileDef, files); + for(std::vector::iterator f = files.begin(); + f != files.end(); ++f) { - std::string temp = *i; - temp += "/"; - temp += fname; - if(cmSystemTools::FileExists(temp.c_str())) + // Construct a checker for the given language. + std::auto_ptr + checker(cmLocalUnixMakefileGenerator2 + ::GetDependsChecker(*l, ".", f->c_str())); + if(checker.get()) { - fullName = temp; - break; + checker->Check(); } } } - - // 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. - cmLocalUnixMakefileGenerator2ScanDependenciesC(fin, encountered, - unscanned); - } - } - } - - // Write the dependencies to the output file. - std::string depMarkFile = objFile; - std::string depMakeFile = objFile; - depMarkFile += ".depends"; - depMakeFile += ".depends.make"; - cmGeneratedFileStream fout(depMakeFile.c_str()); - fout << "# Dependencies for " << objFile << std::endl; - for(std::set::iterator i=dependencies.begin(); - i != dependencies.end(); ++i) - { - fout << objFile << ": " - << cmSystemTools::ConvertToOutputPath(i->c_str()).c_str() - << std::endl; - } - fout << std::endl; - fout << "# Dependencies for " << depMarkFile.c_str() << std::endl; - for(std::set::iterator i=dependencies.begin(); - i != dependencies.end(); ++i) - { - fout << depMarkFile.c_str() << ": " - << cmSystemTools::ConvertToOutputPath(i->c_str()).c_str() - << std::endl; - } - - // If we could write the dependencies, touch the corresponding - // depends file to mark dependencies up to date. - if(fout) - { - std::ofstream fmark(depMarkFile.c_str()); - fmark << "Dependencies updated for " << objFile << "\n"; - } - - return true; -} - -//---------------------------------------------------------------------------- -void -cmLocalUnixMakefileGenerator2 -::CheckDependencies(const char* depCheck) -{ - // Get the list of files to scan. This is given through the command - // line hook cmake file. - std::vector files; - cmSystemTools::ExpandListArgument(depCheck, files); - - // Check each file. The current working directory is already - // correct. - for(std::vector::iterator f = files.begin(); - f != files.end(); ++f) - { - cmLocalUnixMakefileGenerator2::CheckDependencies(".", f->c_str()); } } - -//---------------------------------------------------------------------------- -void -cmLocalUnixMakefileGenerator2 -::CheckDependencies(const char* dir, const char* file) -{ - // Check the dependencies associated with the given file whose path - // is specified relative to the given directory. If any dependency - // is missing then dependencies should be regenerated. - bool regenerate = false; - - // Construct the names of the mark and make files. - std::string depMarkFileFull = dir; - depMarkFileFull += "/"; - depMarkFileFull += file; - depMarkFileFull += ".depends"; - std::string depMakeFileFull = depMarkFileFull; - depMakeFileFull += ".make"; - - // Open the dependency makefile. - std::ifstream fin(depMakeFileFull.c_str()); - if(fin) - { - // Parse dependencies. - std::string line; - std::string depender; - std::string dependee; - while(cmSystemTools::GetLineFromStream(fin, 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(), dir); - } - - // If the dependee does not exist, we need to regenerate - // dependencies and the depender should be removed. - if(!cmSystemTools::FileExists(dependee.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(), dir); - } - - // Remove the depender. - cmSystemTools::RemoveFile(depender.c_str()); - - // Mark the need for regeneration. - regenerate = true; - } - } - } - else - { - // Could not open the dependencies file. It needs to be - // regenerated. - regenerate = true; - } - - // If the dependencies file needs to be regenerated, create an empty - // one and delete the mark file. - if(regenerate) - { - cmLocalUnixMakefileGenerator2 - ::WriteEmptyDependMakeFile(file, depMarkFileFull.c_str(), - depMakeFileFull.c_str()); - } -} - -//---------------------------------------------------------------------------- -void -cmLocalUnixMakefileGenerator2 -::WriteEmptyDependMakeFile(const char* file, - const char* depMarkFileFull, - const char* depMakeFileFull) -{ - // Remove the dependency mark file to be sure dependencies will be - // regenerated. - cmSystemTools::RemoveFile(depMarkFileFull); - - // Write an empty dependency file. - cmGeneratedFileStream depFileStream(depMakeFileFull); - depFileStream - << "# Empty dependencies file for " << file << ".\n" - << "# This may be replaced when dependencies are built.\n"; -} diff --git a/Source/cmLocalUnixMakefileGenerator2.h b/Source/cmLocalUnixMakefileGenerator2.h index b86733970..5597f6616 100644 --- a/Source/cmLocalUnixMakefileGenerator2.h +++ b/Source/cmLocalUnixMakefileGenerator2.h @@ -21,6 +21,7 @@ class cmCustomCommand; class cmDependInformation; +class cmDepends; class cmMakeDepend; class cmTarget; class cmSourceFile; @@ -57,7 +58,7 @@ public: static bool ScanDependencies(std::vector const& args); /** Called from command-line hook to check dependencies. */ - static void CheckDependencies(const char* depCheck); + static void CheckDependencies(cmMakefile* mf); protected: @@ -69,7 +70,10 @@ protected: std::vector& objects); void GenerateCustomRuleFile(const cmCustomCommand& cc); void GenerateUtilityRuleFile(const cmTarget& target); - std::string GenerateDependsMakeFile(const char* file); + bool GenerateDependsMakeFile(const std::string& lang, + const char* objFile, + std::string& depMakeFile, + std::string& depMarkFile); void WriteMakeRule(std::ostream& os, const char* comment, const char* preEcho, @@ -163,12 +167,10 @@ protected: std::string GetRecursiveMakeCall(const char* tgt); void WriteJumpAndBuildRules(std::ostream& makefileStream); - static bool ScanDependenciesC(const char* objFile, const char* srcFile, - std::vector const& includes); - static void CheckDependencies(const char* dir, const char* file); - static void WriteEmptyDependMakeFile(const char* file, - const char* depMarkFileFull, - const char* depMakeFileFull); + static cmDepends* GetDependsChecker(const std::string& lang, + const char* dir, + const char* objFile); + private: // Map from target name to build directory containing it for // jump-and-build targets. @@ -179,8 +181,11 @@ private: }; std::map m_JumpAndBuild; - // List the files for which to check dependency integrity. - std::set m_CheckDependFiles; + // List the files for which to check dependency integrity. Each + // language has its own list because integrity may be checked + // differently. + struct IntegrityCheckSet: public std::set {}; + std::map m_CheckDependFiles; // Command used when a rule has no dependencies or commands. std::vector m_EmptyCommands; diff --git a/Source/cmake.cxx b/Source/cmake.cxx index a7e978128..9c319f14c 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1635,10 +1635,7 @@ int cmake::CheckBuildSystem() #if defined(CMAKE_BUILD_WITH_CMAKE) // We do not need to rerun CMake. Check dependency integrity. - if(const char* depCheck = mf->GetDefinition("CMAKE_DEPENDS_CHECK")) - { - cmLocalUnixMakefileGenerator2::CheckDependencies(depCheck); - } + cmLocalUnixMakefileGenerator2::CheckDependencies(mf); #endif // No need to rerun.