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 "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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue