mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-20 03:38:05 +08:00

`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`.
2194 lines
67 KiB
C++
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;
|
|
}
|