summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2008-06-02 20:44:58 (GMT)
committerBrad King <brad.king@kitware.com>2008-06-02 20:44:58 (GMT)
commit6be09c366774ed6d723a06f5f07ba5c09d8e4579 (patch)
tree55aef0c5ad0c7b582b76494f5db1b5e291c2e76a
parentdb59f49ecf6767e66d9edd12194bcd60804dc8a3 (diff)
downloadCMake-6be09c366774ed6d723a06f5f07ba5c09d8e4579.zip
CMake-6be09c366774ed6d723a06f5f07ba5c09d8e4579.tar.gz
CMake-6be09c366774ed6d723a06f5f07ba5c09d8e4579.tar.bz2
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.
-rw-r--r--Source/cmGlobalGenerator.cxx149
-rw-r--r--Source/cmGlobalGenerator.h9
-rw-r--r--Source/cmMakefileTargetGenerator.cxx13
3 files changed, 164 insertions, 7 deletions
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 33a7f20..b971298 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 <cmsys/Directory.hxx>
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+# include <cmsys/MD5.h>
+#endif
+
#include <stdlib.h> // required for atof
#include <assert.h>
@@ -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<std::string>& outputs,
+ const std::vector<std::string>& depends,
+ const std::vector<std::string>& 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<std::string>::const_iterator i = outputs.begin();
+ i != outputs.end(); ++i)
+ {
+ data = reinterpret_cast<unsigned char const*>(i->c_str());
+ length = static_cast<int>(i->length());
+ cmsysMD5_Append(sum, data, length);
+ }
+ for(std::vector<std::string>::const_iterator i = depends.begin();
+ i != depends.end(); ++i)
+ {
+ data = reinterpret_cast<unsigned char const*>(i->c_str());
+ length = static_cast<int>(i->length());
+ cmsysMD5_Append(sum, data, length);
+ }
+ for(std::vector<std::string>::const_iterator i = commands.begin();
+ i != commands.end(); ++i)
+ {
+ data = reinterpret_cast<unsigned char const*>(i->c_str());
+ length = static_cast<int>(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<cmStdString, RuleHash>::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<cmStdString, RuleHash>::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 519158f..ae9a46d 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -245,6 +245,10 @@ public:
void FileReplacedDuringGenerate(const std::string& filename);
void GetFilesReplacedDuringGenerate(std::vector<std::string>& filenames);
+ void AddRuleHash(const std::vector<std::string>& outputs,
+ const std::vector<std::string>& depends,
+ const std::vector<std::string>& 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<cmStdString,cmTarget *> TotalTargets;
+ // Record hashes for rules and outputs.
+ struct RuleHash { char Data[32]; };
+ std::map<cmStdString, RuleHash> RuleHashes;
+ void CheckRuleHashes();
+
cmExternalMakefileProjectGenerator* ExtraGenerator;
// track files replaced during a Generate
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 5e724d4..ea0d9b8 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -1122,13 +1122,6 @@ void cmMakefileTargetGenerator
std::vector<std::string> 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.