1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-06-20 03:38:05 +08:00
CMake/Source/cmQtAutoMocUic.cxx
Sebastian Holtermann b5ad572ac1 Autogen: Deprecation message for CMAKE_AUTOMOC_RELAXED_MODE
`CMAKE_AUTOMOC_RELAXED_MODE` was added for backwards compatibility with KDE 4,
which had its last release in 2014.  It does not offer additional features
but complicates the `AUTOMOC` code and dependency computation considerably.

Projects that use `CMAKE_AUTOMOC_RELAXED_MODE` functionality always got
extensive warnings during builds and tips on how to convert to regular mode,
which is trivial (see commit e474dcb231, CMake 2.8.7).

It's time to consider this feature deprecated and issue a warning at
configuration time as well.

This adds a configuration time deprecation `AUTHOR_WARNING` for
`CMAKE_AUTOMOC_RELAXED_MODE`.
2019-05-27 13:06:01 +02:00

2194 lines
67 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoMocUic.h"
#include <algorithm>
#include <array>
#include <list>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmGeneratedFileStream.h"
#include "cmMakefile.h"
#include "cmQtAutoGen.h"
#include "cmSystemTools.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
#if defined(__APPLE__)
# include <unistd.h>
#endif
static constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_"
static constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_"
cmQtAutoMocUic::IncludeKeyT::IncludeKeyT(std::string const& key,
std::size_t basePrefixLength)
: Key(key)
, Dir(SubDirPrefix(key))
, Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
{
if (basePrefixLength != 0) {
Base = Base.substr(basePrefixLength);
}
}
void cmQtAutoMocUic::ParseCacheT::FileT::Clear()
{
Moc.Macro.clear();
Moc.Include.Underscore.clear();
Moc.Include.Dot.clear();
Moc.Depends.clear();
Uic.Include.clear();
Uic.Depends.clear();
}
cmQtAutoMocUic::ParseCacheT::FileHandleT cmQtAutoMocUic::ParseCacheT::Get(
std::string const& fileName) const
{
auto it = Map_.find(fileName);
if (it != Map_.end()) {
return it->second;
}
return FileHandleT();
}
cmQtAutoMocUic::ParseCacheT::GetOrInsertT
cmQtAutoMocUic::ParseCacheT::GetOrInsert(std::string const& fileName)
{
// Find existing entry
{
auto it = Map_.find(fileName);
if (it != Map_.end()) {
return GetOrInsertT{ it->second, false };
}
}
// Insert new entry
return GetOrInsertT{
Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
};
}
cmQtAutoMocUic::ParseCacheT::ParseCacheT() = default;
cmQtAutoMocUic::ParseCacheT::~ParseCacheT() = default;
void cmQtAutoMocUic::ParseCacheT::Clear()
{
Map_.clear();
}
bool cmQtAutoMocUic::ParseCacheT::ReadFromFile(std::string const& fileName)
{
cmsys::ifstream fin(fileName.c_str());
if (!fin) {
return false;
}
FileHandleT fileHandle;
std::string line;
while (std::getline(fin, line)) {
// Check if this an empty or a comment line
if (line.empty() || line.front() == '#') {
continue;
}
// Drop carriage return character at the end
if (line.back() == '\r') {
line.pop_back();
if (line.empty()) {
continue;
}
}
// Check if this a file name line
if (line.front() != ' ') {
fileHandle = GetOrInsert(line).first;
continue;
}
// Bad line or bad file handle
if (!fileHandle || (line.size() < 6)) {
continue;
}
constexpr std::size_t offset = 5;
if (cmHasLiteralPrefix(line, " mmc:")) {
fileHandle->Moc.Macro = line.substr(offset);
continue;
}
if (cmHasLiteralPrefix(line, " miu:")) {
fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset),
MocUnderscoreLength);
continue;
}
if (cmHasLiteralPrefix(line, " mid:")) {
fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0);
continue;
}
if (cmHasLiteralPrefix(line, " mdp:")) {
fileHandle->Moc.Depends.emplace_back(line.substr(offset));
continue;
}
if (cmHasLiteralPrefix(line, " uic:")) {
fileHandle->Uic.Include.emplace_back(line.substr(offset),
UiUnderscoreLength);
continue;
}
if (cmHasLiteralPrefix(line, " udp:")) {
fileHandle->Uic.Depends.emplace_back(line.substr(offset));
continue;
}
}
return true;
}
bool cmQtAutoMocUic::ParseCacheT::WriteToFile(std::string const& fileName)
{
cmGeneratedFileStream ofs(fileName);
if (!ofs) {
return false;
}
ofs << "# Generated by CMake. Changes will be overwritten." << std::endl;
for (auto const& pair : Map_) {
ofs << pair.first << std::endl;
FileT const& file = *pair.second;
if (!file.Moc.Macro.empty()) {
ofs << " mmc:" << file.Moc.Macro << std::endl;
}
for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
ofs << " miu:" << item.Key << std::endl;
}
for (IncludeKeyT const& item : file.Moc.Include.Dot) {
ofs << " mid:" << item.Key << std::endl;
}
for (std::string const& item : file.Moc.Depends) {
ofs << " mdp:" << item << std::endl;
}
for (IncludeKeyT const& item : file.Uic.Include) {
ofs << " uic:" << item.Key << std::endl;
}
for (std::string const& item : file.Uic.Depends) {
ofs << " udp:" << item << std::endl;
}
}
return ofs.Close();
}
cmQtAutoMocUic::BaseSettingsT::BaseSettingsT() = default;
cmQtAutoMocUic::BaseSettingsT::~BaseSettingsT() = default;
cmQtAutoMocUic::MocSettingsT::MocSettingsT()
{
RegExpInclude.compile(
"(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
}
cmQtAutoMocUic::MocSettingsT::~MocSettingsT() = default;
bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const
{
return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
}
std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const
{
std::string res;
const auto itB = MacroFilters.cbegin();
const auto itE = MacroFilters.cend();
const auto itL = itE - 1;
auto itC = itB;
for (; itC != itE; ++itC) {
// Separator
if (itC != itB) {
if (itC != itL) {
res += ", ";
} else {
res += " or ";
}
}
// Key
res += itC->Key;
}
return res;
}
cmQtAutoMocUic::UicSettingsT::UicSettingsT()
{
RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
}
cmQtAutoMocUic::UicSettingsT::~UicSettingsT() = default;
bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const
{
return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
}
void cmQtAutoMocUic::JobT::LogError(GenT genType,
std::string const& message) const
{
Gen()->AbortError();
Gen()->Log().Error(genType, message);
}
void cmQtAutoMocUic::JobT::LogFileError(GenT genType,
std::string const& filename,
std::string const& message) const
{
Gen()->AbortError();
Gen()->Log().ErrorFile(genType, filename, message);
}
void cmQtAutoMocUic::JobT::LogCommandError(
GenT genType, std::string const& message,
std::vector<std::string> const& command, std::string const& output) const
{
Gen()->AbortError();
Gen()->Log().ErrorCommand(genType, message, command, output);
}
bool cmQtAutoMocUic::JobT::RunProcess(GenT genType,
cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command,
std::string* infoMessage)
{
// Log command
if (Log().Verbose()) {
std::string msg;
if ((infoMessage != nullptr) && !infoMessage->empty()) {
msg = *infoMessage;
if (msg.back() != '\n') {
msg += '\n';
}
}
msg += QuotedCommand(command);
msg += '\n';
Log().Info(genType, msg);
}
return cmWorkerPool::JobT::RunProcess(result, command,
BaseConst().AutogenBuildDir);
}
void cmQtAutoMocUic::JobMocPredefsT::Process()
{
// (Re)generate moc_predefs.h on demand
std::unique_ptr<std::string> reason;
if (Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (!Update(reason.get())) {
return;
}
std::string const& predefsFileRel = MocConst().PredefsFileRel;
std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
{
cmWorkerPool::ProcessResultT result;
{
// Compose command
std::vector<std::string> cmd = MocConst().PredefsCmd;
// Add includes
cmAppend(cmd, MocConst().Includes);
// Add definitions
for (std::string const& def : MocConst().Definitions) {
cmd.emplace_back("-D" + def);
}
// Execute command
if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
std::string msg = "The content generation command for ";
msg += Quoted(predefsFileRel);
msg += " failed.\n";
msg += result.ErrorMessage;
LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
return;
}
}
// (Re)write predefs file only on demand
if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
std::string msg = "Writing ";
msg += Quoted(predefsFileRel);
msg += " failed.";
LogFileError(GenT::MOC, predefsFileAbs, msg);
return;
}
} else {
// Touch to update the time stamp
if (Log().Verbose()) {
Log().Info(GenT::MOC, "Touching " + Quoted(predefsFileRel));
}
if (!cmSystemTools::Touch(predefsFileAbs, false)) {
std::string msg = "Touching ";
msg += Quoted(predefsFileAbs);
msg += " failed.";
LogFileError(GenT::MOC, predefsFileAbs, msg);
return;
}
}
}
// Read file time afterwards
if (!MocEval().PredefsTime.Load(predefsFileAbs)) {
LogFileError(GenT::MOC, predefsFileAbs, "File time reading failed.");
return;
}
}
bool cmQtAutoMocUic::JobMocPredefsT::Update(std::string* reason) const
{
// Test if the file exists
if (!MocEval().PredefsTime.Load(MocConst().PredefsFileAbs)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(MocConst().PredefsFileRel);
*reason += ", because it doesn't exist.";
}
return true;
}
// Test if the settings changed
if (MocConst().SettingsChanged) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(MocConst().PredefsFileRel);
*reason += ", because the moc settings changed.";
}
return true;
}
// Test if the executable is newer
{
std::string const& exec = MocConst().PredefsCmd.at(0);
cmFileTime execTime;
if (execTime.Load(exec)) {
if (MocEval().PredefsTime.Older(execTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(MocConst().PredefsFileRel);
*reason += " because it is older than ";
*reason += Quoted(exec);
*reason += ".";
}
return true;
}
}
}
return false;
}
bool cmQtAutoMocUic::JobParseT::ReadFile()
{
// Clear old parse information
FileHandle->ParseData->Clear();
std::string const& fileName = FileHandle->FileName;
// Write info
if (Log().Verbose()) {
Log().Info(GenT::GEN, "Parsing " + Quoted(fileName));
}
// Read file content
{
std::string error;
if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) {
LogFileError(GenT::GEN, fileName, "Could not read the file: " + error);
return false;
}
}
// Warn if empty
if (Content.empty()) {
Log().WarningFile(GenT::GEN, fileName, "The file is empty.");
return false;
}
return true;
}
void cmQtAutoMocUic::JobParseT::CreateKeys(std::vector<IncludeKeyT>& container,
std::set<std::string> const& source,
std::size_t basePrefixLength)
{
if (source.empty()) {
return;
}
container.reserve(source.size());
for (std::string const& src : source) {
container.emplace_back(src, basePrefixLength);
}
}
void cmQtAutoMocUic::JobParseT::MocMacro()
{
for (KeyExpT const& filter : MocConst().MacroFilters) {
// Run a simple find string check
if (Content.find(filter.Key) == std::string::npos) {
continue;
}
// Run the expensive regular expression check loop
cmsys::RegularExpressionMatch match;
if (filter.Exp.find(Content.c_str(), match)) {
// Keep detected macro name
FileHandle->ParseData->Moc.Macro = filter.Key;
return;
}
}
}
void cmQtAutoMocUic::JobParseT::MocDependecies()
{
if (MocConst().DependFilters.empty()) {
return;
}
// Find dependency strings
std::set<std::string> parseDepends;
for (KeyExpT const& filter : MocConst().DependFilters) {
// Run a simple find string check
if (Content.find(filter.Key) == std::string::npos) {
continue;
}
// Run the expensive regular expression check loop
const char* contentChars = Content.c_str();
cmsys::RegularExpressionMatch match;
while (filter.Exp.find(contentChars, match)) {
{
std::string dep = match.match(1);
if (!dep.empty()) {
parseDepends.emplace(std::move(dep));
}
}
contentChars += match.end();
}
}
// Store dependency strings
{
auto& Depends = FileHandle->ParseData->Moc.Depends;
Depends.reserve(parseDepends.size());
for (std::string const& item : parseDepends) {
Depends.emplace_back(item);
// Replace end of line characters in filenames
std::string& path = Depends.back();
std::replace(path.begin(), path.end(), '\n', ' ');
std::replace(path.begin(), path.end(), '\r', ' ');
}
}
}
void cmQtAutoMocUic::JobParseT::MocIncludes()
{
if (Content.find("moc") == std::string::npos) {
return;
}
std::set<std::string> underscore;
std::set<std::string> dot;
{
const char* contentChars = Content.c_str();
cmsys::RegularExpression const& regExp = MocConst().RegExpInclude;
cmsys::RegularExpressionMatch match;
while (regExp.find(contentChars, match)) {
std::string incString = match.match(2);
std::string const incBase =
cmSystemTools::GetFilenameWithoutLastExtension(incString);
if (cmHasLiteralPrefix(incBase, "moc_")) {
// moc_<BASE>.cpp
// Remove the moc_ part from the base name
underscore.emplace(std::move(incString));
} else {
// <BASE>.moc
dot.emplace(std::move(incString));
}
// Forward content pointer
contentChars += match.end();
}
}
auto& Include = FileHandle->ParseData->Moc.Include;
CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
CreateKeys(Include.Dot, dot, 0);
}
void cmQtAutoMocUic::JobParseT::UicIncludes()
{
if (Content.find("ui_") == std::string::npos) {
return;
}
std::set<std::string> includes;
{
const char* contentChars = Content.c_str();
cmsys::RegularExpression const& regExp = UicConst().RegExpInclude;
cmsys::RegularExpressionMatch match;
while (regExp.find(contentChars, match)) {
includes.emplace(match.match(2));
// Forward content pointer
contentChars += match.end();
}
}
CreateKeys(FileHandle->ParseData->Uic.Include, includes, UiUnderscoreLength);
}
void cmQtAutoMocUic::JobParseHeaderT::Process()
{
if (!ReadFile()) {
return;
}
// Moc parsing
if (FileHandle->Moc) {
MocMacro();
MocDependecies();
}
// Uic parsing
if (FileHandle->Uic) {
UicIncludes();
}
}
void cmQtAutoMocUic::JobParseSourceT::Process()
{
if (!ReadFile()) {
return;
}
// Moc parsing
if (FileHandle->Moc) {
MocMacro();
MocDependecies();
MocIncludes();
}
// Uic parsing
if (FileHandle->Uic) {
UicIncludes();
}
}
void cmQtAutoMocUic::JobEvaluateT::Process()
{
// Evaluate for moc
if (MocConst().Enabled) {
// Evaluate headers
for (auto const& pair : BaseEval().Headers) {
if (!MocEvalHeader(pair.second)) {
return;
}
}
// Evaluate sources
for (auto const& pair : BaseEval().Sources) {
if (!MocEvalSource(pair.second)) {
return;
}
}
}
// Evaluate for uic
if (UicConst().Enabled) {
if (!UicEval(BaseEval().Headers) || !UicEval(BaseEval().Sources)) {
return;
}
}
// Add discovered header parse jobs
Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered);
// Add generate job after
Gen()->WorkerPool().EmplaceJob<JobGenerateT>();
}
bool cmQtAutoMocUic::JobEvaluateT::MocEvalHeader(SourceFileHandleT source)
{
SourceFileT const& sourceFile = *source;
auto const& parseData = sourceFile.ParseData->Moc;
if (!source->Moc) {
return true;
}
if (!parseData.Macro.empty()) {
// Create a new mapping
MappingHandleT handle = std::make_shared<MappingT>();
handle->SourceFile = std::move(source);
// Absolute build path
if (BaseConst().MultiConfig) {
handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
} else {
handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
}
// Register mapping in headers map
MocRegisterMapping(handle, true);
}
return true;
}
bool cmQtAutoMocUic::JobEvaluateT::MocEvalSource(
SourceFileHandleT const& source)
{
SourceFileT const& sourceFile = *source;
auto const& parseData = sourceFile.ParseData->Moc;
if (!sourceFile.Moc ||
(parseData.Macro.empty() && parseData.Include.Underscore.empty() &&
parseData.Include.Dot.empty())) {
return true;
}
std::string const sourceDir = SubDirPrefix(sourceFile.FileName);
std::string const sourceBase =
cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName);
// For relaxed mode check if the own "moc_" or ".moc" file is included
bool const relaxedMode = MocConst().RelaxedMode;
bool sourceIncludesMocUnderscore = false;
bool sourceIncludesDotMoc = false;
// Check if the sources own "moc_" or ".moc" file is included
if (relaxedMode) {
for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
if (incKey.Base == sourceBase) {
sourceIncludesMocUnderscore = true;
break;
}
}
}
for (IncludeKeyT const& incKey : parseData.Include.Dot) {
if (incKey.Base == sourceBase) {
sourceIncludesDotMoc = true;
break;
}
}
// Check if this source needs to be moc processed but doesn't.
if (!sourceIncludesDotMoc && !parseData.Macro.empty() &&
!(relaxedMode && sourceIncludesMocUnderscore)) {
{
std::string emsg = "The file contains a ";
emsg += Quoted(parseData.Macro);
emsg += " macro, but does not include ";
emsg += Quoted(sourceBase + ".moc");
emsg += "!\nConsider to\n - add #include \"";
emsg += sourceBase;
emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
LogFileError(GenT::MOC, sourceFile.FileName, emsg);
}
return false;
}
// Evaluate "moc_" includes
for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
std::string const headerBase = incKey.Dir + incKey.Base;
SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
if (!header) {
{
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += ",\nbut the header could not be found "
"in the following locations\n";
msg += MocMessageTestHeaders(headerBase);
LogFileError(GenT::MOC, sourceFile.FileName, msg);
}
return false;
}
// The include might be handled differently in relaxed mode
if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() &&
(incKey.Base == sourceBase)) {
// The <BASE>.cpp file includes a Qt macro but does not include the
// <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably
// be generated from <BASE>.cpp instead of <BASE>.h, because otherwise
// it won't build. But warn, since this is not how it is supposed to be
// used. This is for KDE4 compatibility.
{
// Issue a warning
std::string msg = "The file contains a ";
msg += Quoted(parseData.Macro);
msg += " macro, but does not include ";
msg += Quoted(sourceBase + ".moc");
msg += ".\nInstead it includes ";
msg += Quoted(incKey.Key);
msg += ".\nRunning moc on the source\n ";
msg += Quoted(sourceFile.FileName);
msg += "!\nBetter include ";
msg += Quoted(sourceBase + ".moc");
msg += " for compatibility with regular mode.\n";
msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
}
// Create mapping
if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
return false;
}
continue;
}
// Check if header is skipped
if (MocConst().skipped(header->FileName)) {
continue;
}
// Create mapping
if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
return false;
}
}
// Evaluate ".moc" includes
if (relaxedMode) {
// Relaxed mode
for (IncludeKeyT const& incKey : parseData.Include.Dot) {
// Check if this is the sources own .moc file
bool const ownMoc = (incKey.Base == sourceBase);
if (ownMoc && !parseData.Macro.empty()) {
// Create mapping for the regular use case
if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
return false;
}
continue;
}
// Try to find a header instead but issue a warning.
// This is for KDE4 compatibility.
std::string const headerBase = incKey.Dir + incKey.Base;
SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
if (!header) {
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += ",\nwhich seems to be the moc file from a different source "
"file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a matching header"
"could not be found in the following locations\n";
msg += MocMessageTestHeaders(headerBase);
LogFileError(GenT::MOC, sourceFile.FileName, msg);
return false;
}
// Check if header is skipped
if (MocConst().skipped(header->FileName)) {
continue;
}
// Issue a warning
if (ownMoc && parseData.Macro.empty()) {
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += ", but does not contain a\n";
msg += MocConst().MacrosString();
msg += " macro.\nRunning moc on the header\n ";
msg += Quoted(header->FileName);
msg += "!\nBetter include ";
msg += Quoted("moc_" + incKey.Base + ".cpp");
msg += " for a compatibility with regular mode.\n";
msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
} else {
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += " instead of ";
msg += Quoted("moc_" + incKey.Base + ".cpp");
msg += ".\nRunning moc on the header\n ";
msg += Quoted(header->FileName);
msg += "!\nBetter include ";
msg += Quoted("moc_" + incKey.Base + ".cpp");
msg += " for compatibility with regular mode.\n";
msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
}
// Create mapping
if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
return false;
}
}
} else {
// Strict mode
for (IncludeKeyT const& incKey : parseData.Include.Dot) {
// Check if this is the sources own .moc file
bool const ownMoc = (incKey.Base == sourceBase);
if (!ownMoc) {
// Don't allow <BASE>.moc include other than own in regular mode
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += ",\nwhich seems to be the moc file from a different "
"source file.\nThis is not supported. Include ";
msg += Quoted(sourceBase + ".moc");
msg += " to run moc on this source file.";
LogFileError(GenT::MOC, sourceFile.FileName, msg);
return false;
}
// Accept but issue a warning if moc isn't required
if (parseData.Macro.empty()) {
std::string msg = "The file includes the moc file ";
msg += Quoted(incKey.Key);
msg += ", but does not contain a ";
msg += MocConst().MacrosString();
msg += " macro.";
Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
}
// Create mapping
if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
return false;
}
}
}
return true;
}
cmQtAutoMocUic::SourceFileHandleT
cmQtAutoMocUic::JobEvaluateT::MocFindIncludedHeader(
std::string const& includerDir, std::string const& includeBase) const
{
// Search in vicinity of the source
{
SourceFileHandleT res = MocFindHeader(includerDir + includeBase);
if (res) {
return res;
}
}
// Search in include directories
for (std::string const& path : MocConst().IncludePaths) {
std::string testPath = path;
testPath += '/';
testPath += includeBase;
SourceFileHandleT res = MocFindHeader(testPath);
if (res) {
return res;
}
}
// Return without success
return SourceFileHandleT();
}
cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindHeader(
std::string const& basePath) const
{
std::string testPath;
testPath.reserve(basePath.size() + 8);
for (std::string const& ext : BaseConst().HeaderExtensions) {
testPath.clear();
testPath += basePath;
testPath += '.';
testPath += ext;
cmFileTime fileTime;
if (fileTime.Load(testPath)) {
// Compute real path of the file
testPath = cmSystemTools::GetRealPath(testPath);
// Return a known file if it exists already
{
auto it = BaseEval().Headers.find(testPath);
if (it != BaseEval().Headers.end()) {
return it->second;
}
}
// Created and return discovered file entry
SourceFileHandleT& res = MocEval().HeadersDiscovered[testPath];
if (!res) {
res = std::make_shared<SourceFileT>(testPath);
res->FileTime = fileTime;
res->Moc = true;
}
return res;
}
}
// Return without success
return SourceFileHandleT();
}
std::string cmQtAutoMocUic::JobEvaluateT::MocMessageTestHeaders(
std::string const& fileBase) const
{
std::ostringstream res;
{
std::string exts = ".{";
exts += cmJoin(BaseConst().HeaderExtensions, ",");
exts += '}';
// Compose result string
res << " " << fileBase << exts << '\n';
for (std::string const& path : MocConst().IncludePaths) {
res << " " << path << '/' << fileBase << exts << '\n';
}
}
return res.str();
}
bool cmQtAutoMocUic::JobEvaluateT::MocRegisterIncluded(
std::string const& includeString, SourceFileHandleT includerFileHandle,
SourceFileHandleT sourceFileHandle, bool sourceIsHeader) const
{
// Check if this file is already included
MappingHandleT& handle = MocEval().Includes[includeString];
if (handle) {
// Check if the output file would be generated from different source files
if (handle->SourceFile != sourceFileHandle) {
std::string msg = "The source files\n ";
msg += Quoted(includerFileHandle->FileName);
msg += '\n';
for (auto const& item : handle->IncluderFiles) {
msg += " ";
msg += Quoted(item->FileName);
msg += '\n';
}
msg += "contain the same include string ";
msg += Quoted(includeString);
msg += ", but\nthe moc file would be generated from different "
"source files\n ";
msg += Quoted(sourceFileHandle->FileName);
msg += " and\n ";
msg += Quoted(handle->SourceFile->FileName);
msg += ".\nConsider to\n"
" - not include the \"moc_<NAME>.cpp\" file\n"
" - add a directory prefix to a \"<NAME>.moc\" include "
"(e.g \"sub/<NAME>.moc\")\n"
" - rename the source file(s)\n";
LogError(GenT::MOC, msg);
return false;
}
// The same mapping already exists. Just add to the includers list.
handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
return true;
}
// Create a new mapping
handle = std::make_shared<MappingT>();
handle->IncludeString = includeString;
handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
handle->SourceFile = std::move(sourceFileHandle);
handle->OutputFile += Gen()->AbsoluteIncludePath(includeString);
// Register mapping in sources/headers map
MocRegisterMapping(handle, sourceIsHeader);
return true;
}
void cmQtAutoMocUic::JobEvaluateT::MocRegisterMapping(
MappingHandleT mappingHandle, bool sourceIsHeader) const
{
auto& regMap =
sourceIsHeader ? MocEval().HeaderMappings : MocEval().SourceMappings;
// Check if source file already gets mapped
auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
if (!regHandle) {
// Yet unknown mapping
regHandle = std::move(mappingHandle);
} else {
// Mappings with include string override those without
if (!mappingHandle->IncludeString.empty()) {
regHandle = std::move(mappingHandle);
}
}
}
bool cmQtAutoMocUic::JobEvaluateT::UicEval(SourceFileMapT const& fileMap)
{
for (auto const& pair : fileMap) {
if (!UicEvalFile(pair.second)) {
return false;
}
}
return true;
}
bool cmQtAutoMocUic::JobEvaluateT::UicEvalFile(
SourceFileHandleT sourceFileHandle)
{
SourceFileT const& sourceFile = *sourceFileHandle;
auto const& Include = sourceFile.ParseData->Uic.Include;
if (!sourceFile.Uic || Include.empty()) {
return true;
}
std::string const sourceDir = SubDirPrefix(sourceFile.FileName);
for (IncludeKeyT const& incKey : Include) {
// Find .ui file name
SourceFileHandleT uiFileHandle =
UicFindIncludedUi(sourceFile.FileName, sourceDir, incKey);
if (!uiFileHandle || UicConst().skipped(uiFileHandle->FileName)) {
continue;
}
// Register mapping
if (!UicRegisterMapping(incKey.Key, std::move(uiFileHandle),
std::move(sourceFileHandle))) {
return false;
}
}
return true;
}
bool cmQtAutoMocUic::JobEvaluateT::UicRegisterMapping(
std::string const& includeString, SourceFileHandleT uiFileHandle,
SourceFileHandleT includerFileHandle)
{
auto& Includes = Gen()->UicEval().Includes;
auto it = Includes.find(includeString);
if (it != Includes.end()) {
MappingHandleT const& handle = it->second;
if (handle->SourceFile != uiFileHandle) {
// The output file already gets generated - from a different .ui file!
std::string msg = "The source files\n ";
msg += Quoted(includerFileHandle->FileName);
msg += '\n';
for (auto const& item : handle->IncluderFiles) {
msg += " ";
msg += Quoted(item->FileName);
msg += '\n';
}
msg += "contain the same include string ";
msg += Quoted(includeString);
msg += ", but\nthe uic file would be generated from different "
"user interface files\n ";
msg += Quoted(uiFileHandle->FileName);
msg += " and\n ";
msg += Quoted(handle->SourceFile->FileName);
msg += ".\nConsider to\n"
" - add a directory prefix to a \"ui_<NAME>.h\" include "
"(e.g \"sub/ui_<NAME>.h\")\n"
" - rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" "
"include(s)\n";
LogError(GenT::UIC, msg);
return false;
}
// Add includer file to existing mapping
handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
} else {
// New mapping handle
MappingHandleT handle = std::make_shared<MappingT>();
handle->IncludeString = includeString;
handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
handle->SourceFile = std::move(uiFileHandle);
handle->OutputFile += Gen()->AbsoluteIncludePath(includeString);
// Register mapping
Includes.emplace(includeString, std::move(handle));
}
return true;
}
cmQtAutoMocUic::SourceFileHandleT
cmQtAutoMocUic::JobEvaluateT::UicFindIncludedUi(
std::string const& sourceFile, std::string const& sourceDir,
IncludeKeyT const& incKey) const
{
std::string searchFileName = incKey.Base;
searchFileName += ".ui";
// Collect search paths list
std::vector<std::string> testFiles;
{
auto& searchPaths = UicConst().SearchPaths;
testFiles.reserve((searchPaths.size() + 1) * 2);
// Vicinity of the source
testFiles.emplace_back(sourceDir + searchFileName);
if (!incKey.Dir.empty()) {
std::string path = sourceDir;
path += incKey.Dir;
path += searchFileName;
testFiles.emplace_back(path);
}
// AUTOUIC search paths
if (!searchPaths.empty()) {
for (std::string const& sPath : searchPaths) {
std::string path = sPath;
path += '/';
path += searchFileName;
testFiles.emplace_back(std::move(path));
}
if (!incKey.Dir.empty()) {
for (std::string const& sPath : searchPaths) {
std::string path = sPath;
path += '/';
path += incKey.Dir;
path += searchFileName;
testFiles.emplace_back(std::move(path));
}
}
}
}
// Search for the .ui file!
for (std::string const& testFile : testFiles) {
cmFileTime fileTime;
if (fileTime.Load(testFile)) {
// .ui file found in files system!
std::string realPath = cmSystemTools::GetRealPath(testFile);
// Get or create .ui file handle
SourceFileHandleT& handle = Gen()->UicEval().UiFiles[realPath];
if (!handle) {
// The file wasn't registered, yet
handle = std::make_shared<SourceFileT>(realPath);
handle->FileTime = fileTime;
}
return handle;
}
}
// Log error
{
std::string msg = "The file includes the uic file ";
msg += Quoted(incKey.Key);
msg += ",\nbut the user interface file ";
msg += Quoted(searchFileName);
msg += "\ncould not be found in the following locations\n";
for (std::string const& testFile : testFiles) {
msg += " ";
msg += Quoted(testFile);
msg += '\n';
}
LogFileError(GenT::UIC, sourceFile, msg);
}
return SourceFileHandleT();
}
void cmQtAutoMocUic::JobGenerateT::Process()
{
// Add moc compile jobs
if (MocConst().Enabled) {
for (auto const& pair : MocEval().HeaderMappings) {
// Register if this mapping is a candidate for mocs_compilation.cpp
bool const compFile = pair.second->IncludeString.empty();
if (compFile) {
MocEval().CompFiles.emplace_back(pair.second->SourceFile->BuildPath);
}
if (!MocGenerate(pair.second, compFile)) {
return;
}
}
for (auto const& pair : MocEval().SourceMappings) {
if (!MocGenerate(pair.second, false)) {
return;
}
}
// Add mocs compilations job on demand
Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
}
// Add uic compile jobs
if (UicConst().Enabled) {
for (auto const& pair : Gen()->UicEval().Includes) {
if (!UicGenerate(pair.second)) {
return;
}
}
}
// Add finish job
Gen()->WorkerPool().EmplaceJob<JobFinishT>();
}
bool cmQtAutoMocUic::JobGenerateT::MocGenerate(MappingHandleT const& mapping,
bool compFile) const
{
std::unique_ptr<std::string> reason;
if (Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (MocUpdate(*mapping, reason.get())) {
// Create the parent directory
if (!MakeParentDirectory(mapping->OutputFile)) {
LogFileError(GenT::MOC, mapping->OutputFile,
"Could not create parent directory.");
return false;
}
// Add moc job
Gen()->WorkerPool().EmplaceJob<JobMocT>(mapping, std::move(reason));
// Check if a moc job for a mocs_compilation.cpp entry was generated
if (compFile) {
MocEval().CompUpdated = true;
}
}
return true;
}
bool cmQtAutoMocUic::JobGenerateT::MocUpdate(MappingT const& mapping,
std::string* reason) const
{
std::string const& sourceFile = mapping.SourceFile->FileName;
std::string const& outputFile = mapping.OutputFile;
// Test if the output file exists
cmFileTime outputFileTime;
if (!outputFileTime.Load(outputFile)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it doesn't exist, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if any setting changed
if (MocConst().SettingsChanged) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because the uic settings changed, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if the source file is newer
if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it's older than its source file, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if the moc_predefs file is newer
if (!MocConst().PredefsFileAbs.empty()) {
if (outputFileTime.Older(MocEval().PredefsTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it's older than ";
*reason += Quoted(MocConst().PredefsFileAbs);
*reason += ", from ";
*reason += Quoted(sourceFile);
}
return true;
}
}
// Test if the moc executable is newer
if (outputFileTime.Older(MocConst().ExecutableTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it's older than the moc executable, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if a dependency file is newer
{
// Check dependency timestamps
std::string const sourceDir = SubDirPrefix(sourceFile);
for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) {
// Find dependency file
auto const depMatch = MocFindDependency(sourceDir, dep);
if (depMatch.first.empty()) {
Log().WarningFile(GenT::MOC, sourceFile,
"Could not find dependency file " + Quoted(dep));
continue;
}
// Test if dependency file is older
if (outputFileTime.Older(depMatch.second)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it's older than its dependency file ";
*reason += Quoted(depMatch.first);
*reason += ", from ";
*reason += Quoted(sourceFile);
}
return true;
}
}
}
return false;
}
std::pair<std::string, cmFileTime>
cmQtAutoMocUic::JobGenerateT::MocFindDependency(
std::string const& sourceDir, std::string const& includeString) const
{
typedef std::pair<std::string, cmFileTime> ResPair;
// Search in vicinity of the source
{
ResPair res{ sourceDir + includeString, {} };
if (res.second.Load(res.first)) {
return res;
}
}
// Search in include directories
for (std::string const& includePath : MocConst().IncludePaths) {
ResPair res{ includePath, {} };
res.first += '/';
res.first += includeString;
if (res.second.Load(res.first)) {
return res;
}
}
// Return empty
return ResPair();
}
bool cmQtAutoMocUic::JobGenerateT::UicGenerate(
MappingHandleT const& mapping) const
{
std::unique_ptr<std::string> reason;
if (Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (UicUpdate(*mapping, reason.get())) {
// Create the parent directory
if (!MakeParentDirectory(mapping->OutputFile)) {
LogFileError(GenT::UIC, mapping->OutputFile,
"Could not create parent directory.");
return false;
}
// Add uic job
Gen()->WorkerPool().EmplaceJob<JobUicT>(mapping, std::move(reason));
}
return true;
}
bool cmQtAutoMocUic::JobGenerateT::UicUpdate(MappingT const& mapping,
std::string* reason) const
{
std::string const& sourceFile = mapping.SourceFile->FileName;
std::string const& outputFile = mapping.OutputFile;
// Test if the build file exists
cmFileTime outputFileTime;
if (!outputFileTime.Load(outputFile)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it doesn't exist, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if the uic settings changed
if (UicConst().SettingsChanged) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because the uic settings changed, from ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if the source file is newer
if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += " because it's older than the source file ";
*reason += Quoted(sourceFile);
}
return true;
}
// Test if the uic executable is newer
if (outputFileTime.Older(UicConst().ExecutableTime)) {
if (reason != nullptr) {
*reason = "Generating ";
*reason += Quoted(outputFile);
*reason += ", because it's older than the uic executable, from ";
*reason += Quoted(sourceFile);
}
return true;
}
return false;
}
void cmQtAutoMocUic::JobMocT::Process()
{
std::string const& sourceFile = Mapping->SourceFile->FileName;
std::string const& outputFile = Mapping->OutputFile;
// Compose moc command
std::vector<std::string> cmd;
cmd.push_back(MocConst().Executable);
// Add options
cmAppend(cmd, MocConst().AllOptions);
// Add predefs include
if (!MocConst().PredefsFileAbs.empty()) {
cmd.emplace_back("--include");
cmd.push_back(MocConst().PredefsFileAbs);
}
cmd.emplace_back("-o");
cmd.push_back(outputFile);
cmd.push_back(sourceFile);
// Execute moc command
cmWorkerPool::ProcessResultT result;
if (RunProcess(GenT::MOC, result, cmd, Reason.get())) {
// Moc command success. Print moc output.
if (!result.StdOut.empty()) {
Log().Info(GenT::MOC, result.StdOut);
}
} else {
// Moc command failed
std::string msg = "The moc process failed to compile\n ";
msg += Quoted(sourceFile);
msg += "\ninto\n ";
msg += Quoted(outputFile);
if (Mapping->IncluderFiles.empty()) {
msg += ".\n";
} else {
msg += "\nincluded by\n";
for (auto const& item : Mapping->IncluderFiles) {
msg += " ";
msg += Quoted(item->FileName);
msg += '\n';
}
}
msg += result.ErrorMessage;
LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
}
}
void cmQtAutoMocUic::JobUicT::Process()
{
std::string const& sourceFile = Mapping->SourceFile->FileName;
std::string const& outputFile = Mapping->OutputFile;
// Compose uic command
std::vector<std::string> cmd;
cmd.push_back(UicConst().Executable);
{
std::vector<std::string> allOpts = UicConst().TargetOptions;
auto optionIt = UicConst().Options.find(sourceFile);
if (optionIt != UicConst().Options.end()) {
UicMergeOptions(allOpts, optionIt->second,
(BaseConst().QtVersionMajor == 5));
}
cmAppend(cmd, allOpts);
}
cmd.emplace_back("-o");
cmd.emplace_back(outputFile);
cmd.emplace_back(sourceFile);
cmWorkerPool::ProcessResultT result;
if (RunProcess(GenT::UIC, result, cmd, Reason.get())) {
// Uic command success
// Print uic output
if (!result.StdOut.empty()) {
Log().Info(GenT::UIC, result.StdOut);
}
} else {
// Uic command failed
std::string msg = "The uic process failed to compile\n ";
msg += Quoted(sourceFile);
msg += "\ninto\n ";
msg += Quoted(outputFile);
msg += "\nincluded by\n";
for (auto const& item : Mapping->IncluderFiles) {
msg += " ";
msg += Quoted(item->FileName);
msg += '\n';
}
msg += result.ErrorMessage;
LogCommandError(GenT::UIC, msg, cmd, result.StdOut);
}
}
void cmQtAutoMocUic::JobMocsCompilationT::Process()
{
// Compose mocs compilation file content
std::string content =
"// This file is autogenerated. Changes will be overwritten.\n";
if (MocEval().CompFiles.empty()) {
// Placeholder content
content += "// No files found that require moc or the moc files are "
"included\n";
content += "enum some_compilers { need_more_than_nothing };\n";
} else {
// Valid content
char const clampB = BaseConst().MultiConfig ? '<' : '"';
char const clampE = BaseConst().MultiConfig ? '>' : '"';
for (std::string const& mocfile : MocEval().CompFiles) {
content += "#include ";
content += clampB;
content += mocfile;
content += clampE;
content += '\n';
}
}
std::string const& compAbs = MocConst().CompFileAbs;
if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
// Actually write mocs compilation file
if (Log().Verbose()) {
Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs);
}
if (!FileWrite(compAbs, content)) {
LogFileError(GenT::MOC, compAbs,
"mocs compilation file writing failed.");
}
} else if (MocEval().CompUpdated) {
// Only touch mocs compilation file
if (Log().Verbose()) {
Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
}
if (!cmSystemTools::Touch(compAbs, false)) {
LogFileError(GenT::MOC, compAbs,
"mocs compilation file touching failed.");
}
}
}
void cmQtAutoMocUic::JobFinishT::Process()
{
Gen()->AbortSuccess();
}
cmQtAutoMocUic::cmQtAutoMocUic() = default;
cmQtAutoMocUic::~cmQtAutoMocUic() = default;
bool cmQtAutoMocUic::Init(cmMakefile* makefile)
{
// Utility lambdas
auto InfoGet = [makefile](const char* key) {
return makefile->GetSafeDefinition(key);
};
auto InfoGetBool = [makefile](const char* key) {
return makefile->IsOn(key);
};
auto InfoGetList = [makefile](const char* key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
return list;
};
auto InfoGetLists =
[makefile](const char* key) -> std::vector<std::vector<std::string>> {
std::vector<std::vector<std::string>> lists;
{
std::string const value = makefile->GetSafeDefinition(key);
std::string::size_type pos = 0;
while (pos < value.size()) {
std::string::size_type next = value.find(ListSep, pos);
std::string::size_type length =
(next != std::string::npos) ? next - pos : value.size() - pos;
// Remove enclosing braces
if (length >= 2) {
std::string::const_iterator itBeg = value.begin() + (pos + 1);
std::string::const_iterator itEnd = itBeg + (length - 2);
{
std::string subValue(itBeg, itEnd);
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(subValue, list);
lists.push_back(std::move(list));
}
}
pos += length;
pos += ListSep.size();
}
}
return lists;
};
auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
const char* valueConf = nullptr;
{
std::string keyConf = key;
keyConf += '_';
keyConf += InfoConfig();
valueConf = makefile->GetDefinition(keyConf);
}
if (valueConf == nullptr) {
return makefile->GetSafeDefinition(key);
}
return std::string(valueConf);
};
auto InfoGetConfigList =
[&InfoGetConfig](const char* key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
return list;
};
auto LogInfoError = [this](std::string const& msg) -> bool {
std::ostringstream err;
err << "In " << Quoted(this->InfoFile()) << ":\n" << msg;
this->Log().Error(GenT::GEN, err.str());
return false;
};
auto MatchSizes = [&LogInfoError](const char* keyA, const char* keyB,
std::size_t sizeA,
std::size_t sizeB) -> bool {
if (sizeA == sizeB) {
return true;
}
std::ostringstream err;
err << "Lists sizes mismatch " << keyA << '(' << sizeA << ") " << keyB
<< '(' << sizeB << ')';
return LogInfoError(err.str());
};
// -- Read info file
if (!makefile->ReadListFile(InfoFile())) {
return LogInfoError("File processing failed");
}
// -- Meta
Logger_.RaiseVerbosity(InfoGet("AM_VERBOSITY"));
BaseConst_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG");
{
unsigned long num = 1;
if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL").c_str(), &num)) {
num = std::max<unsigned long>(num, 1);
num = std::min<unsigned long>(num, ParallelMax);
}
WorkerPool_.SetThreadCount(static_cast<unsigned int>(num));
}
BaseConst_.HeaderExtensions =
makefile->GetCMakeInstance()->GetHeaderExtensions();
// - Files and directories
BaseConst_.IncludeProjectDirsBefore =
InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
BaseConst_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
BaseConst_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
BaseConst_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
BaseConst_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
BaseConst_.AutogenBuildDir = InfoGet("AM_BUILD_DIR");
if (BaseConst_.AutogenBuildDir.empty()) {
return LogInfoError("Autogen build directory missing.");
}
BaseConst_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR");
if (BaseConst_.AutogenIncludeDir.empty()) {
return LogInfoError("Autogen include directory missing.");
}
BaseConst_.CMakeExecutable = InfoGetConfig("AM_CMAKE_EXECUTABLE");
if (BaseConst_.CMakeExecutable.empty()) {
return LogInfoError("CMake executable file name missing.");
}
if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) {
std::string error = "The CMake executable ";
error += Quoted(BaseConst_.CMakeExecutable);
error += " does not exist.";
return LogInfoError(error);
}
BaseConst_.ParseCacheFile = InfoGetConfig("AM_PARSE_CACHE_FILE");
if (BaseConst_.ParseCacheFile.empty()) {
return LogInfoError("Parse cache file name missing.");
}
// - Settings file
SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE");
if (SettingsFile_.empty()) {
return LogInfoError("Settings file name missing.");
}
// - Qt environment
{
unsigned long qtv = BaseConst_.QtVersionMajor;
if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(),
&qtv)) {
BaseConst_.QtVersionMajor = static_cast<unsigned int>(qtv);
}
}
// - Moc
MocConst_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE");
if (!MocConst().Executable.empty()) {
MocConst_.Enabled = true;
// Load the executable file time
if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
std::string error = "The moc executable ";
error += Quoted(MocConst_.Executable);
error += " does not exist.";
return LogInfoError(error);
}
for (std::string& sfl : InfoGetList("AM_MOC_SKIP")) {
MocConst_.SkipList.insert(std::move(sfl));
}
MocConst_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
MocConst_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
MocConst_.Options = InfoGetList("AM_MOC_OPTIONS");
MocConst_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) {
MocConst_.MacroFilters.emplace_back(
item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
}
{
auto addFilter = [this, &LogInfoError](std::string const& key,
std::string const& exp) -> bool {
auto filterErr = [&LogInfoError, &key, &exp](const char* err) -> bool {
std::ostringstream ferr;
ferr << "AUTOMOC_DEPEND_FILTERS: " << err << '\n';
ferr << " Key: " << Quoted(key) << '\n';
ferr << " Exp: " << Quoted(exp) << '\n';
return LogInfoError(ferr.str());
};
if (key.empty()) {
return filterErr("Key is empty");
}
if (exp.empty()) {
return filterErr("Regular expression is empty");
}
this->MocConst_.DependFilters.emplace_back(key, exp);
if (!this->MocConst_.DependFilters.back().Exp.is_valid()) {
return filterErr("Regular expression compiling failed");
}
return true;
};
// Insert default filter for Q_PLUGIN_METADATA
if (BaseConst().QtVersionMajor != 4) {
if (!addFilter("Q_PLUGIN_METADATA",
"[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
"[^\\)]*FILE[ \t]*\"([^\"]+)\"")) {
return false;
}
}
// Insert user defined dependency filters
std::vector<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS");
if ((flts.size() % 2) != 0) {
return LogInfoError(
"AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
}
for (auto itC = flts.begin(), itE = flts.end(); itC != itE; itC += 2) {
if (!addFilter(*itC, *(itC + 1))) {
return false;
}
}
}
MocConst_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
}
// - Uic
UicConst_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE");
if (!UicConst().Executable.empty()) {
UicConst_.Enabled = true;
// Load the executable file time
if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) {
std::string error = "The uic executable ";
error += Quoted(UicConst_.Executable);
error += " does not exist.";
return LogInfoError(error);
}
for (std::string& sfl : InfoGetList("AM_UIC_SKIP")) {
UicConst_.SkipList.insert(std::move(sfl));
}
UicConst_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
UicConst_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
{
const char* keyFiles = "AM_UIC_OPTIONS_FILES";
const char* keyOpts = "AM_UIC_OPTIONS_OPTIONS";
auto sources = InfoGetList(keyFiles);
auto options = InfoGetLists(keyOpts);
if (!MatchSizes(keyFiles, keyOpts, sources.size(), options.size())) {
return false;
}
auto fitEnd = sources.cend();
auto fit = sources.begin();
auto oit = options.begin();
while (fit != fitEnd) {
UicConst_.Options[*fit] = std::move(*oit);
++fit;
++oit;
}
}
}
// - Headers and sources
{
auto makeSource =
[&LogInfoError](std::string const& fileName,
std::string const& fileFlags) -> SourceFileHandleT {
if (fileFlags.size() != 2) {
LogInfoError("Invalid file flags string size");
return SourceFileHandleT();
}
cmFileTime fileTime;
if (!fileTime.Load(fileName)) {
LogInfoError("The source file " + cmQtAutoGen::Quoted(fileName) +
" does not exist.");
return SourceFileHandleT();
}
SourceFileHandleT sfh = std::make_shared<SourceFileT>(fileName);
sfh->FileTime = fileTime;
sfh->Moc = (fileFlags[0] == 'M');
sfh->Uic = (fileFlags[1] == 'U');
return sfh;
};
// Headers
{
// Get file lists
const char *keyFiles = "AM_HEADERS", *keyFlags = "AM_HEADERS_FLAGS";
std::vector<std::string> files = InfoGetList(keyFiles);
std::vector<std::string> flags = InfoGetList(keyFlags);
std::vector<std::string> builds;
if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
return false;
}
if (MocConst().Enabled) {
const char* keyPaths = "AM_HEADERS_BUILD_PATHS";
builds = InfoGetList(keyPaths);
if (!MatchSizes(keyFiles, keyPaths, files.size(), builds.size())) {
return false;
}
}
// Process file lists
for (std::size_t ii = 0; ii != files.size(); ++ii) {
std::string& fileName(files[ii]);
SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
if (!sfh) {
return false;
}
if (MocConst().Enabled) {
sfh->BuildPath = std::move(builds[ii]);
if (sfh->BuildPath.empty()) {
Log().ErrorFile(GenT::GEN, this->InfoFile(),
"Header file build path is empty");
return false;
}
}
BaseEval().Headers.emplace(std::move(fileName), std::move(sfh));
}
}
// Sources
{
const char *keyFiles = "AM_SOURCES", *keyFlags = "AM_SOURCES_FLAGS";
std::vector<std::string> files = InfoGetList(keyFiles);
std::vector<std::string> flags = InfoGetList(keyFlags);
if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
return false;
}
// Process file lists
for (std::size_t ii = 0; ii != files.size(); ++ii) {
std::string& fileName(files[ii]);
SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
if (!sfh) {
return false;
}
BaseEval().Sources.emplace(std::move(fileName), std::move(sfh));
}
}
}
// Init derived information
// ------------------------
// Moc variables
if (MocConst().Enabled) {
// Mocs compilation file
MocConst_.CompFileAbs = AbsoluteBuildPath("mocs_compilation.cpp");
// Moc predefs file
if (!MocConst_.PredefsCmd.empty()) {
MocConst_.PredefsFileRel = "moc_predefs";
if (BaseConst_.MultiConfig) {
MocConst_.PredefsFileRel += '_';
MocConst_.PredefsFileRel += InfoConfig();
}
MocConst_.PredefsFileRel += ".h";
MocConst_.PredefsFileAbs = AbsoluteBuildPath(MocConst().PredefsFileRel);
}
// Sort include directories on demand
if (BaseConst().IncludeProjectDirsBefore) {
// Move strings to temporary list
std::list<std::string> includes(MocConst().IncludePaths.begin(),
MocConst().IncludePaths.end());
MocConst_.IncludePaths.clear();
MocConst_.IncludePaths.reserve(includes.size());
// Append project directories only
{
std::array<std::string const*, 2> const movePaths = {
{ &BaseConst().ProjectBinaryDir, &BaseConst().ProjectSourceDir }
};
for (std::string const* ppath : movePaths) {
std::list<std::string>::iterator it = includes.begin();
while (it != includes.end()) {
std::string const& path = *it;
if (cmSystemTools::StringStartsWith(path, ppath->c_str())) {
MocConst_.IncludePaths.push_back(path);
it = includes.erase(it);
} else {
++it;
}
}
}
}
// Append remaining directories
MocConst_.IncludePaths.insert(MocConst_.IncludePaths.end(),
includes.begin(), includes.end());
}
// Compose moc includes list
{
std::set<std::string> frameworkPaths;
for (std::string const& path : MocConst().IncludePaths) {
MocConst_.Includes.push_back("-I" + path);
// Extract framework path
if (cmHasLiteralSuffix(path, ".framework/Headers")) {
// Go up twice to get to the framework root
std::vector<std::string> pathComponents;
cmSystemTools::SplitPath(path, pathComponents);
frameworkPaths.emplace(cmSystemTools::JoinPath(
pathComponents.begin(), pathComponents.end() - 2));
}
}
// Append framework includes
for (std::string const& path : frameworkPaths) {
MocConst_.Includes.emplace_back("-F");
MocConst_.Includes.push_back(path);
}
}
// Setup single list with all options
{
// Add includes
MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
MocConst().Includes.begin(),
MocConst().Includes.end());
// Add definitions
for (std::string const& def : MocConst().Definitions) {
MocConst_.AllOptions.push_back("-D" + def);
}
// Add options
MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
MocConst().Options.begin(),
MocConst().Options.end());
}
}
return true;
}
template <class JOBTYPE>
void cmQtAutoMocUic::CreateParseJobs(SourceFileMapT const& sourceMap)
{
cmFileTime const parseCacheTime = BaseEval().ParseCacheTime;
ParseCacheT& parseCache = BaseEval().ParseCache;
for (auto& src : sourceMap) {
// Get or create the file parse data reference
ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first);
src.second->ParseData = std::move(cacheEntry.first);
// Create a parse job if the cache file was missing or is older
if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) {
BaseEval().ParseCacheChanged = true;
WorkerPool().EmplaceJob<JOBTYPE>(src.second);
}
}
}
void cmQtAutoMocUic::InitJobs()
{
// Add moc_predefs.h job
if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) {
WorkerPool().EmplaceJob<JobMocPredefsT>();
}
// Add header parse jobs
CreateParseJobs<JobParseHeaderT>(BaseEval().Headers);
// Add source parse jobs
CreateParseJobs<JobParseSourceT>(BaseEval().Sources);
// Add evaluate job
WorkerPool().EmplaceJob<JobEvaluateT>();
}
bool cmQtAutoMocUic::Process()
{
SettingsFileRead();
ParseCacheRead();
if (!CreateDirectories()) {
return false;
}
InitJobs();
if (!WorkerPool_.Process(this)) {
return false;
}
if (JobError_) {
return false;
}
if (!ParseCacheWrite()) {
return false;
}
if (!SettingsFileWrite()) {
return false;
}
return true;
}
void cmQtAutoMocUic::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
std::string const sep(";");
auto cha = [&cryptoHash, &sep](std::string const& value) {
cryptoHash.Append(value);
cryptoHash.Append(sep);
};
if (MocConst_.Enabled) {
cryptoHash.Initialize();
cha(MocConst().Executable);
for (auto const& value : MocConst().AllOptions) {
cha(value);
}
cha(BaseConst().IncludeProjectDirsBefore ? "TRUE" : "FALSE");
for (auto const& value : MocConst().PredefsCmd) {
cha(value);
}
for (auto const& filter : MocConst().DependFilters) {
cha(filter.Key);
}
for (auto const& filter : MocConst().MacroFilters) {
cha(filter.Key);
}
SettingsStringMoc_ = cryptoHash.FinalizeHex();
}
if (UicConst().Enabled) {
cryptoHash.Initialize();
cha(UicConst().Executable);
for (auto const& value : UicConst().TargetOptions) {
cha(value);
}
for (const auto& item : UicConst().Options) {
cha(item.first);
for (auto const& svalue : item.second) {
cha(svalue);
}
}
SettingsStringUic_ = cryptoHash.FinalizeHex();
}
}
// Read old settings and compare
{
std::string content;
if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) {
if (MocConst().Enabled) {
if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
MocConst_.SettingsChanged = true;
}
}
if (UicConst().Enabled) {
if (SettingsStringUic_ != SettingsFind(content, "uic")) {
UicConst_.SettingsChanged = true;
}
}
// In case any setting changed remove the old settings file.
// This triggers a full rebuild on the next run if the current
// build is aborted before writing the current settings in the end.
if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
cmSystemTools::RemoveFile(SettingsFile_);
}
} else {
// Settings file read failed
if (MocConst().Enabled) {
MocConst_.SettingsChanged = true;
}
if (UicConst().Enabled) {
UicConst_.SettingsChanged = true;
}
}
}
}
bool cmQtAutoMocUic::SettingsFileWrite()
{
// Only write if any setting changed
if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
if (Log().Verbose()) {
Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_));
}
// Compose settings file content
std::string content;
{
auto SettingAppend = [&content](const char* key,
std::string const& value) {
if (!value.empty()) {
content += key;
content += ':';
content += value;
content += '\n';
}
};
SettingAppend("moc", SettingsStringMoc_);
SettingAppend("uic", SettingsStringUic_);
}
// Write settings file
std::string error;
if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
Log().ErrorFile(GenT::GEN, SettingsFile_,
"Settings file writing failed. " + error);
// Remove old settings file to trigger a full rebuild on the next run
cmSystemTools::RemoveFile(SettingsFile_);
return false;
}
}
return true;
}
void cmQtAutoMocUic::ParseCacheRead()
{
const char* reason = nullptr;
// Don't read the cache if it is invalid
if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) {
reason = "Refreshing parse cache because it doesn't exist.";
} else if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
reason = "Refreshing parse cache because the settings changed.";
} else if (BaseEval().ParseCacheTime.Older(
BaseConst().CMakeExecutableTime)) {
reason =
"Refreshing parse cache because it is older than the CMake executable.";
}
if (reason != nullptr) {
// Don't read but refresh the complete parse cache
if (Log().Verbose()) {
Log().Info(GenT::GEN, reason);
}
BaseEval().ParseCacheChanged = true;
} else {
// Read parse cache
BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile);
}
}
bool cmQtAutoMocUic::ParseCacheWrite()
{
if (BaseEval().ParseCacheChanged) {
if (Log().Verbose()) {
Log().Info(GenT::GEN,
"Writing parse cache file " +
Quoted(BaseConst().ParseCacheFile));
}
if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) {
Log().ErrorFile(GenT::GEN, BaseConst().ParseCacheFile,
"Parse cache file writing failed.");
return false;
}
}
return true;
}
bool cmQtAutoMocUic::CreateDirectories()
{
// Create AUTOGEN include directory
if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) {
Log().ErrorFile(GenT::GEN, BaseConst().AutogenIncludeDir,
"Could not create directory.");
return false;
}
return true;
}
void cmQtAutoMocUic::Abort(bool error)
{
if (error) {
JobError_.store(true);
}
WorkerPool_.Abort();
}
std::string cmQtAutoMocUic::AbsoluteBuildPath(
std::string const& relativePath) const
{
std::string res(BaseConst().AutogenBuildDir);
res += '/';
res += relativePath;
return res;
}
std::string cmQtAutoMocUic::AbsoluteIncludePath(
std::string const& relativePath) const
{
std::string res(BaseConst().AutogenIncludeDir);
res += '/';
res += relativePath;
return res;
}