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:
parent
db59f49ecf
commit
6be09c3667
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue