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.
This commit is contained in:
Brad King 2008-06-02 16:44:58 -04:00
parent db59f49ecf
commit 6be09c3667
3 changed files with 164 additions and 7 deletions

View File

@ -27,9 +27,14 @@
#include "cmVersion.h" #include "cmVersion.h"
#include "cmExportInstallFileGenerator.h" #include "cmExportInstallFileGenerator.h"
#include "cmComputeTargetDepends.h" #include "cmComputeTargetDepends.h"
#include "cmGeneratedFileStream.h"
#include <cmsys/Directory.hxx> #include <cmsys/Directory.hxx>
#if defined(CMAKE_BUILD_WITH_CMAKE)
# include <cmsys/MD5.h>
#endif
#include <stdlib.h> // required for atof #include <stdlib.h> // required for atof
#include <assert.h> #include <assert.h>
@ -686,6 +691,7 @@ void cmGlobalGenerator::Configure()
this->TotalTargets.clear(); this->TotalTargets.clear();
this->LocalGeneratorToTargetMap.clear(); this->LocalGeneratorToTargetMap.clear();
this->ProjectMap.clear(); this->ProjectMap.clear();
this->RuleHashes.clear();
// start with this directory // start with this directory
cmLocalGenerator *lg = this->CreateLocalGenerator(); cmLocalGenerator *lg = this->CreateLocalGenerator();
@ -840,6 +846,9 @@ void cmGlobalGenerator::Generate()
} }
this->SetCurrentLocalGenerator(0); this->SetCurrentLocalGenerator(0);
// Update rule hashes.
this->CheckRuleHashes();
if (this->ExtraGenerator != 0) if (this->ExtraGenerator != 0)
{ {
this->ExtraGenerator->Generate(); this->ExtraGenerator->Generate();
@ -1931,3 +1940,143 @@ cmGlobalGenerator::GetDirectoryContent(std::string const& dir, bool needDisk)
return dc; 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
}

View File

@ -245,6 +245,10 @@ public:
void FileReplacedDuringGenerate(const std::string& filename); void FileReplacedDuringGenerate(const std::string& filename);
void GetFilesReplacedDuringGenerate(std::vector<std::string>& filenames); 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: protected:
// for a project collect all its targets by following depend // for a project collect all its targets by following depend
// information, and also collect all the targets // information, and also collect all the targets
@ -313,6 +317,11 @@ private:
// this is used to improve performance // this is used to improve performance
std::map<cmStdString,cmTarget *> TotalTargets; 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; cmExternalMakefileProjectGenerator* ExtraGenerator;
// track files replaced during a Generate // track files replaced during a Generate

View File

@ -1122,13 +1122,6 @@ void cmMakefileTargetGenerator
std::vector<std::string> depends; std::vector<std::string> depends;
this->LocalGenerator->AppendCustomDepend(depends, cc); 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. // Check whether we need to bother checking for a symbolic output.
bool need_symbolic = this->GlobalGenerator->GetNeedSymbolicMark(); bool need_symbolic = this->GlobalGenerator->GetNeedSymbolicMark();
@ -1147,6 +1140,12 @@ void cmMakefileTargetGenerator
this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, 0, this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, 0,
o->c_str(), depends, commands, o->c_str(), depends, commands,
symbolic); 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. // Write rules to drive building any outputs beyond the first.