Files
CMake/Source/cmQtAutoGeneratorMocUic.cxx
T
Alexandru Croitor e7a760fe7d Autogen: Fix deadlock when uv_spawn() fails while trying to run moc
If by some chance the moc executable does not exist while running
AUTOMOC, instead of showing an error, the CMake Autogen invocation
hangs indefinitely.

This happens because UVProcessFinished() is not called if the process
does not launch correctly.

Make sure to call UVProcessFinished() even if the process launch fails,
and also report the error returned by libuv.
2019-03-25 11:43:14 +01:00

2025 lines
61 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoGeneratorMocUic.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <list>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmMakefile.h"
#include "cmQtAutoGen.h"
#include "cmSystemTools.h"
#include "cmake.h"
#if defined(__APPLE__)
# include <unistd.h>
#endif
// -- Class methods
std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath(
std::string const& relativePath) const
{
return FileSys->CollapseFullPath(relativePath, AutogenBuildDir);
}
/**
* @brief Tries to find the header file to the given file base path by
* appending different header extensions
* @return True on success
*/
bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader(
std::string& header, std::string const& testBasePath) const
{
for (std::string const& ext : HeaderExtensions) {
std::string testFilePath(testBasePath);
testFilePath.push_back('.');
testFilePath += ext;
if (FileSys->FileExists(testFilePath)) {
header = testFilePath;
return true;
}
}
return false;
}
bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped(
std::string const& fileName) const
{
return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
}
/**
* @brief Returns the first relevant Qt macro name found in the given C++ code
* @return The name of the Qt macro or an empty string
*/
std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro(
std::string const& content) const
{
for (KeyExpT const& filter : MacroFilters) {
// Run a simple find string operation before the expensive
// regular expression check
if (content.find(filter.Key) != std::string::npos) {
cmsys::RegularExpressionMatch match;
if (filter.Exp.find(content.c_str(), match)) {
// Return macro name on demand
return filter.Key;
}
}
}
return std::string();
}
std::string cmQtAutoGeneratorMocUic::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;
}
std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile(
std::string const& sourcePath, std::string const& includeString) const
{
// Search in vicinity of the source
{
std::string testPath = sourcePath;
testPath += includeString;
if (FileSys->FileExists(testPath)) {
return FileSys->GetRealPath(testPath);
}
}
// Search in include directories
for (std::string const& path : IncludePaths) {
std::string fullPath = path;
fullPath.push_back('/');
fullPath += includeString;
if (FileSys->FileExists(fullPath)) {
return FileSys->GetRealPath(fullPath);
}
}
// Return empty string
return std::string();
}
void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies(
std::string const& content, std::set<std::string>& depends) const
{
if (!DependFilters.empty() && !content.empty()) {
for (KeyExpT const& filter : DependFilters) {
// Run a simple find string check
if (content.find(filter.Key) != std::string::npos) {
// 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()) {
depends.emplace(std::move(dep));
}
}
contentChars += match.end();
}
}
}
}
}
bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped(
std::string const& fileName) const
{
return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
}
void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk)
{
if (AutoMoc && Header) {
// Don't parse header for moc if the file is included by a source already
if (wrk.Gen().ParallelMocIncluded(FileName)) {
AutoMoc = false;
}
}
if (AutoMoc || AutoUic) {
std::string error;
MetaT meta;
if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) {
if (!meta.Content.empty()) {
meta.FileDir = wrk.FileSys().SubDirPrefix(FileName);
meta.FileBase =
wrk.FileSys().GetFilenameWithoutLastExtension(FileName);
bool success = true;
if (AutoMoc) {
if (Header) {
success = ParseMocHeader(wrk, meta);
} else {
success = ParseMocSource(wrk, meta);
}
}
if (AutoUic && success) {
ParseUic(wrk, meta);
}
} else {
wrk.LogFileWarning(GenT::GEN, FileName, "The source file is empty");
}
} else {
wrk.LogFileError(GenT::GEN, FileName,
"Could not read the file: " + error);
}
}
}
bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
MetaT const& meta)
{
struct JobPre
{
bool self; // source file is self
bool underscore; // "moc_" style include
std::string SourceFile;
std::string IncludeString;
};
struct MocInclude
{
std::string Inc; // full include string
std::string Dir; // include string directory
std::string Base; // include string file base
};
// Check if this source file contains a relevant macro
std::string const ownMacro = wrk.Moc().FindMacro(meta.Content);
// Extract moc includes from file
std::deque<MocInclude> mocIncsUsc;
std::deque<MocInclude> mocIncsDot;
{
if (meta.Content.find("moc") != std::string::npos) {
const char* contentChars = meta.Content.c_str();
cmsys::RegularExpressionMatch match;
while (wrk.Moc().RegExpInclude.find(contentChars, match)) {
std::string incString = match.match(2);
std::string incDir(wrk.FileSys().SubDirPrefix(incString));
std::string incBase =
wrk.FileSys().GetFilenameWithoutLastExtension(incString);
if (cmHasLiteralPrefix(incBase, "moc_")) {
// moc_<BASE>.cxx
// Remove the moc_ part from the base name
mocIncsUsc.emplace_back(MocInclude{
std::move(incString), std::move(incDir), incBase.substr(4) });
} else {
// <BASE>.moc
mocIncsDot.emplace_back(MocInclude{
std::move(incString), std::move(incDir), std::move(incBase) });
}
// Forward content pointer
contentChars += match.end();
}
}
}
// Check if there is anything to do
if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) {
return true;
}
bool ownDotMocIncluded = false;
bool ownMocUscIncluded = false;
std::deque<JobPre> jobs;
// Process moc_<BASE>.cxx includes
for (const MocInclude& mocInc : mocIncsUsc) {
std::string const header =
MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
if (!header.empty()) {
// Check if header is skipped
if (wrk.Moc().skipped(header)) {
continue;
}
// Register moc job
const bool ownMoc = (mocInc.Base == meta.FileBase);
jobs.emplace_back(JobPre{ ownMoc, true, header, mocInc.Inc });
// Store meta information for relaxed mode
if (ownMoc) {
ownMocUscIncluded = true;
}
} else {
{
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += ", but the header ";
emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
emsg += " could not be found.";
wrk.LogFileError(GenT::MOC, FileName, emsg);
}
return false;
}
}
// Process <BASE>.moc includes
for (const MocInclude& mocInc : mocIncsDot) {
const bool ownMoc = (mocInc.Base == meta.FileBase);
if (wrk.Moc().RelaxedMode) {
// Relaxed mode
if (!ownMacro.empty() && ownMoc) {
// Add self
jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
ownDotMocIncluded = true;
} else {
// In relaxed mode try to find a header instead but issue a warning.
// This is for KDE4 compatibility
std::string const header =
MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
if (!header.empty()) {
// Check if header is skipped
if (wrk.Moc().skipped(header)) {
continue;
}
// Register moc job
jobs.emplace_back(JobPre{ ownMoc, false, header, mocInc.Inc });
if (ownMacro.empty()) {
if (ownMoc) {
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += ", but does not contain a ";
emsg += wrk.Moc().MacrosString();
emsg += " macro.\nRunning moc on\n ";
emsg += Quoted(header);
emsg += "!\nBetter include ";
emsg += Quoted("moc_" + mocInc.Base + ".cpp");
emsg += " for a compatibility with strict mode.\n"
"(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
wrk.LogFileWarning(GenT::MOC, FileName, emsg);
} else {
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += " instead of ";
emsg += Quoted("moc_" + mocInc.Base + ".cpp");
emsg += ".\nRunning moc on\n ";
emsg += Quoted(header);
emsg += "!\nBetter include ";
emsg += Quoted("moc_" + mocInc.Base + ".cpp");
emsg += " for compatibility with strict mode.\n"
"(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
wrk.LogFileWarning(GenT::MOC, FileName, emsg);
}
}
} else {
{
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += ", which seems to be the moc file from a different "
"source file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a "
"matching header ";
emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
emsg += " could not be found.";
wrk.LogFileError(GenT::MOC, FileName, emsg);
}
return false;
}
}
} else {
// Strict mode
if (ownMoc) {
// Include self
jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
ownDotMocIncluded = true;
// Accept but issue a warning if moc isn't required
if (ownMacro.empty()) {
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += ", but does not contain a ";
emsg += wrk.Moc().MacrosString();
emsg += " macro.";
wrk.LogFileWarning(GenT::MOC, FileName, emsg);
}
} else {
// Don't allow <BASE>.moc include other than self in strict mode
{
std::string emsg = "The file includes the moc file ";
emsg += Quoted(mocInc.Inc);
emsg += ", which seems to be the moc file from a different "
"source file.\nThis is not supported. Include ";
emsg += Quoted(meta.FileBase + ".moc");
emsg += " to run moc on this source file.";
wrk.LogFileError(GenT::MOC, FileName, emsg);
}
return false;
}
}
}
if (!ownMacro.empty() && !ownDotMocIncluded) {
// In this case, check whether the scanned file itself contains a
// Q_OBJECT.
// If this is the case, the moc_foo.cpp should probably be generated from
// foo.cpp instead of foo.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.
if (wrk.Moc().RelaxedMode && ownMocUscIncluded) {
JobPre uscJobPre;
// Remove underscore job request
{
auto itC = jobs.begin();
auto itE = jobs.end();
for (; itC != itE; ++itC) {
JobPre& job(*itC);
if (job.self && job.underscore) {
uscJobPre = std::move(job);
jobs.erase(itC);
break;
}
}
}
// Issue a warning
{
std::string emsg = "The file contains a ";
emsg += ownMacro;
emsg += " macro, but does not include ";
emsg += Quoted(meta.FileBase + ".moc");
emsg += ". Instead it includes ";
emsg += Quoted(uscJobPre.IncludeString);
emsg += ".\nRunning moc on\n ";
emsg += Quoted(FileName);
emsg += "!\nBetter include ";
emsg += Quoted(meta.FileBase + ".moc");
emsg += " for compatibility with strict mode.\n"
"(CMAKE_AUTOMOC_RELAXED_MODE warning)";
wrk.LogFileWarning(GenT::MOC, FileName, emsg);
}
// Add own source job
jobs.emplace_back(
JobPre{ true, false, FileName, uscJobPre.IncludeString });
} else {
// Otherwise always error out since it will not compile.
{
std::string emsg = "The file contains a ";
emsg += ownMacro;
emsg += " macro, but does not include ";
emsg += Quoted(meta.FileBase + ".moc");
emsg += "!\nConsider to\n - add #include \"";
emsg += meta.FileBase;
emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
wrk.LogFileError(GenT::MOC, FileName, emsg);
}
return false;
}
}
// Convert pre jobs to actual jobs
for (JobPre& jobPre : jobs) {
JobHandleT jobHandle = cm::make_unique<JobMocT>(
std::move(jobPre.SourceFile), FileName, std::move(jobPre.IncludeString));
if (jobPre.self) {
// Read dependencies from this source
static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
}
if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) {
return false;
}
}
return true;
}
bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk,
MetaT const& meta)
{
bool success = true;
std::string const macroName = wrk.Moc().FindMacro(meta.Content);
if (!macroName.empty()) {
JobHandleT jobHandle = cm::make_unique<JobMocT>(
std::string(FileName), std::string(), std::string());
// Read dependencies from this source
static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
success = wrk.Gen().ParallelJobPushMoc(jobHandle);
}
return success;
}
std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
WorkerT& wrk, std::string const& fileBase) const
{
std::string res = fileBase;
res += ".{";
res += cmJoin(wrk.Base().HeaderExtensions, ",");
res += "}";
return res;
}
std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
WorkerT& wrk, std::string const& includerDir, std::string const& includeBase)
{
std::string header;
// Search in vicinity of the source
if (!wrk.Base().FindHeader(header, includerDir + includeBase)) {
// Search in include directories
for (std::string const& path : wrk.Moc().IncludePaths) {
std::string fullPath = path;
fullPath.push_back('/');
fullPath += includeBase;
if (wrk.Base().FindHeader(header, fullPath)) {
break;
}
}
}
// Sanitize
if (!header.empty()) {
header = wrk.FileSys().GetRealPath(header);
}
return header;
}
bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk,
MetaT const& meta)
{
bool success = true;
if (meta.Content.find("ui_") != std::string::npos) {
const char* contentChars = meta.Content.c_str();
cmsys::RegularExpressionMatch match;
while (wrk.Uic().RegExpInclude.find(contentChars, match)) {
if (!ParseUicInclude(wrk, meta, match.match(2))) {
success = false;
break;
}
contentChars += match.end();
}
}
return success;
}
bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
WorkerT& wrk, MetaT const& meta, std::string&& includeString)
{
bool success = false;
std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString);
if (!uiInputFile.empty()) {
if (!wrk.Uic().skipped(uiInputFile)) {
JobHandleT jobHandle = cm::make_unique<JobUicT>(
std::move(uiInputFile), FileName, std::move(includeString));
success = wrk.Gen().ParallelJobPushUic(jobHandle);
} else {
// A skipped file is successful
success = true;
}
}
return success;
}
std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
WorkerT& wrk, MetaT const& meta, std::string const& includeString)
{
std::string res;
std::string searchFile =
wrk.FileSys().GetFilenameWithoutLastExtension(includeString).substr(3);
searchFile += ".ui";
// Collect search paths list
std::deque<std::string> testFiles;
{
std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString);
std::string searchFileFull;
if (!searchPath.empty()) {
searchFileFull = searchPath;
searchFileFull += searchFile;
}
// Vicinity of the source
{
std::string const sourcePath = meta.FileDir;
testFiles.push_back(sourcePath + searchFile);
if (!searchPath.empty()) {
testFiles.push_back(sourcePath + searchFileFull);
}
}
// AUTOUIC search paths
if (!wrk.Uic().SearchPaths.empty()) {
for (std::string const& sPath : wrk.Uic().SearchPaths) {
testFiles.push_back((sPath + "/").append(searchFile));
}
if (!searchPath.empty()) {
for (std::string const& sPath : wrk.Uic().SearchPaths) {
testFiles.push_back((sPath + "/").append(searchFileFull));
}
}
}
}
// Search for the .ui file!
for (std::string const& testFile : testFiles) {
if (wrk.FileSys().FileExists(testFile)) {
res = wrk.FileSys().GetRealPath(testFile);
break;
}
}
// Log error
if (res.empty()) {
std::string emsg = "Could not find ";
emsg += Quoted(searchFile);
emsg += " in\n";
for (std::string const& testFile : testFiles) {
emsg += " ";
emsg += Quoted(testFile);
emsg += "\n";
}
wrk.LogFileError(GenT::UIC, FileName, emsg);
}
return res;
}
void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk)
{
// (Re)generate moc_predefs.h on demand
bool generate(false);
bool fileExists(wrk.FileSys().FileExists(wrk.Moc().PredefsFileAbs));
if (!fileExists) {
if (wrk.Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(wrk.Moc().PredefsFileRel);
reason += " because it doesn't exist";
wrk.LogInfo(GenT::MOC, reason);
}
generate = true;
} else if (wrk.Moc().SettingsChanged) {
if (wrk.Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(wrk.Moc().PredefsFileRel);
reason += " because the settings changed.";
wrk.LogInfo(GenT::MOC, reason);
}
generate = true;
}
if (generate) {
ProcessResultT result;
{
// Compose command
std::vector<std::string> cmd = wrk.Moc().PredefsCmd;
// Add includes
cmd.insert(cmd.end(), wrk.Moc().Includes.begin(),
wrk.Moc().Includes.end());
// Add definitions
for (std::string const& def : wrk.Moc().Definitions) {
cmd.push_back("-D" + def);
}
// Execute command
if (!wrk.RunProcess(GenT::MOC, result, cmd)) {
std::string emsg = "The content generation command for ";
emsg += Quoted(wrk.Moc().PredefsFileRel);
emsg += " failed.\n";
emsg += result.ErrorMessage;
wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
}
}
// (Re)write predefs file only on demand
if (!result.error()) {
if (!fileExists ||
wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) {
if (wrk.FileSys().FileWrite(GenT::MOC, wrk.Moc().PredefsFileAbs,
result.StdOut)) {
// Success
} else {
std::string emsg = "Writing ";
emsg += Quoted(wrk.Moc().PredefsFileRel);
emsg += " failed.";
wrk.LogFileError(GenT::MOC, wrk.Moc().PredefsFileAbs, emsg);
}
} else {
// Touch to update the time stamp
if (wrk.Log().Verbose()) {
std::string msg = "Touching ";
msg += Quoted(wrk.Moc().PredefsFileRel);
msg += ".";
wrk.LogInfo(GenT::MOC, msg);
}
wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs);
}
}
}
}
void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies(
WorkerT& wrk, std::string const& content)
{
wrk.Moc().FindDependencies(content, Depends);
DependsValid = true;
}
void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk)
{
// Compute build file name
if (!IncludeString.empty()) {
BuildFile = wrk.Base().AutogenIncludeDir;
BuildFile += '/';
BuildFile += IncludeString;
} else {
// Relative build path
std::string relPath = wrk.FileSys().GetFilePathChecksum(SourceFile);
relPath += "/moc_";
relPath += wrk.FileSys().GetFilenameWithoutLastExtension(SourceFile);
// Register relative file path with duplication check
relPath = wrk.Gen().ParallelMocAutoRegister(relPath);
// Absolute build path
if (wrk.Base().MultiConfig) {
BuildFile = wrk.Base().AutogenIncludeDir;
BuildFile += '/';
BuildFile += relPath;
} else {
BuildFile = wrk.Base().AbsoluteBuildPath(relPath);
}
}
if (UpdateRequired(wrk)) {
GenerateMoc(wrk);
}
}
bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
{
bool const verbose = wrk.Gen().Log().Verbose();
// Test if the build file exists
if (!wrk.FileSys().FileExists(BuildFile)) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " from its source file ";
reason += Quoted(SourceFile);
reason += " because it doesn't exist";
wrk.LogInfo(GenT::MOC, reason);
}
return true;
}
// Test if any setting changed
if (wrk.Moc().SettingsChanged) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " from ";
reason += Quoted(SourceFile);
reason += " because the MOC settings changed";
wrk.LogInfo(GenT::MOC, reason);
}
return true;
}
// Test if the moc_predefs file is newer
if (!wrk.Moc().PredefsFileAbs.empty()) {
bool isOlder = false;
{
std::string error;
isOlder = wrk.FileSys().FileIsOlderThan(
BuildFile, wrk.Moc().PredefsFileAbs, &error);
if (!isOlder && !error.empty()) {
wrk.LogError(GenT::MOC, error);
return false;
}
}
if (isOlder) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " because it's older than: ";
reason += Quoted(wrk.Moc().PredefsFileAbs);
wrk.LogInfo(GenT::MOC, reason);
}
return true;
}
}
// Test if the source file is newer
{
bool isOlder = false;
{
std::string error;
isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
if (!isOlder && !error.empty()) {
wrk.LogError(GenT::MOC, error);
return false;
}
}
if (isOlder) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " because it's older than its source file ";
reason += Quoted(SourceFile);
wrk.LogInfo(GenT::MOC, reason);
}
return true;
}
}
// Test if a dependency file is newer
{
// Read dependencies on demand
if (!DependsValid) {
std::string content;
{
std::string error;
if (!wrk.FileSys().FileRead(content, SourceFile, &error)) {
std::string emsg = "Could not read file\n ";
emsg += Quoted(SourceFile);
emsg += "\nrequired by moc include ";
emsg += Quoted(IncludeString);
emsg += " in\n ";
emsg += Quoted(IncluderFile);
emsg += ".\n";
emsg += error;
wrk.LogError(GenT::MOC, emsg);
return false;
}
}
FindDependencies(wrk, content);
}
// Check dependency timestamps
std::string error;
std::string sourceDir = wrk.FileSys().SubDirPrefix(SourceFile);
for (std::string const& depFileRel : Depends) {
std::string depFileAbs =
wrk.Moc().FindIncludedFile(sourceDir, depFileRel);
if (!depFileAbs.empty()) {
if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " from ";
reason += Quoted(SourceFile);
reason += " because it is older than it's dependency file ";
reason += Quoted(depFileAbs);
wrk.LogInfo(GenT::MOC, reason);
}
return true;
}
if (!error.empty()) {
wrk.LogError(GenT::MOC, error);
return false;
}
} else {
std::string message = "Could not find dependency file ";
message += Quoted(depFileRel);
wrk.LogFileWarning(GenT::MOC, SourceFile, message);
}
}
}
return false;
}
void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk)
{
// Make sure the parent directory exists
if (wrk.FileSys().MakeParentDirectory(GenT::MOC, BuildFile)) {
// Compose moc command
std::vector<std::string> cmd;
cmd.push_back(wrk.Moc().Executable);
// Add options
cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(),
wrk.Moc().AllOptions.end());
// Add predefs include
if (!wrk.Moc().PredefsFileAbs.empty()) {
cmd.emplace_back("--include");
cmd.push_back(wrk.Moc().PredefsFileAbs);
}
cmd.emplace_back("-o");
cmd.push_back(BuildFile);
cmd.push_back(SourceFile);
// Execute moc command
ProcessResultT result;
if (wrk.RunProcess(GenT::MOC, result, cmd)) {
// Moc command success
// Print moc output
if (!result.StdOut.empty()) {
wrk.LogInfo(GenT::MOC, result.StdOut);
}
// Notify the generator that a not included file changed (on demand)
if (IncludeString.empty()) {
wrk.Gen().ParallelMocAutoUpdated();
}
} else {
// Moc command failed
{
std::string emsg = "The moc process failed to compile\n ";
emsg += Quoted(SourceFile);
emsg += "\ninto\n ";
emsg += Quoted(BuildFile);
emsg += ".\n";
emsg += result.ErrorMessage;
wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
}
wrk.FileSys().FileRemove(BuildFile);
}
}
}
void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk)
{
// Compute build file name
BuildFile = wrk.Base().AutogenIncludeDir;
BuildFile += '/';
BuildFile += IncludeString;
if (UpdateRequired(wrk)) {
GenerateUic(wrk);
}
}
bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
{
bool const verbose = wrk.Gen().Log().Verbose();
// Test if the build file exists
if (!wrk.FileSys().FileExists(BuildFile)) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " from its source file ";
reason += Quoted(SourceFile);
reason += " because it doesn't exist";
wrk.LogInfo(GenT::UIC, reason);
}
return true;
}
// Test if the uic settings changed
if (wrk.Uic().SettingsChanged) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " from ";
reason += Quoted(SourceFile);
reason += " because the UIC settings changed";
wrk.LogInfo(GenT::UIC, reason);
}
return true;
}
// Test if the source file is newer
{
bool isOlder = false;
{
std::string error;
isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
if (!isOlder && !error.empty()) {
wrk.LogError(GenT::UIC, error);
return false;
}
}
if (isOlder) {
if (verbose) {
std::string reason = "Generating ";
reason += Quoted(BuildFile);
reason += " because it's older than its source file ";
reason += Quoted(SourceFile);
wrk.LogInfo(GenT::UIC, reason);
}
return true;
}
}
return false;
}
void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk)
{
// Make sure the parent directory exists
if (wrk.FileSys().MakeParentDirectory(GenT::UIC, BuildFile)) {
// Compose uic command
std::vector<std::string> cmd;
cmd.push_back(wrk.Uic().Executable);
{
std::vector<std::string> allOpts = wrk.Uic().TargetOptions;
auto optionIt = wrk.Uic().Options.find(SourceFile);
if (optionIt != wrk.Uic().Options.end()) {
UicMergeOptions(allOpts, optionIt->second,
(wrk.Base().QtVersionMajor == 5));
}
cmd.insert(cmd.end(), allOpts.begin(), allOpts.end());
}
cmd.emplace_back("-o");
cmd.push_back(BuildFile);
cmd.push_back(SourceFile);
ProcessResultT result;
if (wrk.RunProcess(GenT::UIC, result, cmd)) {
// Uic command success
// Print uic output
if (!result.StdOut.empty()) {
wrk.LogInfo(GenT::UIC, result.StdOut);
}
} else {
// Uic command failed
{
std::string emsg = "The uic process failed to compile\n ";
emsg += Quoted(SourceFile);
emsg += "\ninto\n ";
emsg += Quoted(BuildFile);
emsg += "\nincluded by\n ";
emsg += Quoted(IncluderFile);
emsg += ".\n";
emsg += result.ErrorMessage;
wrk.LogCommandError(GenT::UIC, emsg, cmd, result.StdOut);
}
wrk.FileSys().FileRemove(BuildFile);
}
}
}
cmQtAutoGeneratorMocUic::WorkerT::WorkerT(cmQtAutoGeneratorMocUic* gen,
uv_loop_t* uvLoop)
: Gen_(gen)
{
// Initialize uv asynchronous callback for process starting
ProcessRequest_.init(*uvLoop, &WorkerT::UVProcessStart, this);
// Start thread
Thread_ = std::thread(&WorkerT::Loop, this);
}
cmQtAutoGeneratorMocUic::WorkerT::~WorkerT()
{
// Join thread
if (Thread_.joinable()) {
Thread_.join();
}
}
void cmQtAutoGeneratorMocUic::WorkerT::LogInfo(
GenT genType, std::string const& message) const
{
Log().Info(genType, message);
}
void cmQtAutoGeneratorMocUic::WorkerT::LogWarning(
GenT genType, std::string const& message) const
{
Log().Warning(genType, message);
}
void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning(
GenT genType, std::string const& filename, std::string const& message) const
{
Log().WarningFile(genType, filename, message);
}
void cmQtAutoGeneratorMocUic::WorkerT::LogError(
GenT genType, std::string const& message) const
{
Gen().ParallelRegisterJobError();
Log().Error(genType, message);
}
void cmQtAutoGeneratorMocUic::WorkerT::LogFileError(
GenT genType, std::string const& filename, std::string const& message) const
{
Gen().ParallelRegisterJobError();
Log().ErrorFile(genType, filename, message);
}
void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError(
GenT genType, std::string const& message,
std::vector<std::string> const& command, std::string const& output) const
{
Gen().ParallelRegisterJobError();
Log().ErrorCommand(genType, message, command, output);
}
bool cmQtAutoGeneratorMocUic::WorkerT::RunProcess(
GenT genType, ProcessResultT& result,
std::vector<std::string> const& command)
{
if (command.empty()) {
return false;
}
// Create process instance
{
std::lock_guard<std::mutex> lock(ProcessMutex_);
Process_ = cm::make_unique<ReadOnlyProcessT>();
Process_->setup(&result, true, command, Gen().Base().AutogenBuildDir);
}
// Send asynchronous process start request to libuv loop
ProcessRequest_.send();
// Log command
if (this->Log().Verbose()) {
std::string msg = "Running command:\n";
msg += QuotedCommand(command);
msg += '\n';
this->LogInfo(genType, msg);
}
// Wait until the process has been finished and destroyed
{
std::unique_lock<std::mutex> ulock(ProcessMutex_);
while (Process_) {
ProcessCondition_.wait(ulock);
}
}
return !result.error();
}
void cmQtAutoGeneratorMocUic::WorkerT::Loop()
{
while (true) {
Gen().WorkerSwapJob(JobHandle_);
if (JobHandle_) {
JobHandle_->Process(*this);
} else {
break;
}
}
}
void cmQtAutoGeneratorMocUic::WorkerT::UVProcessStart(uv_async_t* handle)
{
auto& wrk = *reinterpret_cast<WorkerT*>(handle->data);
{
std::lock_guard<std::mutex> lock(wrk.ProcessMutex_);
if (wrk.Process_ && !wrk.Process_->IsStarted()) {
wrk.Process_->start(handle->loop, [&wrk] { wrk.UVProcessFinished(); });
}
}
if (!wrk.Process_->IsStarted()) {
wrk.UVProcessFinished();
}
}
void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished()
{
{
std::lock_guard<std::mutex> lock(ProcessMutex_);
if (Process_ && (Process_->IsFinished() || !Process_->IsStarted())) {
Process_.reset();
}
}
// Notify idling thread
ProcessCondition_.notify_one();
}
cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic()
: Base_(&FileSys())
, Moc_(&FileSys())
{
// Precompile regular expressions
Moc_.RegExpInclude.compile(
"(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
"[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
// Initialize libuv asynchronous iteration request
UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this);
}
cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() = default;
bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile)
{
// -- Meta
Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions();
// 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;
};
// -- Read info file
if (!makefile->ReadListFile(InfoFile())) {
Log().ErrorFile(GenT::GEN, InfoFile(), "File processing failed");
return false;
}
// -- Meta
Log().RaiseVerbosity(InfoGet("AM_VERBOSITY"));
Base_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG");
{
unsigned long num = Base_.NumThreads;
if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL").c_str(), &num)) {
num = std::max<unsigned long>(num, 1);
num = std::min<unsigned long>(num, ParallelMax);
Base_.NumThreads = static_cast<unsigned int>(num);
}
}
// - Files and directories
Base_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
Base_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
Base_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
Base_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
Base_.IncludeProjectDirsBefore =
InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR");
if (Base_.AutogenBuildDir.empty()) {
Log().ErrorFile(GenT::GEN, InfoFile(), "Autogen build directory missing");
return false;
}
// include directory
Base_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR");
if (Base_.AutogenIncludeDir.empty()) {
Log().ErrorFile(GenT::GEN, InfoFile(),
"Autogen include directory missing");
return false;
}
// - Files
SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE");
if (SettingsFile_.empty()) {
Log().ErrorFile(GenT::GEN, InfoFile(), "Settings file name missing");
return false;
}
// - Qt environment
{
unsigned long qtv = Base_.QtVersionMajor;
if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(),
&qtv)) {
Base_.QtVersionMajor = static_cast<unsigned int>(qtv);
}
}
// - Moc
Moc_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE");
Moc_.Enabled = !Moc().Executable.empty();
if (Moc().Enabled) {
for (std::string& sfl : InfoGetList("AM_MOC_SKIP")) {
Moc_.SkipList.insert(std::move(sfl));
}
Moc_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
Moc_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
Moc_.Options = InfoGetList("AM_MOC_OPTIONS");
Moc_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) {
Moc_.MacroFilters.emplace_back(
item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
}
{
auto pushFilter = [this](std::string const& key, std::string const& exp,
std::string& error) {
if (!key.empty()) {
if (!exp.empty()) {
Moc_.DependFilters.emplace_back();
KeyExpT& filter(Moc_.DependFilters.back());
if (filter.Exp.compile(exp)) {
filter.Key = key;
} else {
error = "Regular expression compiling failed";
}
} else {
error = "Regular expression is empty";
}
} else {
error = "Key is empty";
}
if (!error.empty()) {
error = ("AUTOMOC_DEPEND_FILTERS: " + error);
error += "\n";
error += " Key: ";
error += Quoted(key);
error += "\n";
error += " Exp: ";
error += Quoted(exp);
error += "\n";
}
};
std::string error;
// Insert default filter for Q_PLUGIN_METADATA
if (Base().QtVersionMajor != 4) {
pushFilter("Q_PLUGIN_METADATA",
"[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
"[^\\)]*FILE[ \t]*\"([^\"]+)\"",
error);
}
// Insert user defined dependency filters
{
std::vector<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS");
if ((flts.size() % 2) == 0) {
for (std::vector<std::string>::iterator itC = flts.begin(),
itE = flts.end();
itC != itE; itC += 2) {
pushFilter(*itC, *(itC + 1), error);
if (!error.empty()) {
break;
}
}
} else {
Log().ErrorFile(
GenT::MOC, InfoFile(),
"AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
return false;
}
}
if (!error.empty()) {
Log().ErrorFile(GenT::MOC, InfoFile(), error);
return false;
}
}
Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
// Install moc predefs job
if (!Moc().PredefsCmd.empty()) {
JobQueues_.MocPredefs.emplace_back(cm::make_unique<JobMocPredefsT>());
}
}
// - Uic
Uic_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE");
Uic_.Enabled = !Uic().Executable.empty();
if (Uic().Enabled) {
for (std::string& sfl : InfoGetList("AM_UIC_SKIP")) {
Uic_.SkipList.insert(std::move(sfl));
}
Uic_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
Uic_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
{
auto sources = InfoGetList("AM_UIC_OPTIONS_FILES");
auto options = InfoGetLists("AM_UIC_OPTIONS_OPTIONS");
// Compare list sizes
if (sources.size() != options.size()) {
std::ostringstream ost;
ost << "files/options lists sizes mismatch (" << sources.size() << "/"
<< options.size() << ")";
Log().ErrorFile(GenT::UIC, InfoFile(), ost.str());
return false;
}
auto fitEnd = sources.cend();
auto fit = sources.begin();
auto oit = options.begin();
while (fit != fitEnd) {
Uic_.Options[*fit] = std::move(*oit);
++fit;
++oit;
}
}
}
// - Headers and sources
{
auto addHeader = [this](std::string&& hdr, bool moc, bool uic) {
this->JobQueues_.Headers.emplace_back(
cm::make_unique<JobParseT>(std::move(hdr), moc, uic, true));
};
auto addSource = [this](std::string&& src, bool moc, bool uic) {
this->JobQueues_.Sources.emplace_back(
cm::make_unique<JobParseT>(std::move(src), moc, uic, false));
};
// Add headers
for (std::string& hdr : InfoGetList("AM_HEADERS")) {
addHeader(std::move(hdr), true, true);
}
if (Moc().Enabled) {
for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) {
addHeader(std::move(hdr), true, false);
}
}
if (Uic().Enabled) {
for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) {
addHeader(std::move(hdr), false, true);
}
}
// Add sources
for (std::string& src : InfoGetList("AM_SOURCES")) {
addSource(std::move(src), true, true);
}
if (Moc().Enabled) {
for (std::string& src : InfoGetList("AM_MOC_SOURCES")) {
addSource(std::move(src), true, false);
}
}
if (Uic().Enabled) {
for (std::string& src : InfoGetList("AM_UIC_SOURCES")) {
addSource(std::move(src), false, true);
}
}
}
// Init derived information
// ------------------------
// Init file path checksum generator
FileSys().setupFilePathChecksum(
Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir,
Base().ProjectBinaryDir);
// Moc variables
if (Moc().Enabled) {
// Mocs compilation file
Moc_.CompFileAbs = Base().AbsoluteBuildPath("mocs_compilation.cpp");
// Moc predefs file
if (!Moc_.PredefsCmd.empty()) {
Moc_.PredefsFileRel = "moc_predefs";
if (Base_.MultiConfig) {
Moc_.PredefsFileRel += '_';
Moc_.PredefsFileRel += InfoConfig();
}
Moc_.PredefsFileRel += ".h";
Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel);
}
// Sort include directories on demand
if (Base().IncludeProjectDirsBefore) {
// Move strings to temporary list
std::list<std::string> includes;
includes.insert(includes.end(), Moc().IncludePaths.begin(),
Moc().IncludePaths.end());
Moc_.IncludePaths.clear();
Moc_.IncludePaths.reserve(includes.size());
// Append project directories only
{
std::array<std::string const*, 2> const movePaths = {
{ &Base().ProjectBinaryDir, &Base().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())) {
Moc_.IncludePaths.push_back(path);
it = includes.erase(it);
} else {
++it;
}
}
}
}
// Append remaining directories
Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(),
includes.end());
}
// Compose moc includes list
{
std::set<std::string> frameworkPaths;
for (std::string const& path : Moc().IncludePaths) {
Moc_.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;
FileSys().SplitPath(path, pathComponents);
std::string frameworkPath = FileSys().JoinPath(
pathComponents.begin(), pathComponents.end() - 2);
frameworkPaths.insert(frameworkPath);
}
}
// Append framework includes
for (std::string const& path : frameworkPaths) {
Moc_.Includes.emplace_back("-F");
Moc_.Includes.push_back(path);
}
}
// Setup single list with all options
{
// Add includes
Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(),
Moc().Includes.end());
// Add definitions
for (std::string const& def : Moc().Definitions) {
Moc_.AllOptions.push_back("-D" + def);
}
// Add options
Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(),
Moc().Options.end());
}
}
return true;
}
bool cmQtAutoGeneratorMocUic::Process()
{
// Run libuv event loop
UVRequest().send();
if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
if (JobError_) {
return false;
}
} else {
return false;
}
return true;
}
void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle)
{
reinterpret_cast<cmQtAutoGeneratorMocUic*>(handle->data)->PollStage();
}
void cmQtAutoGeneratorMocUic::PollStage()
{
switch (Stage_) {
case StageT::SETTINGS_READ:
SettingsFileRead();
SetStage(StageT::CREATE_DIRECTORIES);
break;
case StageT::CREATE_DIRECTORIES:
CreateDirectories();
SetStage(StageT::PARSE_SOURCES);
break;
case StageT::PARSE_SOURCES:
if (ThreadsStartJobs(JobQueues_.Sources)) {
SetStage(StageT::PARSE_HEADERS);
}
break;
case StageT::PARSE_HEADERS:
if (ThreadsStartJobs(JobQueues_.Headers)) {
SetStage(StageT::MOC_PREDEFS);
}
break;
case StageT::MOC_PREDEFS:
if (ThreadsStartJobs(JobQueues_.MocPredefs)) {
SetStage(StageT::MOC_PROCESS);
}
break;
case StageT::MOC_PROCESS:
if (ThreadsStartJobs(JobQueues_.Moc)) {
SetStage(StageT::MOCS_COMPILATION);
}
break;
case StageT::MOCS_COMPILATION:
if (ThreadsJobsDone()) {
MocGenerateCompilation();
SetStage(StageT::UIC_PROCESS);
}
break;
case StageT::UIC_PROCESS:
if (ThreadsStartJobs(JobQueues_.Uic)) {
SetStage(StageT::SETTINGS_WRITE);
}
break;
case StageT::SETTINGS_WRITE:
SettingsFileWrite();
SetStage(StageT::FINISH);
break;
case StageT::FINISH:
if (ThreadsJobsDone()) {
// Clear all libuv handles
ThreadsStop();
UVRequest().reset();
// Set highest END stage manually
Stage_ = StageT::END;
}
break;
case StageT::END:
break;
}
}
void cmQtAutoGeneratorMocUic::SetStage(StageT stage)
{
if (JobError_) {
stage = StageT::FINISH;
}
// Only allow to increase the stage
if (Stage_ < stage) {
Stage_ = stage;
UVRequest().send();
}
}
void cmQtAutoGeneratorMocUic::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
std::string const sep(" ~~~ ");
if (Moc_.Enabled) {
std::string str;
str += Moc().Executable;
str += sep;
str += cmJoin(Moc().AllOptions, ";");
str += sep;
str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE";
str += sep;
str += cmJoin(Moc().PredefsCmd, ";");
str += sep;
SettingsStringMoc_ = crypt.HashString(str);
}
if (Uic().Enabled) {
std::string str;
str += Uic().Executable;
str += sep;
str += cmJoin(Uic().TargetOptions, ";");
for (const auto& item : Uic().Options) {
str += sep;
str += item.first;
str += sep;
str += cmJoin(item.second, ";");
}
str += sep;
SettingsStringUic_ = crypt.HashString(str);
}
}
// Read old settings and compare
{
std::string content;
if (FileSys().FileRead(content, SettingsFile_)) {
if (Moc().Enabled) {
if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
Moc_.SettingsChanged = true;
}
}
if (Uic().Enabled) {
if (SettingsStringUic_ != SettingsFind(content, "uic")) {
Uic_.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 (Moc().SettingsChanged || Uic().SettingsChanged) {
FileSys().FileRemove(SettingsFile_);
}
} else {
// Settings file read failed
if (Moc().Enabled) {
Moc_.SettingsChanged = true;
}
if (Uic().Enabled) {
Uic_.SettingsChanged = true;
}
}
}
}
void cmQtAutoGeneratorMocUic::SettingsFileWrite()
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
// Only write if any setting changed
if (!JobError_ && (Moc().SettingsChanged || Uic().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
if (!FileSys().FileWrite(GenT::GEN, SettingsFile_, content)) {
Log().ErrorFile(GenT::GEN, SettingsFile_,
"Settings file writing failed");
// Remove old settings file to trigger a full rebuild on the next run
FileSys().FileRemove(SettingsFile_);
RegisterJobError();
}
}
}
void cmQtAutoGeneratorMocUic::CreateDirectories()
{
// Create AUTOGEN include directory
if (!FileSys().MakeDirectory(GenT::GEN, Base().AutogenIncludeDir)) {
RegisterJobError();
}
}
bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue)
{
bool done = false;
std::size_t queueSize = queue.size();
// Change the active queue
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
// Check if there are still unfinished jobs from the previous queue
if (JobsRemain_ == 0) {
if (!JobThreadsAbort_) {
JobQueue_.swap(queue);
JobsRemain_ = queueSize;
} else {
// Abort requested
queue.clear();
queueSize = 0;
}
done = true;
}
}
if (done && (queueSize != 0)) {
// Start new threads on demand
if (Workers_.empty()) {
Workers_.resize(Base().NumThreads);
for (auto& item : Workers_) {
item = cm::make_unique<WorkerT>(this, UVLoop());
}
} else {
// Notify threads
if (queueSize == 1) {
JobsConditionRead_.notify_one();
} else {
JobsConditionRead_.notify_all();
}
}
}
return done;
}
void cmQtAutoGeneratorMocUic::ThreadsStop()
{
if (!Workers_.empty()) {
// Clear all jobs
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
JobThreadsAbort_ = true;
JobsRemain_ -= JobQueue_.size();
JobQueue_.clear();
JobQueues_.Sources.clear();
JobQueues_.Headers.clear();
JobQueues_.MocPredefs.clear();
JobQueues_.Moc.clear();
JobQueues_.Uic.clear();
}
// Wake threads
JobsConditionRead_.notify_all();
// Join and clear threads
Workers_.clear();
}
}
bool cmQtAutoGeneratorMocUic::ThreadsJobsDone()
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
return (JobsRemain_ == 0);
}
void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle)
{
bool const jobProcessed(jobHandle);
if (jobProcessed) {
jobHandle.reset();
}
{
std::unique_lock<std::mutex> jobsLock(JobsMutex_);
// Reduce the remaining job count and notify the libuv loop
// when all jobs are done
if (jobProcessed) {
--JobsRemain_;
if (JobsRemain_ == 0) {
UVRequest().send();
}
}
// Wait for new jobs
while (!JobThreadsAbort_ && JobQueue_.empty()) {
JobsConditionRead_.wait(jobsLock);
}
// Try to pick up a new job handle
if (!JobThreadsAbort_ && !JobQueue_.empty()) {
jobHandle = std::move(JobQueue_.front());
JobQueue_.pop_front();
}
}
}
void cmQtAutoGeneratorMocUic::ParallelRegisterJobError()
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
RegisterJobError();
}
// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be
// locked
void cmQtAutoGeneratorMocUic::RegisterJobError()
{
JobError_ = true;
if (!JobThreadsAbort_) {
JobThreadsAbort_ = true;
// Clear remaining jobs
if (JobsRemain_ != 0) {
JobsRemain_ -= JobQueue_.size();
JobQueue_.clear();
}
}
}
bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle)
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
if (!JobThreadsAbort_) {
bool pushJobHandle = true;
// Do additional tests if this is an included moc job
const JobMocT& mocJob(static_cast<JobMocT&>(*jobHandle));
if (!mocJob.IncludeString.empty()) {
// Register included moc file and look for collisions
MocIncludedFiles_.emplace(mocJob.SourceFile);
if (!MocIncludedStrings_.emplace(mocJob.IncludeString).second) {
// Another source file includes the same moc file!
for (const JobHandleT& otherHandle : JobQueues_.Moc) {
const JobMocT& otherJob(static_cast<JobMocT&>(*otherHandle));
if (otherJob.IncludeString == mocJob.IncludeString) {
// Check if the same moc file would be generated from different
// source files which is an error.
if (otherJob.SourceFile != mocJob.SourceFile) {
// Include string collision
std::string error = "The two source files\n ";
error += Quoted(mocJob.IncluderFile);
error += " and\n ";
error += Quoted(otherJob.IncluderFile);
error += "\ncontain the same moc include string ";
error += Quoted(mocJob.IncludeString);
error += "\nbut the moc file would be generated from different "
"source files\n ";
error += Quoted(mocJob.SourceFile);
error += " and\n ";
error += Quoted(otherJob.SourceFile);
error += ".\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";
Log().Error(GenT::MOC, error);
RegisterJobError();
}
// Do not push this job in since the included moc file already
// gets generated by an other job.
pushJobHandle = false;
break;
}
}
}
}
// Push job on demand
if (pushJobHandle) {
JobQueues_.Moc.emplace_back(std::move(jobHandle));
}
}
return !JobError_;
}
bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle)
{
std::lock_guard<std::mutex> jobsLock(JobsMutex_);
if (!JobThreadsAbort_) {
bool pushJobHandle = true;
// Look for include collisions.
const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle));
for (const JobHandleT& otherHandle : JobQueues_.Uic) {
const JobUicT& otherJob(static_cast<JobUicT&>(*otherHandle));
if (otherJob.IncludeString == uicJob.IncludeString) {
// Check if the same uic file would be generated from different
// source files which would be an error.
if (otherJob.SourceFile != uicJob.SourceFile) {
// Include string collision
std::string error = "The two source files\n ";
error += Quoted(uicJob.IncluderFile);
error += " and\n ";
error += Quoted(otherJob.IncluderFile);
error += "\ncontain the same uic include string ";
error += Quoted(uicJob.IncludeString);
error += "\nbut the uic file would be generated from different "
"source files\n ";
error += Quoted(uicJob.SourceFile);
error += " and\n ";
error += Quoted(otherJob.SourceFile);
error +=
".\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";
Log().Error(GenT::UIC, error);
RegisterJobError();
}
// Do not push this job in since the uic file already
// gets generated by an other job.
pushJobHandle = false;
break;
}
}
if (pushJobHandle) {
JobQueues_.Uic.emplace_back(std::move(jobHandle));
}
}
return !JobError_;
}
bool cmQtAutoGeneratorMocUic::ParallelMocIncluded(
std::string const& sourceFile)
{
std::lock_guard<std::mutex> mocLock(JobsMutex_);
return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end());
}
std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister(
std::string const& baseName)
{
std::string res;
{
std::lock_guard<std::mutex> mocLock(JobsMutex_);
res = baseName;
res += ".cpp";
if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) {
MocAutoFiles_.emplace(res);
} else {
// Append number suffix to the file name
for (unsigned int ii = 2; ii != 1024; ++ii) {
res = baseName;
res += '_';
res += std::to_string(ii);
res += ".cpp";
if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) {
MocAutoFiles_.emplace(res);
break;
}
}
}
}
return res;
}
void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated()
{
std::lock_guard<std::mutex> mocLock(JobsMutex_);
MocAutoFileUpdated_ = true;
}
void cmQtAutoGeneratorMocUic::MocGenerateCompilation()
{
std::lock_guard<std::mutex> mocLock(JobsMutex_);
if (!JobError_ && Moc().Enabled) {
// Write mocs compilation build file
{
// Compose mocs compilation file content
std::string content =
"// This file is autogenerated. Changes will be overwritten.\n";
if (MocAutoFiles_.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 sbeg = Base().MultiConfig ? '<' : '"';
char const send = Base().MultiConfig ? '>' : '"';
for (std::string const& mocfile : MocAutoFiles_) {
content += "#include ";
content += sbeg;
content += mocfile;
content += send;
content += '\n';
}
}
std::string const& compAbs = Moc().CompFileAbs;
if (FileSys().FileDiffers(compAbs, content)) {
// Actually write mocs compilation file
if (Log().Verbose()) {
Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs);
}
if (!FileSys().FileWrite(GenT::MOC, compAbs, content)) {
Log().ErrorFile(GenT::MOC, compAbs,
"mocs compilation file writing failed");
RegisterJobError();
return;
}
} else if (MocAutoFileUpdated_) {
// Only touch mocs compilation file
if (Log().Verbose()) {
Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
}
FileSys().Touch(compAbs);
}
}
// Write mocs compilation wrapper file
if (Base().MultiConfig) {
}
}
}