From 6be09c366774ed6d723a06f5f07ba5c09d8e4579 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 2 Jun 2008 16:44:58 -0400 Subject: [PATCH] ENH: Introduce "rule hashes" to help rebuild files when rules change. - In CMake 2.4 custom commands would not rebuild when rules changed. - In CMake 2.6.0 custom commands have a dependency on build.make which causes them to rebuild when changed, but also when any source is added or removed. This is too often. - We cannot have a per-rule file because Windows filesystems do not deal well with lots of small files. - Instead we add a persistent CMakeFiles/CMakeRuleHashes.txt file at the top of the build tree that is updated during each CMake Generate step. It records a hash of the build rule for each file to be built. When the hash changes the file is removed so that it will be rebuilt. --- Source/cmGlobalGenerator.cxx | 149 +++++++++++++++++++++++++++ Source/cmGlobalGenerator.h | 9 ++ Source/cmMakefileTargetGenerator.cxx | 13 ++- 3 files changed, 164 insertions(+), 7 deletions(-) diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 33a7f208f..b97129813 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -27,9 +27,14 @@ #include "cmVersion.h" #include "cmExportInstallFileGenerator.h" #include "cmComputeTargetDepends.h" +#include "cmGeneratedFileStream.h" #include +#if defined(CMAKE_BUILD_WITH_CMAKE) +# include +#endif + #include // required for atof #include @@ -686,6 +691,7 @@ void cmGlobalGenerator::Configure() this->TotalTargets.clear(); this->LocalGeneratorToTargetMap.clear(); this->ProjectMap.clear(); + this->RuleHashes.clear(); // start with this directory cmLocalGenerator *lg = this->CreateLocalGenerator(); @@ -840,6 +846,9 @@ void cmGlobalGenerator::Generate() } this->SetCurrentLocalGenerator(0); + // Update rule hashes. + this->CheckRuleHashes(); + if (this->ExtraGenerator != 0) { this->ExtraGenerator->Generate(); @@ -1931,3 +1940,143 @@ cmGlobalGenerator::GetDirectoryContent(std::string const& dir, bool needDisk) return dc; } +//---------------------------------------------------------------------------- +void +cmGlobalGenerator::AddRuleHash(const std::vector& outputs, + const std::vector& depends, + const std::vector& commands) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + // Ignore if there are no outputs. + if(outputs.empty()) + { + return; + } + + // Compute a hash of the rule. + RuleHash hash; + { + unsigned char const* data; + int length; + cmsysMD5* sum = cmsysMD5_New(); + cmsysMD5_Initialize(sum); + for(std::vector::const_iterator i = outputs.begin(); + i != outputs.end(); ++i) + { + data = reinterpret_cast(i->c_str()); + length = static_cast(i->length()); + cmsysMD5_Append(sum, data, length); + } + for(std::vector::const_iterator i = depends.begin(); + i != depends.end(); ++i) + { + data = reinterpret_cast(i->c_str()); + length = static_cast(i->length()); + cmsysMD5_Append(sum, data, length); + } + for(std::vector::const_iterator i = commands.begin(); + i != commands.end(); ++i) + { + data = reinterpret_cast(i->c_str()); + length = static_cast(i->length()); + cmsysMD5_Append(sum, data, length); + } + cmsysMD5_FinalizeHex(sum, hash.Data); + cmsysMD5_Delete(sum); + } + + // Shorten the output name (in expected use case). + cmLocalGenerator* lg = this->GetLocalGenerators()[0]; + std::string fname = lg->Convert(outputs[0].c_str(), + cmLocalGenerator::HOME_OUTPUT); + + // Associate the hash with this output. + this->RuleHashes[fname] = hash; +#else + (void)outputs; + (void)depends; + (void)commands; +#endif +} + +//---------------------------------------------------------------------------- +void cmGlobalGenerator::CheckRuleHashes() +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + std::string home = this->GetCMakeInstance()->GetHomeOutputDirectory(); + std::string pfile = home; + pfile += this->GetCMakeInstance()->GetCMakeFilesDirectory(); + pfile += "/CMakeRuleHashes.txt"; + +#if defined(_WIN32) || defined(__CYGWIN__) + std::ifstream fin(pfile.c_str(), std::ios::in | std::ios::binary); +#else + std::ifstream fin(pfile.c_str(), std::ios::in); +#endif + std::string line; + std::string fname; + while(cmSystemTools::GetLineFromStream(fin, line)) + { + // Line format is a 32-byte hex string followed by a space + // followed by a file name (with no escaping). + + // Skip blank and comment lines. + if(line.size() < 34 || line[0] == '#') + { + continue; + } + + // Get the filename. + fname = line.substr(33, line.npos); + + // Look for a hash for this file's rule. + std::map::const_iterator rhi = + this->RuleHashes.find(fname); + if(rhi != this->RuleHashes.end()) + { + // Compare the rule hash in the file to that we were given. + if(strncmp(line.c_str(), rhi->second.Data, 32) != 0) + { + // The rule has changed. Delete the output so it will be + // built again. + fname = cmSystemTools::CollapseFullPath(fname.c_str(), home.c_str()); + cmSystemTools::RemoveFile(fname.c_str()); + } + } + else + { + // We have no hash for a rule previously listed. This may be a + // case where a user has turned off a build option and might + // want to turn it back on later, so do not delete the file. + // Instead, we keep the rule hash as long as the file exists so + // that if the feature is turned back on and the rule has + // changed the file is still rebuilt. + std::string fpath = + cmSystemTools::CollapseFullPath(fname.c_str(), home.c_str()); + if(cmSystemTools::FileExists(fpath.c_str())) + { + RuleHash hash; + strncpy(hash.Data, line.c_str(), 32); + this->RuleHashes[fname] = hash; + } + } + } + + // Now generate a new persistence file with the current hashes. + if(this->RuleHashes.empty()) + { + cmSystemTools::RemoveFile(pfile.c_str()); + } + else + { + cmGeneratedFileStream fout(pfile.c_str()); + fout << "# Hashes of file build rules.\n"; + for(std::map::const_iterator + rhi = this->RuleHashes.begin(); rhi != this->RuleHashes.end(); ++rhi) + { + fout.write(rhi->second.Data, 32); + fout << " " << rhi->first << "\n"; + } + } +#endif +} diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 519158fff..ae9a46dbb 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -245,6 +245,10 @@ public: void FileReplacedDuringGenerate(const std::string& filename); void GetFilesReplacedDuringGenerate(std::vector& filenames); + void AddRuleHash(const std::vector& outputs, + const std::vector& depends, + const std::vector& commands); + protected: // for a project collect all its targets by following depend // information, and also collect all the targets @@ -313,6 +317,11 @@ private: // this is used to improve performance std::map TotalTargets; + // Record hashes for rules and outputs. + struct RuleHash { char Data[32]; }; + std::map RuleHashes; + void CheckRuleHashes(); + cmExternalMakefileProjectGenerator* ExtraGenerator; // track files replaced during a Generate diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 5e724d458..ea0d9b83a 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -1122,13 +1122,6 @@ void cmMakefileTargetGenerator std::vector depends; this->LocalGenerator->AppendCustomDepend(depends, cc); - // Add a dependency on the rule file itself. - if(!cc.GetSkipRuleDepends()) - { - this->LocalGenerator->AppendRuleDepend(depends, - this->BuildFileNameFull.c_str()); - } - // Check whether we need to bother checking for a symbolic output. bool need_symbolic = this->GlobalGenerator->GetNeedSymbolicMark(); @@ -1147,6 +1140,12 @@ void cmMakefileTargetGenerator this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, 0, o->c_str(), depends, commands, symbolic); + + // If the rule has changed make sure the output is rebuilt. + if(!symbolic) + { + this->GlobalGenerator->AddRuleHash(cc.GetOutputs(), depends, commands); + } } // Write rules to drive building any outputs beyond the first.