CMake/Source/cmQtAutoMocUic.cxx
Brad King e08d34eda1 automoc: revert attempts to silence linker warning on macos
We've made several attempts to add a dummy symbol to `mocs_compilation.cpp`
when there are no automoc sources:

* commit 4a9daae483 (automoc: silence linker warning on macos,
                     2022-05-25, v3.24.0-rc1~55^2)
* commit 844244ccdc (automoc: avoid compiler warnings in linker-warning-
                     silencing code, 2022-08-10, v3.24.1~6^2)
* commit fc8628389f (automoc: avoid more compiler warnings in linker-
                     warning-silencing code, 2022-08-29)

The last attempt derives a symbol name from the path to the source
file, but that breaks reproducible builds because it is not stable
w.r.t. the location of the build tree.  Revert all these attempts
for the 3.24 release series, and return to what 3.23 and below did.
Further investigation will be needed to resolve the original issue.

Fixes: #23937
Issue: #23551, #23823, #23823
2022-09-07 10:35:17 -04:00

3067 lines
97 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 <atomic>
#include <cstddef>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cm3p/json/value.h>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmCryptoHash.h"
#include "cmFileTime.h"
#include "cmGccDepfileReader.h"
#include "cmGeneratedFileStream.h"
#include "cmQtAutoGen.h"
#include "cmQtAutoGenerator.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmWorkerPool.h"
#if defined(__APPLE__)
# include <unistd.h>
#endif
namespace {
constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_"
constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_"
/** \class cmQtAutoMocUicT
* \brief AUTOMOC and AUTOUIC generator
*/
class cmQtAutoMocUicT : public cmQtAutoGenerator
{
public:
cmQtAutoMocUicT();
~cmQtAutoMocUicT() override;
cmQtAutoMocUicT(cmQtAutoMocUicT const&) = delete;
cmQtAutoMocUicT& operator=(cmQtAutoMocUicT const&) = delete;
// -- Types
/** Include string with sub parts. */
struct IncludeKeyT
{
IncludeKeyT(std::string const& key, std::size_t basePrefixLength);
std::string Key; // Full include string
std::string Dir; // Include directory
std::string Base; // Base part of the include file name
};
/** Search key plus regular expression pair. */
struct KeyExpT
{
KeyExpT(std::string key, std::string const& exp)
: Key(std::move(key))
, Exp(exp)
{
}
std::string Key;
cmsys::RegularExpression Exp;
};
/** Source file parsing cache. */
class ParseCacheT
{
public:
// -- Types
/** Entry of the file parsing cache. */
struct FileT
{
void Clear();
struct MocT
{
std::string Macro;
struct IncludeT
{
std::vector<IncludeKeyT> Underscore;
std::vector<IncludeKeyT> Dot;
} Include;
std::vector<std::string> Depends;
} Moc;
struct UicT
{
std::vector<IncludeKeyT> Include;
std::vector<std::string> Depends;
} Uic;
};
using FileHandleT = std::shared_ptr<FileT>;
using GetOrInsertT = std::pair<FileHandleT, bool>;
ParseCacheT();
~ParseCacheT();
bool ReadFromFile(std::string const& fileName);
bool WriteToFile(std::string const& fileName);
//! Always returns a valid handle
GetOrInsertT GetOrInsert(std::string const& fileName);
private:
std::unordered_map<std::string, FileHandleT> Map_;
};
/** Source file data. */
class SourceFileT
{
public:
SourceFileT(std::string fileName)
: FileName(std::move(fileName))
{
}
std::string FileName;
cmFileTime FileTime;
ParseCacheT::FileHandleT ParseData;
std::string BuildPath;
bool IsHeader = false;
bool Moc = false;
bool Uic = false;
};
using SourceFileHandleT = std::shared_ptr<SourceFileT>;
using SourceFileMapT = std::map<std::string, SourceFileHandleT>;
/** Meta compiler file mapping information. */
struct MappingT
{
SourceFileHandleT SourceFile;
std::string OutputFile;
std::string IncludeString;
std::vector<SourceFileHandleT> IncluderFiles;
};
using MappingHandleT = std::shared_ptr<MappingT>;
using MappingMapT = std::map<std::string, MappingHandleT>;
/** Common settings. */
class BaseSettingsT
{
public:
// -- Constructors
BaseSettingsT();
~BaseSettingsT();
BaseSettingsT(BaseSettingsT const&) = delete;
BaseSettingsT& operator=(BaseSettingsT const&) = delete;
// -- Attributes
// - Config
bool MultiConfig = false;
IntegerVersion QtVersion = { 4, 0 };
unsigned int ThreadCount = 0;
// - Directories
std::string AutogenBuildDir;
std::string AutogenIncludeDir;
// - Files
std::string CMakeExecutable;
cmFileTime CMakeExecutableTime;
std::string ParseCacheFile;
std::string DepFile;
std::string DepFileRuleName;
std::vector<std::string> HeaderExtensions;
std::vector<std::string> ListFiles;
};
/** Shared common variables. */
class BaseEvalT
{
public:
// -- Parse Cache
std::atomic<bool> ParseCacheChanged = ATOMIC_VAR_INIT(false);
cmFileTime ParseCacheTime;
ParseCacheT ParseCache;
// -- Sources
SourceFileMapT Headers;
SourceFileMapT Sources;
};
/** Moc settings. */
class MocSettingsT
{
public:
// -- Constructors
MocSettingsT();
~MocSettingsT();
MocSettingsT(MocSettingsT const&) = delete;
MocSettingsT& operator=(MocSettingsT const&) = delete;
// -- Const methods
bool skipped(std::string const& fileName) const;
std::string MacrosString() const;
// -- Attributes
bool Enabled = false;
bool SettingsChanged = false;
bool RelaxedMode = false;
bool PathPrefix = false;
bool CanOutputDependencies = false;
cmFileTime ExecutableTime;
std::string Executable;
std::string CompFileAbs;
std::string PredefsFileAbs;
std::unordered_set<std::string> SkipList;
std::vector<std::string> IncludePaths;
std::vector<std::string> Definitions;
std::vector<std::string> OptionsIncludes;
std::vector<std::string> OptionsDefinitions;
std::vector<std::string> OptionsExtra;
std::vector<std::string> PredefsCmd;
std::vector<KeyExpT> DependFilters;
std::vector<KeyExpT> MacroFilters;
cmsys::RegularExpression RegExpInclude;
};
/** Moc shared variables. */
class MocEvalT
{
public:
// -- predefines file
cmFileTime PredefsTime;
// -- Mappings
MappingMapT HeaderMappings;
MappingMapT SourceMappings;
MappingMapT Includes;
// -- Discovered files
SourceFileMapT HeadersDiscovered;
// -- Output directories
std::unordered_set<std::string> OutputDirs;
// -- Mocs compilation
bool CompUpdated = false;
std::vector<std::string> CompFiles;
};
/** Uic settings. */
class UicSettingsT
{
public:
struct UiFile
{
std::vector<std::string> Options;
};
UicSettingsT();
~UicSettingsT();
UicSettingsT(UicSettingsT const&) = delete;
UicSettingsT& operator=(UicSettingsT const&) = delete;
// -- Const methods
bool skipped(std::string const& fileName) const;
// -- Attributes
bool Enabled = false;
bool SettingsChanged = false;
cmFileTime ExecutableTime;
std::string Executable;
std::unordered_set<std::string> SkipList;
std::vector<std::string> Options;
std::unordered_map<std::string, UiFile> UiFiles;
std::vector<std::string> SearchPaths;
cmsys::RegularExpression RegExpInclude;
};
/** Uic shared variables. */
class UicEvalT
{
public:
// -- Discovered files
SourceFileMapT UiFiles;
// -- Mappings
MappingMapT Includes;
// -- Output directories
std::unordered_set<std::string> OutputDirs;
};
/** Abstract job class for concurrent job processing. */
class JobT : public cmWorkerPool::JobT
{
protected:
/** Protected default constructor. */
JobT(bool fence = false)
: cmWorkerPool::JobT(fence)
{
}
//! Get the generator. Only valid during Process() call!
cmQtAutoMocUicT* Gen() const
{
return static_cast<cmQtAutoMocUicT*>(this->UserData());
}
// -- Accessors. Only valid during Process() call!
Logger const& Log() const { return this->Gen()->Log(); }
BaseSettingsT const& BaseConst() const { return this->Gen()->BaseConst(); }
BaseEvalT& BaseEval() const { return this->Gen()->BaseEval(); }
MocSettingsT const& MocConst() const { return this->Gen()->MocConst(); }
MocEvalT& MocEval() const { return this->Gen()->MocEval(); }
UicSettingsT const& UicConst() const { return this->Gen()->UicConst(); }
UicEvalT& UicEval() const { return this->Gen()->UicEval(); }
// -- Logging
std::string MessagePath(cm::string_view path) const
{
return this->Gen()->MessagePath(path);
}
// - Error logging with automatic abort
void LogError(GenT genType, cm::string_view message) const;
void LogCommandError(GenT genType, cm::string_view message,
std::vector<std::string> const& command,
std::string const& output) const;
/** @brief Run an external process. Use only during Process() call! */
bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command,
std::string* infoMessage = nullptr);
};
/** Fence job utility class. */
class JobFenceT : public JobT
{
public:
JobFenceT()
: JobT(true)
{
}
void Process() override {}
};
/** Generate moc_predefs.h. */
class JobMocPredefsT : public JobFenceT
{
void Process() override;
bool Update(std::string* reason) const;
};
/** File parse job base class. */
class JobParseT : public JobT
{
public:
JobParseT(SourceFileHandleT fileHandle)
: FileHandle(std::move(fileHandle))
{
}
protected:
bool ReadFile();
void CreateKeys(std::vector<IncludeKeyT>& container,
std::set<std::string> const& source,
std::size_t basePrefixLength);
void MocMacro();
void MocDependecies();
void MocIncludes();
void UicIncludes();
SourceFileHandleT FileHandle;
std::string Content;
};
/** Header file parse job. */
class JobParseHeaderT : public JobParseT
{
public:
using JobParseT::JobParseT;
void Process() override;
};
/** Source file parse job. */
class JobParseSourceT : public JobParseT
{
public:
using JobParseT::JobParseT;
void Process() override;
};
/** Evaluate cached file parse data - moc. */
class JobEvalCacheT : public JobT
{
protected:
std::string MessageSearchLocations() const;
std::vector<std::string> SearchLocations;
};
/** Evaluate cached file parse data - moc. */
class JobEvalCacheMocT : public JobEvalCacheT
{
void Process() override;
bool EvalHeader(SourceFileHandleT source);
bool EvalSource(SourceFileHandleT const& source);
bool FindIncludedHeader(SourceFileHandleT& headerHandle,
cm::string_view includerDir,
cm::string_view includeBase);
bool RegisterIncluded(std::string const& includeString,
SourceFileHandleT includerFileHandle,
SourceFileHandleT sourceFileHandle) const;
void RegisterMapping(MappingHandleT mappingHandle) const;
std::string MessageHeader(cm::string_view headerBase) const;
};
/** Evaluate cached file parse data - uic. */
class JobEvalCacheUicT : public JobEvalCacheT
{
void Process() override;
bool EvalFile(SourceFileHandleT const& sourceFileHandle);
bool FindIncludedUi(cm::string_view sourceDirPrefix,
cm::string_view includePrefix);
bool RegisterMapping(std::string const& includeString,
SourceFileHandleT includerFileHandle);
std::string UiName;
SourceFileHandleT UiFileHandle;
};
/** Evaluate cached file parse data - finish */
class JobEvalCacheFinishT : public JobFenceT
{
void Process() override;
};
/** Dependency probing base job. */
class JobProbeDepsT : public JobT
{
};
/** Probes file dependencies and generates moc compile jobs. */
class JobProbeDepsMocT : public JobProbeDepsT
{
void Process() override;
bool Generate(MappingHandleT const& mapping, bool compFile) const;
bool Probe(MappingT const& mapping, std::string* reason) const;
std::pair<std::string, cmFileTime> FindDependency(
std::string const& sourceDir, std::string const& includeString) const;
};
/** Probes file dependencies and generates uic compile jobs. */
class JobProbeDepsUicT : public JobProbeDepsT
{
void Process() override;
bool Probe(MappingT const& mapping, std::string* reason) const;
};
/** Dependency probing finish job. */
class JobProbeDepsFinishT : public JobFenceT
{
void Process() override;
};
/** Meta compiler base job. */
class JobCompileT : public JobT
{
public:
JobCompileT(MappingHandleT uicMapping, std::unique_ptr<std::string> reason)
: Mapping(std::move(uicMapping))
, Reason(std::move(reason))
{
}
protected:
MappingHandleT Mapping;
std::unique_ptr<std::string> Reason;
};
/** moc compiles a file. */
class JobCompileMocT : public JobCompileT
{
public:
JobCompileMocT(MappingHandleT uicMapping,
std::unique_ptr<std::string> reason,
ParseCacheT::FileHandleT cacheEntry)
: JobCompileT(std::move(uicMapping), std::move(reason))
, CacheEntry(std::move(cacheEntry))
{
}
void Process() override;
protected:
ParseCacheT::FileHandleT CacheEntry;
private:
void MaybeWriteMocResponseFile(std::string const& outputFile,
std::vector<std::string>& cmd) const;
};
/** uic compiles a file. */
class JobCompileUicT : public JobCompileT
{
public:
using JobCompileT::JobCompileT;
void Process() override;
};
/** Generate mocs_compilation.cpp. */
class JobMocsCompilationT : public JobFenceT
{
private:
void Process() override;
};
class JobDepFilesMergeT : public JobFenceT
{
private:
std::vector<std::string> initialDependencies() const;
void Process() override;
};
/** @brief The last job. */
class JobFinishT : public JobFenceT
{
private:
void Process() override;
};
// -- Const settings interface
BaseSettingsT const& BaseConst() const { return this->BaseConst_; }
BaseEvalT& BaseEval() { return this->BaseEval_; }
MocSettingsT const& MocConst() const { return this->MocConst_; }
MocEvalT& MocEval() { return this->MocEval_; }
UicSettingsT const& UicConst() const { return this->UicConst_; }
UicEvalT& UicEval() { return this->UicEval_; }
// -- Parallel job processing interface
cmWorkerPool& WorkerPool() { return this->WorkerPool_; }
void AbortError() { this->Abort(true); }
void AbortSuccess() { this->Abort(false); }
// -- Utility
std::string AbsoluteBuildPath(cm::string_view relativePath) const;
std::string AbsoluteIncludePath(cm::string_view relativePath) const;
template <class JOBTYPE>
void CreateParseJobs(SourceFileMapT const& sourceMap);
std::string CollapseFullPathTS(std::string const& path) const;
private:
// -- Abstract processing interface
bool InitFromInfo(InfoT const& info) override;
void InitJobs();
bool Process() override;
// -- Settings file
void SettingsFileRead();
bool SettingsFileWrite();
// -- Parse cache
void ParseCacheRead();
bool ParseCacheWrite();
// -- Thread processing
void Abort(bool error);
// -- Generation
bool CreateDirectories();
// -- Support for depfiles
std::vector<std::string> dependenciesFromDepFile(const char* filePath);
// -- Settings
BaseSettingsT BaseConst_;
BaseEvalT BaseEval_;
MocSettingsT MocConst_;
MocEvalT MocEval_;
UicSettingsT UicConst_;
UicEvalT UicEval_;
// -- Settings file
std::string SettingsFile_;
std::string SettingsStringMoc_;
std::string SettingsStringUic_;
// -- Worker thread pool
std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
cmWorkerPool WorkerPool_;
// -- Concurrent processing
mutable std::mutex CMakeLibMutex_;
};
cmQtAutoMocUicT::IncludeKeyT::IncludeKeyT(std::string const& key,
std::size_t basePrefixLength)
: Key(key)
, Dir(SubDirPrefix(key))
, Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
{
if (basePrefixLength != 0) {
this->Base = this->Base.substr(basePrefixLength);
}
}
void cmQtAutoMocUicT::ParseCacheT::FileT::Clear()
{
this->Moc.Macro.clear();
this->Moc.Include.Underscore.clear();
this->Moc.Include.Dot.clear();
this->Moc.Depends.clear();
this->Uic.Include.clear();
this->Uic.Depends.clear();
}
cmQtAutoMocUicT::ParseCacheT::GetOrInsertT
cmQtAutoMocUicT::ParseCacheT::GetOrInsert(std::string const& fileName)
{
// Find existing entry
{
auto it = this->Map_.find(fileName);
if (it != this->Map_.end()) {
return GetOrInsertT{ it->second, false };
}
}
// Insert new entry
return GetOrInsertT{
this->Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
};
}
cmQtAutoMocUicT::ParseCacheT::ParseCacheT() = default;
cmQtAutoMocUicT::ParseCacheT::~ParseCacheT() = default;
bool cmQtAutoMocUicT::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 = this->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 cmQtAutoMocUicT::ParseCacheT::WriteToFile(std::string const& fileName)
{
cmGeneratedFileStream ofs(fileName);
if (!ofs) {
return false;
}
ofs << "# Generated by CMake. Changes will be overwritten.\n";
for (auto const& pair : this->Map_) {
ofs << pair.first << '\n';
FileT const& file = *pair.second;
if (!file.Moc.Macro.empty()) {
ofs << " mmc:" << file.Moc.Macro << '\n';
}
for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
ofs << " miu:" << item.Key << '\n';
}
for (IncludeKeyT const& item : file.Moc.Include.Dot) {
ofs << " mid:" << item.Key << '\n';
}
for (std::string const& item : file.Moc.Depends) {
ofs << " mdp:" << item << '\n';
}
for (IncludeKeyT const& item : file.Uic.Include) {
ofs << " uic:" << item.Key << '\n';
}
for (std::string const& item : file.Uic.Depends) {
ofs << " udp:" << item << '\n';
}
}
return ofs.Close();
}
cmQtAutoMocUicT::BaseSettingsT::BaseSettingsT() = default;
cmQtAutoMocUicT::BaseSettingsT::~BaseSettingsT() = default;
cmQtAutoMocUicT::MocSettingsT::MocSettingsT()
{
this->RegExpInclude.compile(
"(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
}
cmQtAutoMocUicT::MocSettingsT::~MocSettingsT() = default;
bool cmQtAutoMocUicT::MocSettingsT::skipped(std::string const& fileName) const
{
return (!this->Enabled ||
(this->SkipList.find(fileName) != this->SkipList.end()));
}
std::string cmQtAutoMocUicT::MocSettingsT::MacrosString() const
{
std::string res;
const auto itB = this->MacroFilters.cbegin();
const auto itE = this->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;
}
cmQtAutoMocUicT::UicSettingsT::UicSettingsT()
{
this->RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
}
cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;
bool cmQtAutoMocUicT::UicSettingsT::skipped(std::string const& fileName) const
{
return (!this->Enabled ||
(this->SkipList.find(fileName) != this->SkipList.end()));
}
void cmQtAutoMocUicT::JobT::LogError(GenT genType,
cm::string_view message) const
{
this->Gen()->AbortError();
this->Gen()->Log().Error(genType, message);
}
void cmQtAutoMocUicT::JobT::LogCommandError(
GenT genType, cm::string_view message,
std::vector<std::string> const& command, std::string const& output) const
{
this->Gen()->AbortError();
this->Gen()->Log().ErrorCommand(genType, message, command, output);
}
bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType,
cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command,
std::string* infoMessage)
{
// Log command
if (this->Log().Verbose()) {
cm::string_view info;
if (infoMessage != nullptr) {
info = *infoMessage;
}
this->Log().Info(
genType,
cmStrCat(info, info.empty() || cmHasSuffix(info, '\n') ? "" : "\n",
QuotedCommand(command), '\n'));
}
// Run command
return this->cmWorkerPool::JobT::RunProcess(
result, command, this->BaseConst().AutogenBuildDir);
}
void cmQtAutoMocUicT::JobMocPredefsT::Process()
{
// (Re)generate moc_predefs.h on demand
std::unique_ptr<std::string> reason;
if (this->Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (!this->Update(reason.get())) {
return;
}
std::string const& predefsFileAbs = this->MocConst().PredefsFileAbs;
{
cmWorkerPool::ProcessResultT result;
{
// Compose command
std::vector<std::string> cmd = this->MocConst().PredefsCmd;
// Add definitions
cm::append(cmd, this->MocConst().OptionsDefinitions);
// Add includes
cm::append(cmd, this->MocConst().OptionsIncludes);
// Execute command
if (!this->RunProcess(GenT::MOC, result, cmd, reason.get())) {
this->LogCommandError(GenT::MOC,
cmStrCat("The content generation command for ",
this->MessagePath(predefsFileAbs),
" failed.\n", result.ErrorMessage),
cmd, result.StdOut);
return;
}
}
// (Re)write predefs file only on demand
if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
this->LogError(
GenT::MOC,
cmStrCat("Writing ", this->MessagePath(predefsFileAbs), " failed."));
return;
}
} else {
// Touch to update the time stamp
if (this->Log().Verbose()) {
this->Log().Info(GenT::MOC,
"Touching " + this->MessagePath(predefsFileAbs));
}
if (!cmSystemTools::Touch(predefsFileAbs, false)) {
this->LogError(GenT::MOC,
cmStrCat("Touching ", this->MessagePath(predefsFileAbs),
" failed."));
return;
}
}
}
// Read file time afterwards
if (!this->MocEval().PredefsTime.Load(predefsFileAbs)) {
this->LogError(GenT::MOC,
cmStrCat("Reading the file time of ",
this->MessagePath(predefsFileAbs), " failed."));
return;
}
}
bool cmQtAutoMocUicT::JobMocPredefsT::Update(std::string* reason) const
{
// Test if the file exists
if (!this->MocEval().PredefsTime.Load(this->MocConst().PredefsFileAbs)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ",
this->MessagePath(this->MocConst().PredefsFileAbs),
", because it doesn't exist.");
}
return true;
}
// Test if the settings changed
if (this->MocConst().SettingsChanged) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ",
this->MessagePath(this->MocConst().PredefsFileAbs),
", because the moc settings changed.");
}
return true;
}
// Test if the executable is newer
{
std::string const& exec = this->MocConst().PredefsCmd.at(0);
cmFileTime execTime;
if (execTime.Load(exec)) {
if (this->MocEval().PredefsTime.Older(execTime)) {
if (reason != nullptr) {
*reason = cmStrCat(
"Generating ", this->MessagePath(this->MocConst().PredefsFileAbs),
" because it is older than ", this->MessagePath(exec), '.');
}
return true;
}
}
}
return false;
}
bool cmQtAutoMocUicT::JobParseT::ReadFile()
{
// Clear old parse information
this->FileHandle->ParseData->Clear();
std::string const& fileName = this->FileHandle->FileName;
// Write info
if (this->Log().Verbose()) {
this->Log().Info(GenT::GEN,
cmStrCat("Parsing ", this->MessagePath(fileName)));
}
// Read file content
{
std::string error;
if (!cmQtAutoGenerator::FileRead(this->Content, fileName, &error)) {
this->LogError(GenT::GEN,
cmStrCat("Could not read ", this->MessagePath(fileName),
".\n", error));
return false;
}
}
// Warn if empty
if (this->Content.empty()) {
this->Log().Warning(GenT::GEN,
cmStrCat(this->MessagePath(fileName), " is empty."));
return false;
}
return true;
}
void cmQtAutoMocUicT::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 cmQtAutoMocUicT::JobParseT::MocMacro()
{
for (KeyExpT const& filter : this->MocConst().MacroFilters) {
// Run a simple find string check
if (this->Content.find(filter.Key) == std::string::npos) {
continue;
}
// Run the expensive regular expression check loop
cmsys::RegularExpressionMatch match;
if (filter.Exp.find(this->Content.c_str(), match)) {
// Keep detected macro name
this->FileHandle->ParseData->Moc.Macro = filter.Key;
return;
}
}
}
void cmQtAutoMocUicT::JobParseT::MocDependecies()
{
if (this->MocConst().DependFilters.empty() ||
this->MocConst().CanOutputDependencies) {
return;
}
// Find dependency strings
std::set<std::string> parseDepends;
for (KeyExpT const& filter : this->MocConst().DependFilters) {
// Run a simple find string check
if (this->Content.find(filter.Key) == std::string::npos) {
continue;
}
// Run the expensive regular expression check loop
const char* contentChars = this->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 = this->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 cmQtAutoMocUicT::JobParseT::MocIncludes()
{
if (this->Content.find("moc") == std::string::npos) {
return;
}
std::set<std::string> underscore;
std::set<std::string> dot;
{
const char* contentChars = this->Content.c_str();
cmsys::RegularExpression const& regExp = this->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 = this->FileHandle->ParseData->Moc.Include;
this->CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
this->CreateKeys(Include.Dot, dot, 0);
}
void cmQtAutoMocUicT::JobParseT::UicIncludes()
{
if (this->Content.find("ui_") == std::string::npos) {
return;
}
std::set<std::string> includes;
{
const char* contentChars = this->Content.c_str();
cmsys::RegularExpression const& regExp = this->UicConst().RegExpInclude;
cmsys::RegularExpressionMatch match;
while (regExp.find(contentChars, match)) {
includes.emplace(match.match(2));
// Forward content pointer
contentChars += match.end();
}
}
this->CreateKeys(this->FileHandle->ParseData->Uic.Include, includes,
UiUnderscoreLength);
}
void cmQtAutoMocUicT::JobParseHeaderT::Process()
{
if (!this->ReadFile()) {
return;
}
// Moc parsing
if (this->FileHandle->Moc) {
this->MocMacro();
this->MocDependecies();
}
// Uic parsing
if (this->FileHandle->Uic) {
this->UicIncludes();
}
}
void cmQtAutoMocUicT::JobParseSourceT::Process()
{
if (!this->ReadFile()) {
return;
}
// Moc parsing
if (this->FileHandle->Moc) {
this->MocMacro();
this->MocDependecies();
this->MocIncludes();
}
// Uic parsing
if (this->FileHandle->Uic) {
this->UicIncludes();
}
}
std::string cmQtAutoMocUicT::JobEvalCacheT::MessageSearchLocations() const
{
std::string res;
res.reserve(512);
for (std::string const& path : this->SearchLocations) {
res += " ";
res += this->MessagePath(path);
res += '\n';
}
return res;
}
void cmQtAutoMocUicT::JobEvalCacheMocT::Process()
{
// Evaluate headers
for (auto const& pair : this->BaseEval().Headers) {
if (!this->EvalHeader(pair.second)) {
return;
}
}
// Evaluate sources
for (auto const& pair : this->BaseEval().Sources) {
if (!this->EvalSource(pair.second)) {
return;
}
}
}
bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(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 (this->BaseConst().MultiConfig) {
handle->OutputFile =
this->Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
} else {
handle->OutputFile =
this->Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
}
// Register mapping in headers map
this->RegisterMapping(handle);
}
return true;
}
bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalSource(
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 sourceDirPrefix = 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 = this->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)) {
this->LogError(GenT::MOC,
cmStrCat(this->MessagePath(sourceFile.FileName),
"\ncontains a ", Quoted(parseData.Macro),
" macro, but does not include ",
this->MessagePath(sourceBase + ".moc"),
"!\nConsider to\n - add #include \"", sourceBase,
".moc\"\n - enable SKIP_AUTOMOC for this file"));
return false;
}
// Evaluate "moc_" includes
for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
SourceFileHandleT headerHandle;
{
std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
if (!this->FindIncludedHeader(headerHandle, sourceDirPrefix,
headerBase)) {
this->LogError(
GenT::MOC,
cmStrCat(this->MessagePath(sourceFile.FileName),
"\nincludes the moc file ", this->MessagePath(incKey.Key),
",\nbut a header ", this->MessageHeader(headerBase),
"\ncould not be found "
"in the following directories\n",
this->MessageSearchLocations()));
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
this->Log().Warning(
GenT::MOC,
cmStrCat(this->MessagePath(sourceFile.FileName), "\ncontains a ",
Quoted(parseData.Macro), " macro, but does not include ",
this->MessagePath(sourceBase + ".moc"),
".\nInstead it includes ", this->MessagePath(incKey.Key),
".\nRunning moc on the source\n ",
this->MessagePath(sourceFile.FileName), "!\nBetter include ",
this->MessagePath(sourceBase + ".moc"),
" for compatibility with regular mode.\n",
"This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
// Create mapping
if (!this->RegisterIncluded(incKey.Key, source, source)) {
return false;
}
continue;
}
// Check if header is skipped
if (this->MocConst().skipped(headerHandle->FileName)) {
continue;
}
// Create mapping
if (!this->RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
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 (!this->RegisterIncluded(incKey.Key, source, source)) {
return false;
}
continue;
}
// Try to find a header instead but issue a warning.
// This is for KDE4 compatibility.
SourceFileHandleT headerHandle;
{
std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
if (!this->FindIncludedHeader(headerHandle, sourceDirPrefix,
headerBase)) {
this->LogError(
GenT::MOC,
cmStrCat(
this->MessagePath(sourceFile.FileName),
"\nincludes the moc file ", this->MessagePath(incKey.Key),
",\nwhich seems to be the moc file from a different source "
"file.\nCMAKE_AUTOMOC_RELAXED_MODE:\nAlso a matching header ",
this->MessageHeader(headerBase),
"\ncould not be found in the following directories\n",
this->MessageSearchLocations()));
return false;
}
}
// Check if header is skipped
if (this->MocConst().skipped(headerHandle->FileName)) {
continue;
}
// Issue a warning
if (ownMoc && parseData.Macro.empty()) {
this->Log().Warning(
GenT::MOC,
cmStrCat(
this->MessagePath(sourceFile.FileName), "\nincludes the moc file ",
this->MessagePath(incKey.Key), ", but does not contain a\n",
this->MocConst().MacrosString(),
" macro.\nRunning moc on the header\n ",
this->MessagePath(headerHandle->FileName), "!\nBetter include ",
this->MessagePath("moc_" + incKey.Base + ".cpp"),
" for a compatibility with regular mode.\n",
"This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
} else {
this->Log().Warning(
GenT::MOC,
cmStrCat(
this->MessagePath(sourceFile.FileName), "\nincludes the moc file ",
this->MessagePath(incKey.Key), " instead of ",
this->MessagePath("moc_" + incKey.Base + ".cpp"),
".\nRunning moc on the header\n ",
this->MessagePath(headerHandle->FileName), "!\nBetter include ",
this->MessagePath("moc_" + incKey.Base + ".cpp"),
" for compatibility with regular mode.\n",
"This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
}
// Create mapping
if (!this->RegisterIncluded(incKey.Key, source,
std::move(headerHandle))) {
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
this->LogError(
GenT::MOC,
cmStrCat(this->MessagePath(sourceFile.FileName),
"\nincludes the moc file ", this->MessagePath(incKey.Key),
",\nwhich seems to be the moc file from a different "
"source file.\nThis is not supported. Include ",
this->MessagePath(sourceBase + ".moc"),
" to run moc on this source file."));
return false;
}
// Accept but issue a warning if moc isn't required
if (parseData.Macro.empty()) {
this->Log().Warning(
GenT::MOC,
cmStrCat(this->MessagePath(sourceFile.FileName),
"\nincludes the moc file ", this->MessagePath(incKey.Key),
", but does not contain a ",
this->MocConst().MacrosString(), " macro."));
}
// Create mapping
if (!this->RegisterIncluded(incKey.Key, source, source)) {
return false;
}
}
}
return true;
}
bool cmQtAutoMocUicT::JobEvalCacheMocT::FindIncludedHeader(
SourceFileHandleT& headerHandle, cm::string_view includerDir,
cm::string_view includeBase)
{
// Clear search locations
this->SearchLocations.clear();
auto findHeader = [this,
&headerHandle](std::string const& basePath) -> bool {
bool found = false;
for (std::string const& ext : this->BaseConst().HeaderExtensions) {
std::string const testPath =
this->Gen()->CollapseFullPathTS(cmStrCat(basePath, '.', ext));
cmFileTime fileTime;
if (!fileTime.Load(testPath)) {
// File not found
continue;
}
// Return a known file if it exists already
{
auto it = this->BaseEval().Headers.find(testPath);
if (it != this->BaseEval().Headers.end()) {
headerHandle = it->second;
found = true;
break;
}
}
// Created and return discovered file entry
{
SourceFileHandleT& handle =
this->MocEval().HeadersDiscovered[testPath];
if (!handle) {
handle = std::make_shared<SourceFileT>(testPath);
handle->FileTime = fileTime;
handle->IsHeader = true;
handle->Moc = true;
}
headerHandle = handle;
found = true;
break;
}
}
if (!found) {
this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(basePath));
}
return found;
};
// Search in vicinity of the source
if (findHeader(cmStrCat(includerDir, includeBase))) {
return true;
}
// Search in include directories
auto const& includePaths = this->MocConst().IncludePaths;
return std::any_of(
includePaths.begin(), includePaths.end(),
[&findHeader, &includeBase](std::string const& path) -> bool {
return findHeader(cmStrCat(path, '/', includeBase));
});
}
bool cmQtAutoMocUicT::JobEvalCacheMocT::RegisterIncluded(
std::string const& includeString, SourceFileHandleT includerFileHandle,
SourceFileHandleT sourceFileHandle) const
{
// Check if this file is already included
MappingHandleT& handle = this->MocEval().Includes[includeString];
if (handle) {
// Check if the output file would be generated from different source files
if (handle->SourceFile != sourceFileHandle) {
std::string files =
cmStrCat(" ", this->MessagePath(includerFileHandle->FileName), '\n');
for (auto const& item : handle->IncluderFiles) {
files += cmStrCat(" ", this->MessagePath(item->FileName), '\n');
}
this->LogError(
GenT::MOC,
cmStrCat("The source files\n", files,
"contain the same include string ",
this->MessagePath(includeString),
", but\nthe moc file would be generated from different "
"source files\n ",
this->MessagePath(sourceFileHandle->FileName), " and\n ",
this->MessagePath(handle->SourceFile->FileName),
".\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"));
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 = this->Gen()->AbsoluteIncludePath(includeString);
// Register mapping in sources/headers map
this->RegisterMapping(handle);
return true;
}
void cmQtAutoMocUicT::JobEvalCacheMocT::RegisterMapping(
MappingHandleT mappingHandle) const
{
auto& regMap = mappingHandle->SourceFile->IsHeader
? this->MocEval().HeaderMappings
: this->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);
}
}
}
std::string cmQtAutoMocUicT::JobEvalCacheMocT::MessageHeader(
cm::string_view headerBase) const
{
return this->MessagePath(cmStrCat(
headerBase, ".{", cmJoin(this->BaseConst().HeaderExtensions, ","), '}'));
}
void cmQtAutoMocUicT::JobEvalCacheUicT::Process()
{
// Prepare buffers
this->SearchLocations.reserve((this->UicConst().SearchPaths.size() + 1) * 2);
// Evaluate headers
for (auto const& pair : this->BaseEval().Headers) {
if (!this->EvalFile(pair.second)) {
return;
}
}
// Evaluate sources
for (auto const& pair : this->BaseEval().Sources) {
if (!this->EvalFile(pair.second)) {
return;
}
}
}
bool cmQtAutoMocUicT::JobEvalCacheUicT::EvalFile(
SourceFileHandleT const& sourceFileHandle)
{
SourceFileT const& sourceFile = *sourceFileHandle;
auto const& Include = sourceFile.ParseData->Uic.Include;
if (!sourceFile.Uic || Include.empty()) {
return true;
}
std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
return std::all_of(
Include.begin(), Include.end(),
[this, &sourceDirPrefix, &sourceFile,
&sourceFileHandle](IncludeKeyT const& incKey) -> bool {
// Find .ui file
this->UiName = cmStrCat(incKey.Base, ".ui");
if (!this->FindIncludedUi(sourceDirPrefix, incKey.Dir)) {
this->LogError(
GenT::UIC,
cmStrCat(this->MessagePath(sourceFile.FileName),
"\nincludes the uic file ", this->MessagePath(incKey.Key),
",\nbut the user interface file ",
this->MessagePath(this->UiName),
"\ncould not be found in the following directories\n",
this->MessageSearchLocations()));
return false;
}
// Check if the file is skipped
if (this->UicConst().skipped(this->UiFileHandle->FileName)) {
return true;
}
// Register mapping
return this->RegisterMapping(incKey.Key, sourceFileHandle);
});
}
bool cmQtAutoMocUicT::JobEvalCacheUicT::FindIncludedUi(
cm::string_view sourceDirPrefix, cm::string_view includePrefix)
{
// Clear locations buffer
this->SearchLocations.clear();
auto findUi = [this](std::string const& testPath) -> bool {
std::string const fullPath = this->Gen()->CollapseFullPathTS(testPath);
cmFileTime fileTime;
if (!fileTime.Load(fullPath)) {
this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(fullPath));
return false;
}
// .ui file found in files system!
// Get or create .ui file handle
SourceFileHandleT& handle = this->UicEval().UiFiles[fullPath];
if (!handle) {
// The file wasn't registered, yet
handle = std::make_shared<SourceFileT>(fullPath);
handle->FileTime = fileTime;
}
this->UiFileHandle = handle;
return true;
};
// Vicinity of the source
if (!includePrefix.empty()) {
if (findUi(cmStrCat(sourceDirPrefix, includePrefix, this->UiName))) {
return true;
}
}
if (findUi(cmStrCat(sourceDirPrefix, this->UiName))) {
return true;
}
// Additional AUTOUIC search paths
auto const& searchPaths = this->UicConst().SearchPaths;
if (!searchPaths.empty()) {
for (std::string const& sPath : searchPaths) {
if (findUi(cmStrCat(sPath, '/', this->UiName))) {
return true;
}
}
if (!includePrefix.empty()) {
for (std::string const& sPath : searchPaths) {
if (findUi(cmStrCat(sPath, '/', includePrefix, this->UiName))) {
return true;
}
}
}
}
return false;
}
bool cmQtAutoMocUicT::JobEvalCacheUicT::RegisterMapping(
std::string const& includeString, SourceFileHandleT includerFileHandle)
{
auto& Includes = this->Gen()->UicEval().Includes;
auto it = Includes.find(includeString);
if (it != Includes.end()) {
MappingHandleT const& handle = it->second;
if (handle->SourceFile != this->UiFileHandle) {
// The output file already gets generated - from a different .ui file!
std::string files =
cmStrCat(" ", this->MessagePath(includerFileHandle->FileName), '\n');
for (auto const& item : handle->IncluderFiles) {
files += cmStrCat(" ", this->MessagePath(item->FileName), '\n');
}
this->LogError(
GenT::UIC,
cmStrCat(
"The source files\n", files, "contain the same include string ",
Quoted(includeString),
", but\nthe uic file would be generated from different "
"user interface files\n ",
this->MessagePath(this->UiFileHandle->FileName), " and\n ",
this->MessagePath(handle->SourceFile->FileName),
".\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"));
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 = this->UiFileHandle;
handle->OutputFile = this->Gen()->AbsoluteIncludePath(includeString);
// Register mapping
Includes.emplace(includeString, std::move(handle));
}
return true;
}
void cmQtAutoMocUicT::JobEvalCacheFinishT::Process()
{
// Add discovered header parse jobs
this->Gen()->CreateParseJobs<JobParseHeaderT>(
this->MocEval().HeadersDiscovered);
// Add dependency probing jobs
{
// Add fence job to ensure all parsing has finished
this->Gen()->WorkerPool().EmplaceJob<JobFenceT>();
if (this->MocConst().Enabled) {
this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsMocT>();
}
if (this->UicConst().Enabled) {
this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsUicT>();
}
// Add probe finish job
this->Gen()->WorkerPool().EmplaceJob<JobProbeDepsFinishT>();
}
}
void cmQtAutoMocUicT::JobProbeDepsMocT::Process()
{
// Create moc header jobs
for (auto const& pair : this->MocEval().HeaderMappings) {
// Register if this mapping is a candidate for mocs_compilation.cpp
bool const compFile = pair.second->IncludeString.empty();
if (compFile) {
this->MocEval().CompFiles.emplace_back(
pair.second->SourceFile->BuildPath);
}
if (!this->Generate(pair.second, compFile)) {
return;
}
}
// Create moc source jobs
for (auto const& pair : this->MocEval().SourceMappings) {
if (!this->Generate(pair.second, false)) {
return;
}
}
}
bool cmQtAutoMocUicT::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
bool compFile) const
{
std::unique_ptr<std::string> reason;
if (this->Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (this->Probe(*mapping, reason.get())) {
// Register the parent directory for creation
this->MocEval().OutputDirs.emplace(
cmQtAutoGen::ParentDir(mapping->OutputFile));
// Fetch the cache entry for the source file
std::string const& sourceFile = mapping->SourceFile->FileName;
ParseCacheT::GetOrInsertT cacheEntry =
this->BaseEval().ParseCache.GetOrInsert(sourceFile);
// Add moc job
this->Gen()->WorkerPool().EmplaceJob<JobCompileMocT>(
mapping, std::move(reason), std::move(cacheEntry.first));
// Check if a moc job for a mocs_compilation.cpp entry was generated
if (compFile) {
this->MocEval().CompUpdated = true;
}
}
return true;
}
bool cmQtAutoMocUicT::JobProbeDepsMocT::Probe(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 = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it doesn't exist, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if any setting changed
if (this->MocConst().SettingsChanged) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because the uic settings changed, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if the source file is newer
if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it's older than its source file, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if the moc_predefs file is newer
if (!this->MocConst().PredefsFileAbs.empty()) {
if (outputFileTime.Older(this->MocEval().PredefsTime)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it's older than ",
this->MessagePath(this->MocConst().PredefsFileAbs),
", from ", this->MessagePath(sourceFile));
}
return true;
}
}
// Test if the moc executable is newer
if (outputFileTime.Older(this->MocConst().ExecutableTime)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it's older than the moc executable, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if a dependency file is newer
{
// Check dependency timestamps
std::string const sourceDir = SubDirPrefix(sourceFile);
auto& dependencies = mapping.SourceFile->ParseData->Moc.Depends;
for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
auto& dep = *it;
// Find dependency file
auto const depMatch = this->FindDependency(sourceDir, dep);
if (depMatch.first.empty()) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
" from ", this->MessagePath(sourceFile),
", because its dependency ",
this->MessagePath(dep), " vanished.");
}
dependencies.erase(it);
this->BaseEval().ParseCacheChanged = true;
return true;
}
// Test if dependency file is older
if (outputFileTime.Older(depMatch.second)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it's older than its dependency file ",
this->MessagePath(depMatch.first), ", from ",
this->MessagePath(sourceFile));
}
return true;
}
}
}
return false;
}
std::pair<std::string, cmFileTime>
cmQtAutoMocUicT::JobProbeDepsMocT::FindDependency(
std::string const& sourceDir, std::string const& includeString) const
{
using ResPair = std::pair<std::string, cmFileTime>;
// moc's dependency file contains absolute paths
if (this->MocConst().CanOutputDependencies) {
ResPair res{ includeString, {} };
if (res.second.Load(res.first)) {
return res;
}
return {};
}
// 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 : this->MocConst().IncludePaths) {
ResPair res{ cmStrCat(includePath, '/', includeString), {} };
if (res.second.Load(res.first)) {
return res;
}
}
// Return empty
return ResPair();
}
void cmQtAutoMocUicT::JobProbeDepsUicT::Process()
{
for (auto const& pair : this->Gen()->UicEval().Includes) {
MappingHandleT const& mapping = pair.second;
std::unique_ptr<std::string> reason;
if (this->Log().Verbose()) {
reason = cm::make_unique<std::string>();
}
if (!this->Probe(*mapping, reason.get())) {
continue;
}
// Register the parent directory for creation
this->UicEval().OutputDirs.emplace(
cmQtAutoGen::ParentDir(mapping->OutputFile));
// Add uic job
this->Gen()->WorkerPool().EmplaceJob<JobCompileUicT>(mapping,
std::move(reason));
}
}
bool cmQtAutoMocUicT::JobProbeDepsUicT::Probe(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 = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it doesn't exist, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if the uic settings changed
if (this->UicConst().SettingsChanged) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because the uic settings changed, from ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if the source file is newer
if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
" because it's older than the source file ",
this->MessagePath(sourceFile));
}
return true;
}
// Test if the uic executable is newer
if (outputFileTime.Older(this->UicConst().ExecutableTime)) {
if (reason != nullptr) {
*reason = cmStrCat("Generating ", this->MessagePath(outputFile),
", because it's older than the uic executable, from ",
this->MessagePath(sourceFile));
}
return true;
}
return false;
}
void cmQtAutoMocUicT::JobProbeDepsFinishT::Process()
{
// Create output directories
{
using StringSet = std::unordered_set<std::string>;
auto createDirs = [this](GenT genType, StringSet const& dirSet) {
for (std::string const& dirName : dirSet) {
if (!cmSystemTools::MakeDirectory(dirName)) {
this->LogError(genType,
cmStrCat("Creating directory ",
this->MessagePath(dirName), " failed."));
return;
}
}
};
if (this->MocConst().Enabled && this->UicConst().Enabled) {
StringSet outputDirs = this->MocEval().OutputDirs;
outputDirs.insert(this->UicEval().OutputDirs.begin(),
this->UicEval().OutputDirs.end());
createDirs(GenT::GEN, outputDirs);
} else if (this->MocConst().Enabled) {
createDirs(GenT::MOC, this->MocEval().OutputDirs);
} else if (this->UicConst().Enabled) {
createDirs(GenT::UIC, this->UicEval().OutputDirs);
}
}
if (this->MocConst().Enabled) {
// Add mocs compilations job
this->Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
}
if (!this->BaseConst().DepFile.empty()) {
// Add job to merge dep files
this->Gen()->WorkerPool().EmplaceJob<JobDepFilesMergeT>();
}
// Add finish job
this->Gen()->WorkerPool().EmplaceJob<JobFinishT>();
}
void cmQtAutoMocUicT::JobCompileMocT::Process()
{
std::string const& sourceFile = this->Mapping->SourceFile->FileName;
std::string const& outputFile = this->Mapping->OutputFile;
// Compose moc command
std::vector<std::string> cmd;
{
// Reserve large enough
cmd.reserve(this->MocConst().OptionsDefinitions.size() +
this->MocConst().OptionsIncludes.size() +
this->MocConst().OptionsExtra.size() + 16);
cmd.push_back(this->MocConst().Executable);
// Add definitions
cm::append(cmd, this->MocConst().OptionsDefinitions);
// Add includes
cm::append(cmd, this->MocConst().OptionsIncludes);
// Add predefs include
if (!this->MocConst().PredefsFileAbs.empty()) {
cmd.emplace_back("--include");
cmd.push_back(this->MocConst().PredefsFileAbs);
}
// Add path prefix on demand
if (this->MocConst().PathPrefix && this->Mapping->SourceFile->IsHeader) {
for (std::string const& dir : this->MocConst().IncludePaths) {
cm::string_view prefix = sourceFile;
if (cmHasPrefix(prefix, dir)) {
prefix.remove_prefix(dir.size());
if (cmHasPrefix(prefix, '/')) {
prefix.remove_prefix(1);
auto slashPos = prefix.rfind('/');
if (slashPos != cm::string_view::npos) {
cmd.emplace_back("-p");
cmd.emplace_back(prefix.substr(0, slashPos));
} else {
cmd.emplace_back("-p");
cmd.emplace_back("./");
}
break;
}
}
}
}
// Add extra options
cm::append(cmd, this->MocConst().OptionsExtra);
if (this->MocConst().CanOutputDependencies) {
cmd.emplace_back("--output-dep-file");
}
// Add output file
cmd.emplace_back("-o");
cmd.push_back(outputFile);
// Add source file
cmd.push_back(sourceFile);
MaybeWriteMocResponseFile(outputFile, cmd);
}
// Execute moc command
cmWorkerPool::ProcessResultT result;
if (!this->RunProcess(GenT::MOC, result, cmd, this->Reason.get())) {
// Moc command failed
std::string includers;
if (!this->Mapping->IncluderFiles.empty()) {
includers = "included by\n";
for (auto const& item : this->Mapping->IncluderFiles) {
includers += cmStrCat(" ", this->MessagePath(item->FileName), '\n');
}
}
this->LogCommandError(GenT::MOC,
cmStrCat("The moc process failed to compile\n ",
this->MessagePath(sourceFile), "\ninto\n ",
this->MessagePath(outputFile), '\n',
includers, result.ErrorMessage),
cmd, result.StdOut);
return;
}
// Moc command success. Print moc output.
if (!result.StdOut.empty()) {
this->Log().Info(GenT::MOC, result.StdOut);
}
// Extract dependencies from the dep file moc generated for us
if (this->MocConst().CanOutputDependencies) {
const std::string depfile = outputFile + ".d";
if (this->Log().Verbose()) {
this->Log().Info(
GenT::MOC, "Reading dependencies from " + this->MessagePath(depfile));
}
if (!cmSystemTools::FileExists(depfile)) {
this->Log().Warning(GenT::MOC,
"Dependency file " + this->MessagePath(depfile) +
" does not exist.");
return;
}
this->CacheEntry->Moc.Depends =
this->Gen()->dependenciesFromDepFile(depfile.c_str());
}
}
/*
* Check if command line exceeds maximum length supported by OS
* (if on Windows) and switch to using a response file instead.
*/
void cmQtAutoMocUicT::JobCompileMocT::MaybeWriteMocResponseFile(
std::string const& outputFile, std::vector<std::string>& cmd) const
{
#ifdef _WIN32
// Ensure cmd is less than CommandLineLengthMax characters
size_t commandLineLength = cmd.size(); // account for separating spaces
for (std::string const& str : cmd) {
commandLineLength += str.length();
}
if (commandLineLength >= CommandLineLengthMax) {
// Command line exceeds maximum size allowed by OS
// => create response file
std::string const responseFile = cmStrCat(outputFile, ".rsp");
cmsys::ofstream fout(responseFile.c_str());
if (!fout) {
this->LogError(
GenT::MOC,
cmStrCat("AUTOMOC was unable to create a response file at\n ",
this->MessagePath(responseFile)));
return;
}
auto it = cmd.begin();
while (++it != cmd.end()) {
fout << *it << "\n";
}
fout.close();
// Keep all but executable
cmd.resize(1);
// Specify response file
cmd.push_back(cmStrCat('@', responseFile));
}
#else
static_cast<void>(outputFile);
static_cast<void>(cmd);
#endif
}
void cmQtAutoMocUicT::JobCompileUicT::Process()
{
std::string const& sourceFile = this->Mapping->SourceFile->FileName;
std::string const& outputFile = this->Mapping->OutputFile;
// Compose uic command
std::vector<std::string> cmd;
cmd.push_back(this->UicConst().Executable);
{
std::vector<std::string> allOpts = this->UicConst().Options;
auto optionIt = this->UicConst().UiFiles.find(sourceFile);
if (optionIt != this->UicConst().UiFiles.end()) {
UicMergeOptions(allOpts, optionIt->second.Options,
(this->BaseConst().QtVersion.Major >= 5));
}
cm::append(cmd, allOpts);
}
cmd.emplace_back("-o");
cmd.emplace_back(outputFile);
cmd.emplace_back(sourceFile);
cmWorkerPool::ProcessResultT result;
if (this->RunProcess(GenT::UIC, result, cmd, this->Reason.get())) {
// Uic command success
// Print uic output
if (!result.StdOut.empty()) {
this->Log().Info(GenT::UIC, result.StdOut);
}
} else {
// Uic command failed
std::string includers;
for (auto const& item : this->Mapping->IncluderFiles) {
includers += cmStrCat(" ", this->MessagePath(item->FileName), '\n');
}
this->LogCommandError(GenT::UIC,
cmStrCat("The uic process failed to compile\n ",
this->MessagePath(sourceFile), "\ninto\n ",
this->MessagePath(outputFile),
"\nincluded by\n", includers,
result.ErrorMessage),
cmd, result.StdOut);
}
}
void cmQtAutoMocUicT::JobMocsCompilationT::Process()
{
std::string const& compAbs = this->MocConst().CompFileAbs;
// Compose mocs compilation file content
std::string content =
"// This file is autogenerated. Changes will be overwritten.\n";
if (this->MocEval().CompFiles.empty()) {
// Placeholder content
content += "// No files found that require moc or the moc files are "
"included\n"
"enum some_compilers { need_more_than_nothing };\n";
} else {
// Valid content
const bool mc = this->BaseConst().MultiConfig;
cm::string_view const wrapFront = mc ? "#include <" : "#include \"";
cm::string_view const wrapBack = mc ? ">\n" : "\"\n";
content += cmWrap(wrapFront, this->MocEval().CompFiles, wrapBack, "");
}
if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
// Actually write mocs compilation file
if (this->Log().Verbose()) {
this->Log().Info(
GenT::MOC, "Generating MOC compilation " + this->MessagePath(compAbs));
}
if (!FileWrite(compAbs, content)) {
this->LogError(GenT::MOC,
cmStrCat("Writing MOC compilation ",
this->MessagePath(compAbs), " failed."));
}
} else if (this->MocEval().CompUpdated) {
// Only touch mocs compilation file
if (this->Log().Verbose()) {
this->Log().Info(
GenT::MOC, "Touching MOC compilation " + this->MessagePath(compAbs));
}
if (!cmSystemTools::Touch(compAbs, false)) {
this->LogError(GenT::MOC,
cmStrCat("Touching MOC compilation ",
this->MessagePath(compAbs), " failed."));
}
}
}
/*
* Escapes paths for Ninja depfiles.
* This is a re-implementation of what moc does when writing depfiles.
*/
std::string escapeDependencyPath(cm::string_view path)
{
std::string escapedPath;
escapedPath.reserve(path.size());
const size_t s = path.size();
int backslashCount = 0;
for (size_t i = 0; i < s; ++i) {
if (path[i] == '\\') {
++backslashCount;
} else {
if (path[i] == '$') {
escapedPath.push_back('$');
} else if (path[i] == '#') {
escapedPath.push_back('\\');
} else if (path[i] == ' ') {
// Double the amount of written backslashes,
// and add one more to escape the space.
while (backslashCount-- >= 0) {
escapedPath.push_back('\\');
}
}
backslashCount = 0;
}
escapedPath.push_back(path[i]);
}
return escapedPath;
}
/*
* Return the initial dependencies of the merged depfile.
* Those are dependencies from the project files, not from moc runs.
*/
std::vector<std::string>
cmQtAutoMocUicT::JobDepFilesMergeT::initialDependencies() const
{
std::vector<std::string> dependencies;
dependencies.reserve(this->BaseConst().ListFiles.size() +
this->BaseEval().Headers.size() +
this->BaseEval().Sources.size());
cm::append(dependencies, this->BaseConst().ListFiles);
auto append_file_path =
[&dependencies](const SourceFileMapT::value_type& p) {
dependencies.push_back(p.first);
};
std::for_each(this->BaseEval().Headers.begin(),
this->BaseEval().Headers.end(), append_file_path);
std::for_each(this->BaseEval().Sources.begin(),
this->BaseEval().Sources.end(), append_file_path);
return dependencies;
}
void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
{
if (this->Log().Verbose()) {
this->Log().Info(
GenT::MOC,
cmStrCat("Merging MOC dependencies into ",
this->MessagePath(this->BaseConst().DepFile.c_str())));
}
auto processDepFile =
[this](const std::string& mocOutputFile) -> std::vector<std::string> {
std::string f = mocOutputFile + ".d";
if (!cmSystemTools::FileExists(f)) {
return {};
}
return this->Gen()->dependenciesFromDepFile(f.c_str());
};
std::vector<std::string> dependencies = this->initialDependencies();
ParseCacheT& parseCache = this->BaseEval().ParseCache;
auto processMappingEntry = [&](const MappingMapT::value_type& m) {
auto cacheEntry = parseCache.GetOrInsert(m.first);
if (cacheEntry.first->Moc.Depends.empty()) {
cacheEntry.first->Moc.Depends = processDepFile(m.second->OutputFile);
}
dependencies.insert(dependencies.end(),
cacheEntry.first->Moc.Depends.begin(),
cacheEntry.first->Moc.Depends.end());
};
std::for_each(this->MocEval().HeaderMappings.begin(),
this->MocEval().HeaderMappings.end(), processMappingEntry);
std::for_each(this->MocEval().SourceMappings.begin(),
this->MocEval().SourceMappings.end(), processMappingEntry);
// Remove SKIP_AUTOMOC files.
// Also remove AUTOUIC header files to avoid cyclic dependency.
dependencies.erase(
std::remove_if(dependencies.begin(), dependencies.end(),
[this](const std::string& dep) {
return this->MocConst().skipped(dep) ||
std::any_of(
this->UicEval().Includes.begin(),
this->UicEval().Includes.end(),
[&dep](MappingMapT::value_type const& mapping) {
return dep == mapping.second->OutputFile;
});
}),
dependencies.end());
// Remove duplicates to make the depfile smaller
std::sort(dependencies.begin(), dependencies.end());
dependencies.erase(std::unique(dependencies.begin(), dependencies.end()),
dependencies.end());
// Add form files
for (const auto& uif : this->UicEval().UiFiles) {
dependencies.push_back(uif.first);
}
// Write the file
cmsys::ofstream ofs;
ofs.open(this->BaseConst().DepFile.c_str(),
(std::ios::out | std::ios::binary | std::ios::trunc));
if (!ofs) {
this->LogError(GenT::GEN,
cmStrCat("Cannot open ",
this->MessagePath(this->BaseConst().DepFile),
" for writing."));
return;
}
ofs << this->BaseConst().DepFileRuleName << ": \\\n";
for (const std::string& file : dependencies) {
ofs << '\t' << escapeDependencyPath(file) << " \\\n";
if (!ofs.good()) {
this->LogError(GenT::GEN,
cmStrCat("Writing depfile",
this->MessagePath(this->BaseConst().DepFile),
" failed."));
return;
}
}
// Add the CMake executable to re-new cache data if necessary.
// Also, this is the last entry, so don't add a backslash.
ofs << '\t' << escapeDependencyPath(this->BaseConst().CMakeExecutable)
<< '\n';
}
void cmQtAutoMocUicT::JobFinishT::Process()
{
this->Gen()->AbortSuccess();
}
cmQtAutoMocUicT::cmQtAutoMocUicT()
: cmQtAutoGenerator(GenT::GEN)
{
}
cmQtAutoMocUicT::~cmQtAutoMocUicT() = default;
bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
{
// -- Required settings
if (!info.GetBool("MULTI_CONFIG", this->BaseConst_.MultiConfig, true) ||
!info.GetUInt("QT_VERSION_MAJOR", this->BaseConst_.QtVersion.Major,
true) ||
!info.GetUInt("QT_VERSION_MINOR", this->BaseConst_.QtVersion.Minor,
true) ||
!info.GetUInt("PARALLEL", this->BaseConst_.ThreadCount, false) ||
!info.GetString("BUILD_DIR", this->BaseConst_.AutogenBuildDir, true) ||
!info.GetStringConfig("INCLUDE_DIR", this->BaseConst_.AutogenIncludeDir,
true) ||
!info.GetString("CMAKE_EXECUTABLE", this->BaseConst_.CMakeExecutable,
true) ||
!info.GetStringConfig("PARSE_CACHE_FILE",
this->BaseConst_.ParseCacheFile, true) ||
!info.GetString("DEP_FILE", this->BaseConst_.DepFile, false) ||
!info.GetString("DEP_FILE_RULE_NAME", this->BaseConst_.DepFileRuleName,
false) ||
!info.GetStringConfig("SETTINGS_FILE", this->SettingsFile_, true) ||
!info.GetArray("CMAKE_LIST_FILES", this->BaseConst_.ListFiles, true) ||
!info.GetArray("HEADER_EXTENSIONS", this->BaseConst_.HeaderExtensions,
true) ||
!info.GetString("QT_MOC_EXECUTABLE", this->MocConst_.Executable,
false) ||
!info.GetString("QT_UIC_EXECUTABLE", this->UicConst_.Executable,
false)) {
return false;
}
// -- Checks
if (!this->BaseConst_.CMakeExecutableTime.Load(
this->BaseConst_.CMakeExecutable)) {
return info.LogError(
cmStrCat("The CMake executable ",
this->MessagePath(this->BaseConst_.CMakeExecutable),
" does not exist."));
}
// -- Evaluate values
this->BaseConst_.ThreadCount =
std::min(this->BaseConst_.ThreadCount, ParallelMax);
this->WorkerPool_.SetThreadCount(this->BaseConst_.ThreadCount);
// -- Moc
if (!this->MocConst_.Executable.empty()) {
// -- Moc is enabled
this->MocConst_.Enabled = true;
// -- Temporary buffers
struct
{
std::vector<std::string> MacroNames;
std::vector<std::string> DependFilters;
} tmp;
// -- Required settings
if (!info.GetBool("MOC_RELAXED_MODE", this->MocConst_.RelaxedMode,
false) ||
!info.GetBool("MOC_PATH_PREFIX", this->MocConst_.PathPrefix, true) ||
!info.GetArray("MOC_SKIP", this->MocConst_.SkipList, false) ||
!info.GetArrayConfig("MOC_DEFINITIONS", this->MocConst_.Definitions,
false) ||
!info.GetArrayConfig("MOC_INCLUDES", this->MocConst_.IncludePaths,
false) ||
!info.GetArray("MOC_OPTIONS", this->MocConst_.OptionsExtra, false) ||
!info.GetStringConfig("MOC_COMPILATION_FILE",
this->MocConst_.CompFileAbs, true) ||
!info.GetArray("MOC_PREDEFS_CMD", this->MocConst_.PredefsCmd, false) ||
!info.GetStringConfig("MOC_PREDEFS_FILE",
this->MocConst_.PredefsFileAbs,
!this->MocConst_.PredefsCmd.empty()) ||
!info.GetArray("MOC_MACRO_NAMES", tmp.MacroNames, true) ||
!info.GetArray("MOC_DEPEND_FILTERS", tmp.DependFilters, false)) {
return false;
}
// -- Evaluate settings
for (std::string const& item : tmp.MacroNames) {
this->MocConst_.MacroFilters.emplace_back(
item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
}
// Can moc output dependencies or do we need to setup dependency filters?
if (this->BaseConst_.QtVersion >= IntegerVersion(5, 15)) {
this->MocConst_.CanOutputDependencies = true;
} else {
Json::Value const& val = info.GetValue("MOC_DEPEND_FILTERS");
if (!val.isArray()) {
return info.LogError("MOC_DEPEND_FILTERS JSON value is not an array.");
}
Json::ArrayIndex const arraySize = val.size();
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
// Test entry closure
auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
if (!test) {
info.LogError(
cmStrCat("MOC_DEPEND_FILTERS filter ", ii, ": ", msg));
}
return !test;
};
Json::Value const& pairVal = val[ii];
if (testEntry(pairVal.isArray(), "JSON value is not an array.") ||
testEntry(pairVal.size() == 2, "JSON array size invalid.")) {
return false;
}
Json::Value const& keyVal = pairVal[0u];
Json::Value const& expVal = pairVal[1u];
if (testEntry(keyVal.isString(),
"JSON value for keyword is not a string.") ||
testEntry(expVal.isString(),
"JSON value for regular expression is not a string.")) {
return false;
}
std::string const key = keyVal.asString();
std::string const exp = expVal.asString();
if (testEntry(!key.empty(), "Keyword is empty.") ||
testEntry(!exp.empty(), "Regular expression is empty.")) {
return false;
}
this->MocConst_.DependFilters.emplace_back(key, exp);
if (testEntry(
this->MocConst_.DependFilters.back().Exp.is_valid(),
cmStrCat("Regular expression compilation failed.\nKeyword: ",
Quoted(key), "\nExpression: ", Quoted(exp)))) {
return false;
}
}
}
// Check if moc executable exists (by reading the file time)
if (!this->MocConst_.ExecutableTime.Load(this->MocConst_.Executable)) {
return info.LogError(cmStrCat(
"The moc executable ", this->MessagePath(this->MocConst_.Executable),
" does not exist."));
}
}
// -- Uic
if (!this->UicConst_.Executable.empty()) {
// Uic is enabled
this->UicConst_.Enabled = true;
// -- Required settings
if (!info.GetArray("UIC_SKIP", this->UicConst_.SkipList, false) ||
!info.GetArray("UIC_SEARCH_PATHS", this->UicConst_.SearchPaths,
false) ||
!info.GetArrayConfig("UIC_OPTIONS", this->UicConst_.Options, false)) {
return false;
}
// .ui files
{
Json::Value const& val = info.GetValue("UIC_UI_FILES");
if (!val.isArray()) {
return info.LogError("UIC_UI_FILES JSON value is not an array.");
}
Json::ArrayIndex const arraySize = val.size();
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
// Test entry closure
auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
if (!test) {
info.LogError(cmStrCat("UIC_UI_FILES entry ", ii, ": ", msg));
}
return !test;
};
Json::Value const& entry = val[ii];
if (testEntry(entry.isArray(), "JSON value is not an array.") ||
testEntry(entry.size() == 2, "JSON array size invalid.")) {
return false;
}
Json::Value const& entryName = entry[0u];
Json::Value const& entryOptions = entry[1u];
if (testEntry(entryName.isString(),
"JSON value for name is not a string.") ||
testEntry(entryOptions.isArray(),
"JSON value for options is not an array.")) {
return false;
}
auto& uiFile = this->UicConst_.UiFiles[entryName.asString()];
InfoT::GetJsonArray(uiFile.Options, entryOptions);
}
}
// -- Evaluate settings
// Check if uic executable exists (by reading the file time)
if (!this->UicConst_.ExecutableTime.Load(this->UicConst_.Executable)) {
return info.LogError(cmStrCat(
"The uic executable ", this->MessagePath(this->UicConst_.Executable),
" does not exist."));
}
}
// -- Headers
{
Json::Value const& val = info.GetValue("HEADERS");
if (!val.isArray()) {
return info.LogError("HEADERS JSON value is not an array.");
}
Json::ArrayIndex const arraySize = val.size();
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
// Test entry closure
auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
if (!test) {
info.LogError(cmStrCat("HEADERS entry ", ii, ": ", msg));
}
return !test;
};
Json::Value const& entry = val[ii];
if (testEntry(entry.isArray(), "JSON value is not an array.") ||
testEntry(entry.size() == 4, "JSON array size invalid.")) {
return false;
}
Json::Value const& entryName = entry[0u];
Json::Value const& entryFlags = entry[1u];
Json::Value const& entryBuild = entry[2u];
Json::Value const& entryConfigs = entry[3u];
if (testEntry(entryName.isString(),
"JSON value for name is not a string.") ||
testEntry(entryFlags.isString(),
"JSON value for flags is not a string.") ||
testEntry(entryConfigs.isNull() || entryConfigs.isArray(),
"JSON value for configs is not null or array.") ||
testEntry(entryBuild.isString(),
"JSON value for build path is not a string.")) {
return false;
}
std::string name = entryName.asString();
std::string flags = entryFlags.asString();
std::string build = entryBuild.asString();
if (testEntry(flags.size() == 2, "Invalid flags string size")) {
return false;
}
if (entryConfigs.isArray()) {
bool configFound = false;
Json::ArrayIndex const configArraySize = entryConfigs.size();
for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) {
Json::Value const& config = entryConfigs[ci];
if (testEntry(config.isString(),
"JSON value in config array is not a string.")) {
return false;
}
configFound = configFound || config.asString() == this->InfoConfig();
}
if (!configFound) {
continue;
}
}
cmFileTime fileTime;
if (!fileTime.Load(name)) {
return info.LogError(cmStrCat(
"The header file ", this->MessagePath(name), " does not exist."));
}
SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
sourceHandle->FileTime = fileTime;
sourceHandle->IsHeader = true;
sourceHandle->Moc = (flags[0] == 'M');
sourceHandle->Uic = (flags[1] == 'U');
if (sourceHandle->Moc && this->MocConst().Enabled) {
if (build.empty()) {
return info.LogError(
cmStrCat("Header file ", ii, " build path is empty"));
}
sourceHandle->BuildPath = std::move(build);
}
this->BaseEval().Headers.emplace(std::move(name),
std::move(sourceHandle));
}
}
// -- Sources
{
Json::Value const& val = info.GetValue("SOURCES");
if (!val.isArray()) {
return info.LogError("SOURCES JSON value is not an array.");
}
Json::ArrayIndex const arraySize = val.size();
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
// Test entry closure
auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
if (!test) {
info.LogError(cmStrCat("SOURCES entry ", ii, ": ", msg));
}
return !test;
};
Json::Value const& entry = val[ii];
if (testEntry(entry.isArray(), "JSON value is not an array.") ||
testEntry(entry.size() == 3, "JSON array size invalid.")) {
return false;
}
Json::Value const& entryName = entry[0u];
Json::Value const& entryFlags = entry[1u];
Json::Value const& entryConfigs = entry[2u];
if (testEntry(entryName.isString(),
"JSON value for name is not a string.") ||
testEntry(entryFlags.isString(),
"JSON value for flags is not a string.") ||
testEntry(entryConfigs.isNull() || entryConfigs.isArray(),
"JSON value for configs is not null or array.")) {
return false;
}
std::string name = entryName.asString();
std::string flags = entryFlags.asString();
if (testEntry(flags.size() == 2, "Invalid flags string size")) {
return false;
}
if (entryConfigs.isArray()) {
bool configFound = false;
Json::ArrayIndex const configArraySize = entryConfigs.size();
for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) {
Json::Value const& config = entryConfigs[ci];
if (testEntry(config.isString(),
"JSON value in config array is not a string.")) {
return false;
}
configFound = configFound || config.asString() == this->InfoConfig();
}
if (!configFound) {
continue;
}
}
cmFileTime fileTime;
if (!fileTime.Load(name)) {
return info.LogError(cmStrCat(
"The source file ", this->MessagePath(name), " does not exist."));
}
SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
sourceHandle->FileTime = fileTime;
sourceHandle->IsHeader = false;
sourceHandle->Moc = (flags[0] == 'M');
sourceHandle->Uic = (flags[1] == 'U');
this->BaseEval().Sources.emplace(std::move(name),
std::move(sourceHandle));
}
}
// -- Init derived information
// Moc variables
if (this->MocConst().Enabled) {
// Compose moc includes list
{
// Compute framework paths
std::set<std::string> frameworkPaths;
for (std::string const& path : this->MocConst().IncludePaths) {
// 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));
}
}
// Reserve options
this->MocConst_.OptionsIncludes.reserve(
this->MocConst().IncludePaths.size() + frameworkPaths.size() * 2);
// Append includes
for (std::string const& path : this->MocConst().IncludePaths) {
this->MocConst_.OptionsIncludes.emplace_back("-I" + path);
}
// Append framework includes
for (std::string const& path : frameworkPaths) {
this->MocConst_.OptionsIncludes.emplace_back("-F");
this->MocConst_.OptionsIncludes.push_back(path);
}
}
// Compose moc definitions list
{
this->MocConst_.OptionsDefinitions.reserve(
this->MocConst().Definitions.size());
for (std::string const& def : this->MocConst().Definitions) {
this->MocConst_.OptionsDefinitions.emplace_back("-D" + def);
}
}
}
return true;
}
template <class JOBTYPE>
void cmQtAutoMocUicT::CreateParseJobs(SourceFileMapT const& sourceMap)
{
cmFileTime const parseCacheTime = this->BaseEval().ParseCacheTime;
ParseCacheT& parseCache = this->BaseEval().ParseCache;
for (const 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)) {
this->BaseEval().ParseCacheChanged = true;
this->WorkerPool().EmplaceJob<JOBTYPE>(src.second);
}
}
}
/** Concurrently callable implementation of cmSystemTools::CollapseFullPath */
std::string cmQtAutoMocUicT::CollapseFullPathTS(std::string const& path) const
{
std::lock_guard<std::mutex> guard(this->CMakeLibMutex_);
#if defined(__NVCOMPILER) || defined(__LCC__)
static_cast<void>(guard); // convince compiler var is used
#endif
return cmSystemTools::CollapseFullPath(path,
this->ProjectDirs().CurrentSource);
}
void cmQtAutoMocUicT::InitJobs()
{
// Add moc_predefs.h job
if (this->MocConst().Enabled && !this->MocConst().PredefsCmd.empty()) {
this->WorkerPool().EmplaceJob<JobMocPredefsT>();
}
// Add header parse jobs
this->CreateParseJobs<JobParseHeaderT>(this->BaseEval().Headers);
// Add source parse jobs
this->CreateParseJobs<JobParseSourceT>(this->BaseEval().Sources);
// Add parse cache evaluations jobs
{
// Add a fence job to ensure all parsing has finished
this->WorkerPool().EmplaceJob<JobFenceT>();
if (this->MocConst().Enabled) {
this->WorkerPool().EmplaceJob<JobEvalCacheMocT>();
}
if (this->UicConst().Enabled) {
this->WorkerPool().EmplaceJob<JobEvalCacheUicT>();
}
// Add evaluate job
this->WorkerPool().EmplaceJob<JobEvalCacheFinishT>();
}
}
bool cmQtAutoMocUicT::Process()
{
this->SettingsFileRead();
this->ParseCacheRead();
if (!this->CreateDirectories()) {
return false;
}
this->InitJobs();
if (!this->WorkerPool_.Process(this)) {
return false;
}
if (this->JobError_) {
return false;
}
if (!this->ParseCacheWrite()) {
return false;
}
if (!this->SettingsFileWrite()) {
return false;
}
return true;
}
void cmQtAutoMocUicT::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
auto cha = [&cryptoHash](cm::string_view value) {
cryptoHash.Append(value);
cryptoHash.Append(";");
};
if (this->MocConst_.Enabled) {
cryptoHash.Initialize();
cha(this->MocConst().Executable);
for (auto const& item : this->MocConst().OptionsDefinitions) {
cha(item);
}
for (auto const& item : this->MocConst().OptionsIncludes) {
cha(item);
}
for (auto const& item : this->MocConst().OptionsExtra) {
cha(item);
}
for (auto const& item : this->MocConst().PredefsCmd) {
cha(item);
}
for (auto const& filter : this->MocConst().DependFilters) {
cha(filter.Key);
}
for (auto const& filter : this->MocConst().MacroFilters) {
cha(filter.Key);
}
this->SettingsStringMoc_ = cryptoHash.FinalizeHex();
}
if (this->UicConst().Enabled) {
cryptoHash.Initialize();
cha(this->UicConst().Executable);
std::for_each(this->UicConst().Options.begin(),
this->UicConst().Options.end(), cha);
for (const auto& item : this->UicConst().UiFiles) {
cha(item.first);
auto const& opts = item.second.Options;
std::for_each(opts.begin(), opts.end(), cha);
}
this->SettingsStringUic_ = cryptoHash.FinalizeHex();
}
}
// Read old settings and compare
{
std::string content;
if (cmQtAutoGenerator::FileRead(content, this->SettingsFile_)) {
if (this->MocConst().Enabled) {
if (this->SettingsStringMoc_ != SettingsFind(content, "moc")) {
this->MocConst_.SettingsChanged = true;
}
}
if (this->UicConst().Enabled) {
if (this->SettingsStringUic_ != SettingsFind(content, "uic")) {
this->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 (this->MocConst().SettingsChanged ||
this->UicConst().SettingsChanged) {
cmSystemTools::RemoveFile(this->SettingsFile_);
}
} else {
// Settings file read failed
if (this->MocConst().Enabled) {
this->MocConst_.SettingsChanged = true;
}
if (this->UicConst().Enabled) {
this->UicConst_.SettingsChanged = true;
}
}
}
}
bool cmQtAutoMocUicT::SettingsFileWrite()
{
// Only write if any setting changed
if (this->MocConst().SettingsChanged || this->UicConst().SettingsChanged) {
if (this->Log().Verbose()) {
this->Log().Info(GenT::GEN,
cmStrCat("Writing the settings file ",
this->MessagePath(this->SettingsFile_)));
}
// Compose settings file content
std::string content;
{
auto SettingAppend = [&content](cm::string_view key,
cm::string_view value) {
if (!value.empty()) {
content += cmStrCat(key, ':', value, '\n');
}
};
SettingAppend("moc", this->SettingsStringMoc_);
SettingAppend("uic", this->SettingsStringUic_);
}
// Write settings file
std::string error;
if (!cmQtAutoGenerator::FileWrite(this->SettingsFile_, content, &error)) {
this->Log().Error(GenT::GEN,
cmStrCat("Writing the settings file ",
this->MessagePath(this->SettingsFile_),
" failed.\n", error));
// Remove old settings file to trigger a full rebuild on the next run
cmSystemTools::RemoveFile(this->SettingsFile_);
return false;
}
}
return true;
}
void cmQtAutoMocUicT::ParseCacheRead()
{
cm::string_view reason;
// Don't read the cache if it is invalid
if (!this->BaseEval().ParseCacheTime.Load(
this->BaseConst().ParseCacheFile)) {
reason = "Refreshing parse cache because it doesn't exist.";
} else if (this->MocConst().SettingsChanged ||
this->UicConst().SettingsChanged) {
reason = "Refreshing parse cache because the settings changed.";
} else if (this->BaseEval().ParseCacheTime.Older(
this->BaseConst().CMakeExecutableTime)) {
reason =
"Refreshing parse cache because it is older than the CMake executable.";
}
if (!reason.empty()) {
// Don't read but refresh the complete parse cache
if (this->Log().Verbose()) {
this->Log().Info(GenT::GEN, reason);
}
this->BaseEval().ParseCacheChanged = true;
} else {
// Read parse cache
this->BaseEval().ParseCache.ReadFromFile(this->BaseConst().ParseCacheFile);
}
}
bool cmQtAutoMocUicT::ParseCacheWrite()
{
if (this->BaseEval().ParseCacheChanged) {
if (this->Log().Verbose()) {
this->Log().Info(
GenT::GEN,
cmStrCat("Writing the parse cache file ",
this->MessagePath(this->BaseConst().ParseCacheFile)));
}
if (!this->BaseEval().ParseCache.WriteToFile(
this->BaseConst().ParseCacheFile)) {
this->Log().Error(
GenT::GEN,
cmStrCat("Writing the parse cache file ",
this->MessagePath(this->BaseConst().ParseCacheFile),
" failed."));
return false;
}
}
return true;
}
bool cmQtAutoMocUicT::CreateDirectories()
{
// Create AUTOGEN include directory
if (!cmSystemTools::MakeDirectory(this->BaseConst().AutogenIncludeDir)) {
this->Log().Error(
GenT::GEN,
cmStrCat("Creating the AUTOGEN include directory ",
this->MessagePath(this->BaseConst().AutogenIncludeDir),
" failed."));
return false;
}
return true;
}
std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
const char* filePath)
{
std::lock_guard<std::mutex> guard(this->CMakeLibMutex_);
#if defined(__NVCOMPILER) || defined(__LCC__)
static_cast<void>(guard); // convince compiler var is used
#endif
auto const content = cmReadGccDepfile(filePath);
if (!content || content->empty()) {
return {};
}
// Moc outputs a depfile with exactly one rule.
// Discard the rule and return the dependencies.
return content->front().paths;
}
void cmQtAutoMocUicT::Abort(bool error)
{
if (error) {
this->JobError_.store(true);
}
this->WorkerPool_.Abort();
}
std::string cmQtAutoMocUicT::AbsoluteBuildPath(
cm::string_view relativePath) const
{
return cmStrCat(this->BaseConst().AutogenBuildDir, '/', relativePath);
}
std::string cmQtAutoMocUicT::AbsoluteIncludePath(
cm::string_view relativePath) const
{
return cmStrCat(this->BaseConst().AutogenIncludeDir, '/', relativePath);
}
} // End of unnamed namespace
bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config)
{
return cmQtAutoMocUicT().Run(infoFile, config);
}