diff options
author | Sebastian Holtermann <sebholt@xwmw.org> | 2018-01-03 15:59:40 (GMT) |
---|---|---|
committer | Sebastian Holtermann <sebholt@xwmw.org> | 2018-01-17 16:23:49 (GMT) |
commit | a008578deebfa71b38786281450e3d9cf84f5847 (patch) | |
tree | 70173006b0adc6a62626e59d9cc653826f950336 /Source/cmQtAutoGeneratorMocUic.cxx | |
parent | 488baaf0d6144cd7cedfbbd3bb6eadcc72257fc4 (diff) | |
download | CMake-a008578deebfa71b38786281450e3d9cf84f5847.zip CMake-a008578deebfa71b38786281450e3d9cf84f5847.tar.gz CMake-a008578deebfa71b38786281450e3d9cf84f5847.tar.bz2 |
Autogen: Process files concurrently in AUTOMOC and AUTOUIC
This introduces concurrent thread processing in the `_autogen`
target wich processes AUTOMOC and AUTOUIC.
Source file parsing is distributed among the threads by
using a job queue from which the threads pull new parse jobs.
Each thread might start an independent ``moc`` or ``uic`` process.
Altogether this roughly speeds up the AUTOMOC and AUTOUIC build
process by the number of physical CPUs on the host system.
The exact number of threads to start in the `_autogen` target
is controlled by the new AUTOGEN_PARALLEL target property which
is initialized by the new CMAKE_AUTOGEN_PARALLEL variable.
If AUTOGEN_PARALLEL is empty or unset (which is the default)
the thread count is set to the number of physical CPUs on
the host system.
The AUTOMOC/AUTOUIC generator and the AUTORCC generator are
refactored to use a libuv loop internally.
Closes #17422.
Diffstat (limited to 'Source/cmQtAutoGeneratorMocUic.cxx')
-rw-r--r-- | Source/cmQtAutoGeneratorMocUic.cxx | 3029 |
1 files changed, 1660 insertions, 1369 deletions
diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoGeneratorMocUic.cxx index bce148e..4b02e0b 100644 --- a/Source/cmQtAutoGeneratorMocUic.cxx +++ b/Source/cmQtAutoGeneratorMocUic.cxx @@ -5,16 +5,15 @@ #include <algorithm> #include <array> +#include <functional> #include <list> #include <memory> #include <sstream> -#include <string.h> #include <utility> #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmMakefile.h" -#include "cmOutputConverter.h" #include "cmSystemTools.h" #include "cmake.h" @@ -22,51 +21,1126 @@ #include <unistd.h> #endif -// -- Static variables +// -- Class methods -static const char* SettingsKeyMoc = "AM_MOC_SETTINGS_HASH"; -static const char* SettingsKeyUic = "AM_UIC_SETTINGS_HASH"; +std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( + std::string const& relativePath) const +{ + return cmSystemTools::CollapseCombinedPath(AutogenBuildDir, relativePath); +} -// -- Static functions +/** + * @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; +} -static std::string SubDirPrefix(std::string const& fileName) +bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped( + std::string const& fileName) const { - std::string res(cmSystemTools::GetFilenamePath(fileName)); - if (!res.empty()) { - res += '/'; + 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; } -static bool ListContains(std::vector<std::string> const& list, - std::string const& entry) +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->RealPath(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->RealPath(fullPath); + } + } + // Return empty string + return std::string(); +} + +void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies( + std::string const& content, std::set<std::string>& depends) const { - return (std::find(list.begin(), list.end(), entry) != list.end()); + 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(); + } + } + } + } } -// -- Class methods +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 = SubDirPrefix(FileName); + meta.FileBase = + cmSystemTools::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(GeneratorT::GEN, FileName, + "The source file is empty"); + } + } else { + wrk.LogFileError(GeneratorT::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(1); + std::string incDir(SubDirPrefix(incString)); + std::string incBase = + cmSystemTools::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::MOC, FileName, emsg); + } + return false; + } + } + + // Convert pre jobs to actual jobs + for (JobPre& jobPre : jobs) { + JobHandleT jobHandle(new JobMocT(std::move(jobPre.SourceFile), FileName, + std::move(jobPre.IncludeString))); + if (jobPre.self) { + // Read depdendencies 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( + new JobMocT(std::string(FileName), std::string(), std::string())); + // Read depdendencies 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().RealPath(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(1))) { + 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(new 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 = + cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3); + searchFile += ".ui"; + // Collect search paths list + std::deque<std::string> testFiles; + { + std::string const searchPath = 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().RealPath(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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::MOC, result, cmd)) { + std::string emsg = "The content generation command for "; + emsg += Quoted(wrk.Moc().PredefsFileRel); + emsg += " failed.\n"; + emsg += result.ErrorMessage; + wrk.LogCommandError(GeneratorT::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(GeneratorT::MOC, wrk.Moc().PredefsFileAbs, + result.StdOut)) { + // Success + } else { + std::string emsg = "Writing "; + emsg += Quoted(wrk.Moc().PredefsFileRel); + emsg += " failed."; + wrk.LogFileError(GeneratorT::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(GeneratorT::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().AutogenIncludeDirAbs; + BuildFile += IncludeString; + } else { + std::string buildRel = wrk.Base().FilePathChecksum.getPart(SourceFile); + buildRel += '/'; + buildRel += "moc_"; + buildRel += cmSystemTools::GetFilenameWithoutLastExtension(SourceFile); + if (wrk.Base().MultiConfig != MultiConfigT::SINGLE) { + buildRel += wrk.Base().ConfigSuffix; + } + buildRel += ".cpp"; + wrk.Gen().ParallelMocAutoRegister(buildRel); + BuildFile = wrk.Base().AbsoluteBuildPath(buildRel); + } + + 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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::MOC, emsg); + return false; + } + } + FindDependencies(wrk, content); + } + // Check dependency timestamps + std::string error; + std::string sourceDir = 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(GeneratorT::MOC, reason); + } + return true; + } + if (!error.empty()) { + wrk.LogError(GeneratorT::MOC, error); + return false; + } + } else { + std::string message = "Could not find dependency file "; + message += Quoted(depFileRel); + wrk.LogFileWarning(GeneratorT::MOC, SourceFile, message); + } + } + } + + return false; +} + +void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk) +{ + // Make sure the parent directory exists + if (wrk.FileSys().MakeParentDirectory(GeneratorT::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.push_back("--include"); + cmd.push_back(wrk.Moc().PredefsFileAbs); + } + cmd.push_back("-o"); + cmd.push_back(BuildFile); + cmd.push_back(SourceFile); + + // Execute moc command + ProcessResultT result; + if (wrk.RunProcess(GeneratorT::MOC, result, cmd)) { + // Moc command success + if (IncludeString.empty()) { + // Notify the generator that a not included file changed + 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(GeneratorT::MOC, emsg, cmd, result.StdOut); + } + wrk.FileSys().FileRemove(BuildFile); + } + } +} + +void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk) +{ + // Compute build file name + BuildFile = wrk.Base().AutogenIncludeDirAbs; + 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(GeneratorT::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(GeneratorT::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(GeneratorT::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(GeneratorT::UIC, reason); + } + return true; + } + } + + return false; +} + +void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk) +{ + // Make sure the parent directory exists + if (wrk.FileSys().MakeParentDirectory(GeneratorT::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.push_back("-o"); + cmd.push_back(BuildFile); + cmd.push_back(SourceFile); + + ProcessResultT result; + if (wrk.RunProcess(GeneratorT::UIC, result, cmd)) { + // Success + } else { + // 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(GeneratorT::UIC, emsg, cmd, result.StdOut); + } + wrk.FileSys().FileRemove(BuildFile); + } + } +} + +void cmQtAutoGeneratorMocUic::JobDeleterT::operator()(JobT* job) +{ + delete job; +} + +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( + GeneratorT genType, std::string const& message) const +{ + return Log().Info(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogWarning( + GeneratorT genType, std::string const& message) const +{ + return Log().Warning(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning( + GeneratorT genType, std::string const& filename, + std::string const& message) const +{ + return Log().WarningFile(genType, filename, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogError( + GeneratorT genType, std::string const& message) const +{ + Gen().ParallelRegisterJobError(); + Log().Error(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogFileError( + GeneratorT genType, std::string const& filename, + std::string const& message) const +{ + Gen().ParallelRegisterJobError(); + Log().ErrorFile(genType, filename, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError( + GeneratorT 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( + GeneratorT 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, + std::bind(&WorkerT::UVProcessFinished, &wrk)); + } + } +} + +void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished() +{ + { + std::lock_guard<std::mutex> lock(ProcessMutex_); + if (Process_ && Process_->IsFinished()) { + Process_.reset(); + } + } + // Notify idling thread + ProcessCondition_.notify_one(); +} cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() - : MultiConfig(cmQtAutoGen::WRAP) - , IncludeProjectDirsBefore(false) - , QtVersionMajor(4) - , MocSettingsChanged(false) - , MocPredefsChanged(false) - , MocRelaxedMode(false) - , UicSettingsChanged(false) + : Base_(&FileSys()) + , Moc_(&FileSys()) + , Stage_(StageT::SETTINGS_READ) + , JobsRemain_(0) + , JobError_(false) + , JobThreadsAbort_(false) + , MocAutoFileUpdated_(false) { // Precompile regular expressions - this->MocRegExpInclude.compile( + Moc_.RegExpInclude.compile( "[\n][ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); - this->UicRegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+" - "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); + Uic_.RegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+" + "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); + + // Initialize libuv asynchronous iteration request + UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this); } -bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) +cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() +{ +} + +bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) { // -- Meta - this->HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); + Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); // Utility lambdas auto InfoGet = [makefile](const char* key) { @@ -87,7 +1161,7 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) std::string const value = makefile->GetSafeDefinition(key); std::string::size_type pos = 0; while (pos < value.size()) { - std::string::size_type next = value.find(cmQtAutoGen::listSep, pos); + 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 @@ -102,7 +1176,7 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) } } pos += length; - pos += cmQtAutoGen::listSep.size(); + pos += ListSep.size(); } } return lists; @@ -112,7 +1186,7 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) { std::string keyConf = key; keyConf += '_'; - keyConf += this->GetInfoConfig(); + keyConf += InfoConfig(); valueConf = makefile->GetDefinition(keyConf); } if (valueConf == nullptr) { @@ -128,122 +1202,177 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) }; // -- Read info file - if (!makefile->ReadListFile(this->GetInfoFile().c_str())) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "File processing failed"); + if (!makefile->ReadListFile(InfoFile().c_str())) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), "File processing failed"); return false; } // -- Meta - this->MultiConfig = cmQtAutoGen::MultiConfigType(InfoGet("AM_MULTI_CONFIG")); - this->ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX"); - if (this->ConfigSuffix.empty()) { - this->ConfigSuffix = "_"; - this->ConfigSuffix += this->GetInfoConfig(); + Base_.MultiConfig = MultiConfigType(InfoGet("AM_MULTI_CONFIG")); + + Base_.ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX"); + if (Base_.ConfigSuffix.empty()) { + Base_.ConfigSuffix = "_"; + Base_.ConfigSuffix += InfoConfig(); } - this->SettingsFile = InfoGetConfig("AM_SETTINGS_FILE"); - if (this->SettingsFile.empty()) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "Settings file name missing"); + SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE"); + if (SettingsFile_.empty()) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Settings file name missing"); return false; } + { + unsigned long num = Base_.NumThreads; + if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL"), &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 - this->ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); - this->ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); - this->CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); - this->CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); - this->IncludeProjectDirsBefore = + 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"); - this->AutogenBuildDir = InfoGet("AM_BUILD_DIR"); - if (this->AutogenBuildDir.empty()) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "Autogen build directory missing"); + Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); + if (Base_.AutogenBuildDir.empty()) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), + "Autogen build directory missing"); return false; } // - Qt environment - if (!cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"), - &this->QtVersionMajor)) { - this->QtVersionMajor = 4; + { + unsigned long qtv = Base_.QtVersionMajor; + if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"), &qtv)) { + Base_.QtVersionMajor = static_cast<unsigned int>(qtv); + } } - this->MocExecutable = InfoGet("AM_QT_MOC_EXECUTABLE"); - this->UicExecutable = InfoGet("AM_QT_UIC_EXECUTABLE"); // - Moc - if (this->MocEnabled()) { - this->MocSkipList = InfoGetList("AM_MOC_SKIP"); - this->MocDefinitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); + Moc_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE"); + Moc_.Enabled = !Moc().Executable.empty(); + if (Moc().Enabled) { + { + auto lst = InfoGetList("AM_MOC_SKIP"); + Moc_.SkipList.insert(lst.begin(), lst.end()); + } + Moc_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); #ifdef _WIN32 { - std::string const win32("WIN32"); - if (!ListContains(this->MocDefinitions, win32)) { - this->MocDefinitions.push_back(win32); + std::string win32("WIN32"); + auto itB = Moc().Definitions.cbegin(); + auto itE = Moc().Definitions.cend(); + if (std::find(itB, itE, win32) == itE) { + Moc_.Definitions.emplace_back(std::move(win32)); } } #endif - this->MocIncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); - this->MocOptions = InfoGetList("AM_MOC_OPTIONS"); - this->MocRelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); - { - std::vector<std::string> const MocMacroNames = - InfoGetList("AM_MOC_MACRO_NAMES"); - for (std::string const& item : MocMacroNames) { - this->MocMacroFilters.emplace_back( - item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]")); - } + 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_]")); } { - std::vector<std::string> const mocDependFilters = - InfoGetList("AM_MOC_DEPEND_FILTERS"); - // Insert Q_PLUGIN_METADATA dependency filter - if (this->QtVersionMajor != 4) { - this->MocDependFilterPush("Q_PLUGIN_METADATA", - "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" - "[^\\)]*FILE[ \t]*\"([^\"]+)\""); + auto pushFilter = [this](std::string const& key, std::string const& exp, + std::string& error) { + if (!key.empty()) { + if (!exp.empty()) { + Moc_.DependFilters.push_back(KeyExpT()); + 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 - if ((mocDependFilters.size() % 2) == 0) { - for (std::vector<std::string>::const_iterator - dit = mocDependFilters.begin(), - ditEnd = mocDependFilters.end(); - dit != ditEnd; dit += 2) { - if (!this->MocDependFilterPush(*dit, *(dit + 1))) { - return false; + { + 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( + GeneratorT::MOC, InfoFile(), + "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); + return false; } - } else { - this->LogFileError( - cmQtAutoGen::MOC, this->GetInfoFile(), - "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); + } + if (!error.empty()) { + Log().ErrorFile(GeneratorT::MOC, InfoFile(), error); return false; } } - this->MocPredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); + Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); + // Install moc predefs job + if (!Moc().PredefsCmd.empty()) { + JobQueues_.MocPredefs.emplace_back(new JobMocPredefsT()); + } } // - Uic - if (this->UicEnabled()) { - this->UicSkipList = InfoGetList("AM_UIC_SKIP"); - this->UicSearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); - this->UicTargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS"); + Uic_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE"); + Uic_.Enabled = !Uic().Executable.empty(); + if (Uic().Enabled) { + { + auto lst = InfoGetList("AM_UIC_SKIP"); + Uic_.SkipList.insert(lst.begin(), lst.end()); + } + 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() << "/" + ost << "files/options lists sizes missmatch (" << sources.size() << "/" << options.size() << ")"; - this->LogFileError(cmQtAutoGen::UIC, this->GetInfoFile(), ost.str()); + Log().ErrorFile(GeneratorT::UIC, InfoFile(), ost.str()); return false; } auto fitEnd = sources.cend(); auto fit = sources.begin(); auto oit = options.begin(); while (fit != fitEnd) { - this->UicOptions[*fit] = std::move(*oit); + Uic_.Options[*fit] = std::move(*oit); ++fit; ++oit; } @@ -252,54 +1381,51 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) // Initialize source file jobs { - // Utility lambdas - auto AddJob = [this](std::map<std::string, SourceJob>& jobs, - std::string&& sourceFile) { - const bool moc = !this->MocSkip(sourceFile); - const bool uic = !this->UicSkip(sourceFile); - if (moc || uic) { - SourceJob& job = jobs[std::move(sourceFile)]; - job.Moc = moc; - job.Uic = uic; - } - }; + std::hash<std::string> stringHash; + std::set<std::size_t> uniqueHeaders; // Add header jobs for (std::string& hdr : InfoGetList("AM_HEADERS")) { - AddJob(this->HeaderJobs, std::move(hdr)); + const bool moc = !Moc().skipped(hdr); + const bool uic = !Uic().skipped(hdr); + if ((moc || uic) && uniqueHeaders.emplace(stringHash(hdr)).second) { + JobQueues_.Headers.emplace_back( + new JobParseT(std::move(hdr), moc, uic, true)); + } } // Add source jobs { std::vector<std::string> sources = InfoGetList("AM_SOURCES"); // Add header(s) for the source file - for (std::string const& src : sources) { - const bool srcMoc = !this->MocSkip(src); - const bool srcUic = !this->UicSkip(src); + for (std::string& src : sources) { + const bool srcMoc = !Moc().skipped(src); + const bool srcUic = !Uic().skipped(src); if (!srcMoc && !srcUic) { continue; } // Search for the default header file and a private header - std::array<std::string, 2> headerBases; - headerBases[0] = SubDirPrefix(src); - headerBases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src); - headerBases[1] = headerBases[0]; - headerBases[1] += "_p"; - for (std::string const& headerBase : headerBases) { - std::string header; - if (this->FindHeader(header, headerBase)) { - const bool moc = srcMoc && !this->MocSkip(header); - const bool uic = srcUic && !this->UicSkip(header); - if (moc || uic) { - SourceJob& job = this->HeaderJobs[std::move(header)]; - job.Moc = moc; - job.Uic = uic; + { + std::array<std::string, 2> bases; + bases[0] = SubDirPrefix(src); + bases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src); + bases[1] = bases[0]; + bases[1] += "_p"; + for (std::string const& headerBase : bases) { + std::string header; + if (Base().FindHeader(header, headerBase)) { + const bool moc = srcMoc && !Moc().skipped(header); + const bool uic = srcUic && !Uic().skipped(header); + if ((moc || uic) && + uniqueHeaders.emplace(stringHash(header)).second) { + JobQueues_.Headers.emplace_back( + new JobParseT(std::move(header), moc, uic, true)); + } } } } - } - // Add Source jobs - for (std::string& src : sources) { - AddJob(this->SourceJobs, std::move(src)); + // Add source job + JobQueues_.Sources.emplace_back( + new JobParseT(std::move(src), srcMoc, srcUic)); } } } @@ -308,58 +1434,58 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) // ------------------------ // Init file path checksum generator - this->FilePathChecksum.setupParentDirs( - this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir, - this->ProjectBinaryDir); + Base_.FilePathChecksum.setupParentDirs( + Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir, + Base().ProjectBinaryDir); // include directory - this->AutogenIncludeDir = "include"; - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - this->AutogenIncludeDir += this->ConfigSuffix; + Base_.AutogenIncludeDirRel = "include"; + if (Base().MultiConfig != MultiConfigT::SINGLE) { + Base_.AutogenIncludeDirRel += Base().ConfigSuffix; } - this->AutogenIncludeDir += "/"; + Base_.AutogenIncludeDirRel += "/"; + Base_.AutogenIncludeDirAbs = + Base_.AbsoluteBuildPath(Base().AutogenIncludeDirRel); // Moc variables - if (this->MocEnabled()) { + if (Moc().Enabled) { // Mocs compilation file - this->MocCompFileRel = "mocs_compilation"; - if (this->MultiConfig == cmQtAutoGen::FULL) { - this->MocCompFileRel += this->ConfigSuffix; + Moc_.CompFileRel = "mocs_compilation"; + if (Base_.MultiConfig == MultiConfigT::MULTI) { + Moc_.CompFileRel += Base().ConfigSuffix; } - this->MocCompFileRel += ".cpp"; - this->MocCompFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->MocCompFileRel); + Moc_.CompFileRel += ".cpp"; + Moc_.CompFileAbs = Base_.AbsoluteBuildPath(Moc().CompFileRel); // Moc predefs file - if (!this->MocPredefsCmd.empty()) { - this->MocPredefsFileRel = "moc_predefs"; - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - this->MocPredefsFileRel += this->ConfigSuffix; + if (!Moc_.PredefsCmd.empty()) { + Moc_.PredefsFileRel = "moc_predefs"; + if (Base_.MultiConfig != MultiConfigT::SINGLE) { + Moc_.PredefsFileRel += Base().ConfigSuffix; } - this->MocPredefsFileRel += ".h"; - this->MocPredefsFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->MocPredefsFileRel); + Moc_.PredefsFileRel += ".h"; + Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel); } // Sort include directories on demand - if (this->IncludeProjectDirsBefore) { + if (Base().IncludeProjectDirsBefore) { // Move strings to temporary list std::list<std::string> includes; - includes.insert(includes.end(), this->MocIncludePaths.begin(), - this->MocIncludePaths.end()); - this->MocIncludePaths.clear(); - this->MocIncludePaths.reserve(includes.size()); + 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 = { - { &this->ProjectBinaryDir, &this->ProjectSourceDir } + { &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())) { - this->MocIncludePaths.push_back(path); + Moc_.IncludePaths.push_back(path); it = includes.erase(it); } else { ++it; @@ -368,14 +1494,14 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) } } // Append remaining directories - this->MocIncludePaths.insert(this->MocIncludePaths.end(), - includes.begin(), includes.end()); + Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(), + includes.end()); } // Compose moc includes list { std::set<std::string> frameworkPaths; - for (std::string const& path : this->MocIncludePaths) { - this->MocIncludes.push_back("-I" + path); + 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 @@ -388,1346 +1514,511 @@ bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) } // Append framework includes for (std::string const& path : frameworkPaths) { - this->MocIncludes.push_back("-F"); - this->MocIncludes.push_back(path); + Moc_.Includes.push_back("-F"); + Moc_.Includes.push_back(path); } } // Setup single list with all options { // Add includes - this->MocAllOptions.insert(this->MocAllOptions.end(), - this->MocIncludes.begin(), - this->MocIncludes.end()); + Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(), + Moc().Includes.end()); // Add definitions - for (std::string const& def : this->MocDefinitions) { - this->MocAllOptions.push_back("-D" + def); + for (std::string const& def : Moc().Definitions) { + Moc_.AllOptions.push_back("-D" + def); } // Add options - this->MocAllOptions.insert(this->MocAllOptions.end(), - this->MocOptions.begin(), - this->MocOptions.end()); + Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(), + Moc().Options.end()); } } return true; } -void cmQtAutoGeneratorMocUic::SettingsFileRead(cmMakefile* makefile) +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 (this->MocEnabled()) { + if (Moc_.Enabled) { std::string str; - str += this->MocExecutable; + str += Moc().Executable; str += sep; - str += cmJoin(this->MocAllOptions, ";"); + str += cmJoin(Moc().AllOptions, ";"); str += sep; - str += this->IncludeProjectDirsBefore ? "TRUE" : "FALSE"; + str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE"; str += sep; - str += cmJoin(this->MocPredefsCmd, ";"); + str += cmJoin(Moc().PredefsCmd, ";"); str += sep; - this->SettingsStringMoc = crypt.HashString(str); + SettingsStringMoc_ = crypt.HashString(str); } - if (this->UicEnabled()) { + if (Uic().Enabled) { std::string str; - str += this->UicExecutable; + str += Uic().Executable; str += sep; - str += cmJoin(this->UicTargetOptions, ";"); - for (const auto& item : this->UicOptions) { + str += cmJoin(Uic().TargetOptions, ";"); + for (const auto& item : Uic().Options) { str += sep; str += item.first; str += sep; str += cmJoin(item.second, ";"); } str += sep; - this->SettingsStringUic = crypt.HashString(str); + SettingsStringUic_ = crypt.HashString(str); } } - // Read old settings - if (makefile->ReadListFile(this->SettingsFile.c_str())) { - { - auto SMatch = [makefile](const char* key, std::string const& value) { - return (value == makefile->GetSafeDefinition(key)); - }; - if (!SMatch(SettingsKeyMoc, this->SettingsStringMoc)) { - this->MocSettingsChanged = true; + // 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 (!SMatch(SettingsKeyUic, this->SettingsStringUic)) { - this->UicSettingsChanged = 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; } } - // In case any setting changed remove the old settings file. - // This triggers a full rebuild on the next run if the current - // build is aborted before writing the current settings in the end. - if (this->SettingsChanged()) { - cmSystemTools::RemoveFile(this->SettingsFile); - } - } else { - // If the file could not be read re-generate everythiung. - this->MocSettingsChanged = true; - this->UicSettingsChanged = true; } } -bool cmQtAutoGeneratorMocUic::SettingsFileWrite() +void cmQtAutoGeneratorMocUic::SettingsFileWrite() { - bool success = true; + std::lock_guard<std::mutex> jobsLock(JobsMutex_); // Only write if any setting changed - if (this->SettingsChanged()) { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::GEN, "Writing settings file " + - cmQtAutoGen::Quoted(this->SettingsFile)); + if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) { + if (Log().Verbose()) { + Log().Info(GeneratorT::GEN, + "Writing settings file " + Quoted(SettingsFile_)); } // Compose settings file content - std::string settings; + std::string content; { - auto SettingAppend = [&settings](const char* key, - std::string const& value) { - settings += "set("; - settings += key; - settings += " "; - settings += cmOutputConverter::EscapeForCMake(value); - settings += ")\n"; + auto SettingAppend = [&content](const char* key, + std::string const& value) { + if (!value.empty()) { + content += key; + content += ':'; + content += value; + content += '\n'; + } }; - SettingAppend(SettingsKeyMoc, this->SettingsStringMoc); - SettingAppend(SettingsKeyUic, this->SettingsStringUic); + SettingAppend("moc", SettingsStringMoc_); + SettingAppend("uic", SettingsStringUic_); } // Write settings file - if (!this->FileWrite(cmQtAutoGen::GEN, this->SettingsFile, settings)) { - this->LogFileError(cmQtAutoGen::GEN, this->SettingsFile, - "Settings file writing failed"); + if (!FileSys().FileWrite(GeneratorT::GEN, SettingsFile_, content)) { + Log().ErrorFile(GeneratorT::GEN, SettingsFile_, + "Settings file writing failed"); // Remove old settings file to trigger a full rebuild on the next run - cmSystemTools::RemoveFile(this->SettingsFile); - success = false; + FileSys().FileRemove(SettingsFile_); + RegisterJobError(); } } - return success; } -bool cmQtAutoGeneratorMocUic::Process(cmMakefile* makefile) +void cmQtAutoGeneratorMocUic::CreateDirectories() { - // the program goes through all .cpp files to see which moc files are - // included. It is not really interesting how the moc file is named, but - // what file the moc is created from. Once a moc is included the same moc - // may not be included in the mocs_compilation.cpp file anymore. - // OTOH if there's a header containing Q_OBJECT where no corresponding - // moc file is included anywhere a moc_<filename>.cpp file is created and - // included in the mocs_compilation.cpp file. - - if (!this->InitInfoFile(makefile)) { - return false; - } - // Read latest settings - this->SettingsFileRead(makefile); - // Create AUTOGEN include directory - { - std::string const incDirAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->AutogenIncludeDir); - if (!cmSystemTools::MakeDirectory(incDirAbs)) { - this->LogFileError(cmQtAutoGen::GEN, incDirAbs, - "Could not create directory"); - return false; - } - } - - // Parse source files - for (const auto& item : this->SourceJobs) { - if (!this->ParseSourceFile(item.first, item.second)) { - return false; - } - } - // Parse header files - for (const auto& item : this->HeaderJobs) { - if (!this->ParseHeaderFile(item.first, item.second)) { - return false; - } - } - // Read missing dependency information - if (!this->ParsePostprocess()) { - return false; + if (!FileSys().MakeDirectory(GeneratorT::GEN, Base().AutogenIncludeDirAbs)) { + RegisterJobError(); } - - // Generate files - if (!this->MocGenerateAll()) { - return false; - } - if (!this->UicGenerateAll()) { - return false; - } - - if (!this->SettingsFileWrite()) { - return false; - } - - return true; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParseSourceFile(std::string const& absFilename, - const SourceJob& job) +bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue) { - std::string contentText; - std::string error; - bool success = this->FileRead(contentText, absFilename, &error); - if (success) { - if (!contentText.empty()) { - if (job.Moc) { - success = this->MocParseSourceContent(absFilename, contentText); - } - if (success && job.Uic) { - success = this->UicParseContent(absFilename, contentText); + 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; } - } else { - this->LogFileWarning(cmQtAutoGen::GEN, absFilename, - "The source file is empty"); + done = true; } - } else { - this->LogFileError(cmQtAutoGen::GEN, absFilename, - "Could not read the source file: " + error); } - return success; -} -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParseHeaderFile(std::string const& absFilename, - const SourceJob& job) -{ - std::string contentText; - std::string error; - bool success = this->FileRead(contentText, absFilename, &error); - if (success) { - if (!contentText.empty()) { - if (job.Moc) { - this->MocParseHeaderContent(absFilename, contentText); - } - if (job.Uic) { - success = this->UicParseContent(absFilename, contentText); + 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 { - this->LogFileWarning(cmQtAutoGen::GEN, absFilename, - "The header file is empty"); - } - } else { - this->LogFileError(cmQtAutoGen::GEN, absFilename, - "Could not read the header file: " + error); - } - return success; -} - -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParsePostprocess() -{ - bool success = true; - // Read missing dependencies - for (auto& item : this->MocJobsIncluded) { - if (!item->DependsValid) { - std::string content; - std::string error; - if (this->FileRead(content, item->SourceFile, &error)) { - this->MocFindDepends(item->SourceFile, content, item->Depends); - item->DependsValid = true; + // Notify threads + if (queueSize == 1) { + JobsConditionRead_.notify_one(); } else { - std::string emsg = "Could not read file\n "; - emsg += item->SourceFile; - emsg += "\nrequired by moc include \""; - emsg += item->IncludeString; - emsg += "\".\n"; - emsg += error; - this->LogFileError(cmQtAutoGen::MOC, item->Includer, emsg); - success = false; - break; + JobsConditionRead_.notify_all(); } } } - return success; -} - -/** - * @brief Tests if the file should be ignored for moc scanning - * @return True if the file should be ignored - */ -bool cmQtAutoGeneratorMocUic::MocSkip(std::string const& absFilename) const -{ - if (this->MocEnabled()) { - // Test if the file name is on the skip list - if (!ListContains(this->MocSkipList, absFilename)) { - return false; - } - } - return true; -} -/** - * @brief Tests if the C++ content requires moc processing - * @return True if moc is required - */ -bool cmQtAutoGeneratorMocUic::MocRequired(std::string const& contentText, - std::string* macroName) -{ - for (KeyRegExp& filter : this->MocMacroFilters) { - // Run a simple find string operation before the expensive - // regular expression check - if (contentText.find(filter.Key) != std::string::npos) { - if (filter.RegExp.find(contentText)) { - // Return macro name on demand - if (macroName != nullptr) { - *macroName = filter.Key; - } - return true; - } - } - } - return false; + return done; } -std::string cmQtAutoGeneratorMocUic::MocStringMacros() const +void cmQtAutoGeneratorMocUic::ThreadsStop() { - std::string res; - const auto itB = this->MocMacroFilters.cbegin(); - const auto itE = this->MocMacroFilters.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; + 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(); } - return res; } -std::string cmQtAutoGeneratorMocUic::MocStringHeaders( - std::string const& fileBase) const +bool cmQtAutoGeneratorMocUic::ThreadsJobsDone() { - std::string res = fileBase; - res += ".{"; - res += cmJoin(this->HeaderExtensions, ","); - res += "}"; - return res; + std::lock_guard<std::mutex> jobsLock(JobsMutex_); + return (JobsRemain_ == 0); } -std::string cmQtAutoGeneratorMocUic::MocFindIncludedHeader( - std::string const& sourcePath, std::string const& includeBase) const +void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle) { - std::string header; - // Search in vicinity of the source - if (!this->FindHeader(header, sourcePath + includeBase)) { - // Search in include directories - for (std::string const& path : this->MocIncludePaths) { - std::string fullPath = path; - fullPath.push_back('/'); - fullPath += includeBase; - if (this->FindHeader(header, fullPath)) { - break; - } - } - } - // Sanitize - if (!header.empty()) { - header = cmSystemTools::GetRealPath(header); + bool const jobProcessed(jobHandle); + if (jobProcessed) { + jobHandle.reset(nullptr); } - return header; -} - -bool cmQtAutoGeneratorMocUic::MocFindIncludedFile( - std::string& absFile, std::string const& sourcePath, - std::string const& includeString) const -{ - bool success = false; - // Search in vicinity of the source { - std::string testPath = sourcePath; - testPath += includeString; - if (cmSystemTools::FileExists(testPath.c_str())) { - absFile = cmSystemTools::GetRealPath(testPath); - success = true; - } - } - // Search in include directories - if (!success) { - for (std::string const& path : this->MocIncludePaths) { - std::string fullPath = path; - fullPath.push_back('/'); - fullPath += includeString; - if (cmSystemTools::FileExists(fullPath.c_str())) { - absFile = cmSystemTools::GetRealPath(fullPath); - success = true; - break; + 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(); } } - } - return success; -} - -bool cmQtAutoGeneratorMocUic::MocDependFilterPush(std::string const& key, - std::string const& regExp) -{ - std::string error; - if (!key.empty()) { - if (!regExp.empty()) { - KeyRegExp filter; - filter.Key = key; - if (filter.RegExp.compile(regExp)) { - this->MocDependFilters.push_back(std::move(filter)); - } else { - error = "Regular expression compiling failed"; - } - } else { - error = "Regular expression is empty"; - } - } else { - error = "Key is empty"; - } - if (!error.empty()) { - std::string emsg = "AUTOMOC_DEPEND_FILTERS: "; - emsg += error; - emsg += "\n"; - emsg += " Key: "; - emsg += cmQtAutoGen::Quoted(key); - emsg += "\n"; - emsg += " RegExp: "; - emsg += cmQtAutoGen::Quoted(regExp); - emsg += "\n"; - this->LogError(cmQtAutoGen::MOC, emsg); - return false; - } - return true; -} - -void cmQtAutoGeneratorMocUic::MocFindDepends(std::string const& absFilename, - std::string const& contentText, - std::set<std::string>& depends) -{ - if (this->MocDependFilters.empty() && contentText.empty()) { - return; - } - - std::vector<std::string> matches; - for (KeyRegExp& filter : this->MocDependFilters) { - // Run a simple find string check - if (contentText.find(filter.Key) != std::string::npos) { - // Run the expensive regular expression check loop - const char* contentChars = contentText.c_str(); - while (filter.RegExp.find(contentChars)) { - std::string match = filter.RegExp.match(1); - if (!match.empty()) { - matches.emplace_back(std::move(match)); - } - contentChars += filter.RegExp.end(); - } + // Wait for new jobs + while (!JobThreadsAbort_ && JobQueue_.empty()) { + JobsConditionRead_.wait(jobsLock); } - } - - if (!matches.empty()) { - std::string const sourcePath = SubDirPrefix(absFilename); - for (std::string const& match : matches) { - // Find the dependency file - std::string incFile; - if (this->MocFindIncludedFile(incFile, sourcePath, match)) { - depends.insert(incFile); - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Found dependency:\n " + - cmQtAutoGen::Quoted(absFilename) + "\n " + - cmQtAutoGen::Quoted(incFile)); - } - } else { - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, - "Could not find dependency file " + - cmQtAutoGen::Quoted(match)); - } + // Try to pick up a new job handle + if (!JobThreadsAbort_ && !JobQueue_.empty()) { + jobHandle = std::move(JobQueue_.front()); + JobQueue_.pop_front(); } } } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::MocParseSourceContent( - std::string const& absFilename, std::string const& contentText) +void cmQtAutoGeneratorMocUic::ParallelRegisterJobError() { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename); - } - - auto AddJob = [this, &absFilename](std::string const& sourceFile, - std::string const& includeString, - std::string const* content) { - auto job = cm::make_unique<MocJobIncluded>(); - job->SourceFile = sourceFile; - job->BuildFileRel = this->AutogenIncludeDir; - job->BuildFileRel += includeString; - job->Includer = absFilename; - job->IncludeString = includeString; - job->DependsValid = (content != nullptr); - if (job->DependsValid) { - this->MocFindDepends(sourceFile, *content, job->Depends); - } - this->MocJobsIncluded.push_back(std::move(job)); - }; - - struct MocInc - { - std::string Inc; // full include string - std::string Dir; // include string directory - std::string Base; // include string file base - }; - - // Extract moc includes from file - std::vector<MocInc> mocIncsUsc; - std::vector<MocInc> mocIncsDot; - { - const char* contentChars = contentText.c_str(); - if (strstr(contentChars, "moc") != nullptr) { - while (this->MocRegExpInclude.find(contentChars)) { - std::string incString = this->MocRegExpInclude.match(1); - std::string incDir(SubDirPrefix(incString)); - std::string incBase = - cmSystemTools::GetFilenameWithoutLastExtension(incString); - if (cmHasLiteralPrefix(incBase, "moc_")) { - // moc_<BASE>.cxx - // Remove the moc_ part from the base name - mocIncsUsc.push_back(MocInc{ std::move(incString), std::move(incDir), - incBase.substr(4) }); - } else { - // <BASE>.moc - mocIncsDot.push_back(MocInc{ std::move(incString), std::move(incDir), - std::move(incBase) }); - } - // Forward content pointer - contentChars += this->MocRegExpInclude.end(); - } - } - } - - std::string selfMacroName; - const bool selfRequiresMoc = this->MocRequired(contentText, &selfMacroName); - - // Check if there is anything to do - if (!selfRequiresMoc && mocIncsUsc.empty() && mocIncsDot.empty()) { - return true; - } - - // Scan file variables - std::string const scanFileDir = SubDirPrefix(absFilename); - std::string const scanFileBase = - cmSystemTools::GetFilenameWithoutLastExtension(absFilename); - // Relaxed mode variables - bool ownDotMocIncluded = false; - std::string ownMocUscInclude; - std::string ownMocUscHeader; - - // Process moc_<BASE>.cxx includes - for (const MocInc& mocInc : mocIncsUsc) { - std::string const header = - this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (this->MocSkip(header)) { - continue; - } - // Register moc job - AddJob(header, mocInc.Inc, nullptr); - // Store meta information for relaxed mode - if (this->MocRelaxedMode && (mocInc.Base == scanFileBase)) { - ownMocUscInclude = mocInc.Inc; - ownMocUscHeader = header; - } - } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but could not find the header "; - emsg += cmQtAutoGen::Quoted(this->MocStringHeaders(mocInc.Base)); - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; - } - } - - // Process <BASE>.moc includes - for (const MocInc& mocInc : mocIncsDot) { - const bool ownMoc = (mocInc.Base == scanFileBase); - if (this->MocRelaxedMode) { - // Relaxed mode - if (selfRequiresMoc && ownMoc) { - // Add self - AddJob(absFilename, mocInc.Inc, &contentText); - 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 = - this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (this->MocSkip(header)) { - continue; - } - // Register moc job - AddJob(header, mocInc.Inc, nullptr); - if (!selfRequiresMoc) { - if (ownMoc) { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but does not contain a "; - emsg += this->MocStringMacros(); - emsg += " macro.\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(header); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += " for a compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); - } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += " instead of "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += ".\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(header); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += " for compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); - } - } - } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", which seems to be the moc file from a different " - "source file. CMake also could not find a matching " - "header."; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; - } - } - } else { - // Strict mode - if (ownMoc) { - // Include self - AddJob(absFilename, mocInc.Inc, &contentText); - ownDotMocIncluded = true; - // Accept but issue a warning if moc isn't required - if (!selfRequiresMoc) { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but does not contain a "; - emsg += this->MocStringMacros(); - emsg += " macro."; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); - } - } else { - // Don't allow <BASE>.moc include other than self in strict mode - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", which seems to be the moc file from a different " - "source file.\nThis is not supported. Include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += " to run moc on this source file."; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; - } - } - } - - if (selfRequiresMoc && !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. - if (this->MocRelaxedMode && !ownMocUscInclude.empty()) { - // This is for KDE4 compatibility: - std::string emsg = "The file contains a "; - emsg += selfMacroName; - emsg += " macro, but does not include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += ". Instead it includes "; - emsg += cmQtAutoGen::Quoted(ownMocUscInclude); - emsg += ".\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(absFilename); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += " for compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); - - // Remove own header job - { - auto itC = this->MocJobsIncluded.begin(); - auto itE = this->MocJobsIncluded.end(); - for (; itC != itE; ++itC) { - if ((*itC)->SourceFile == ownMocUscHeader) { - if ((*itC)->IncludeString == ownMocUscInclude) { - this->MocJobsIncluded.erase(itC); - break; - } - } - } - } - // Add own source job - AddJob(absFilename, ownMocUscInclude, &contentText); - } else { - // Otherwise always error out since it will not compile: - std::string emsg = "The file contains a "; - emsg += selfMacroName; - emsg += " macro, but does not include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += "!\nConsider to\n - add #include \""; - emsg += scanFileBase; - emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; - } - } - return true; + std::lock_guard<std::mutex> jobsLock(JobsMutex_); + RegisterJobError(); } -void cmQtAutoGeneratorMocUic::MocParseHeaderContent( - std::string const& absFilename, std::string const& contentText) +// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be +// locked +void cmQtAutoGeneratorMocUic::RegisterJobError() { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename); - } - - auto const fit = - std::find_if(this->MocJobsIncluded.cbegin(), this->MocJobsIncluded.cend(), - [&absFilename](std::unique_ptr<MocJobIncluded> const& job) { - return job->SourceFile == absFilename; - }); - if (fit == this->MocJobsIncluded.cend()) { - if (this->MocRequired(contentText)) { - auto job = cm::make_unique<MocJobAuto>(); - job->SourceFile = absFilename; - { - std::string& bld = job->BuildFileRel; - bld = this->FilePathChecksum.getPart(absFilename); - bld += '/'; - bld += "moc_"; - bld += cmSystemTools::GetFilenameWithoutLastExtension(absFilename); - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - bld += this->ConfigSuffix; - } - bld += ".cpp"; - } - this->MocFindDepends(absFilename, contentText, job->Depends); - this->MocJobsAuto.push_back(std::move(job)); + JobError_ = true; + if (!JobThreadsAbort_) { + JobThreadsAbort_ = true; + // Clear remaining jobs + if (JobsRemain_ != 0) { + JobsRemain_ -= JobQueue_.size(); + JobQueue_.clear(); } } } -bool cmQtAutoGeneratorMocUic::MocGenerateAll() +bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle) { - if (!this->MocEnabled()) { - return true; - } - - // Look for name collisions in included moc files - { - bool collision = false; - std::map<std::string, std::vector<MocJobIncluded const*>> collisions; - for (auto const& job : this->MocJobsIncluded) { - auto& list = collisions[job->IncludeString]; - if (!list.empty()) { - collision = true; - } - list.push_back(job.get()); - } - if (collision) { - std::string emsg = - "Included moc files with the same name will be " - "generated from different sources.\n" - "Consider 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" - "Include conflicts\n" - "-----------------\n"; - const auto& colls = collisions; - for (auto const& coll : colls) { - if (coll.second.size() > 1) { - emsg += cmQtAutoGen::Quoted(coll.first); - emsg += " included in\n"; - for (const MocJobIncluded* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->Includer); - emsg += "\n"; - } - emsg += "would be generated from\n"; - for (const MocJobIncluded* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->SourceFile); - emsg += "\n"; + 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 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(GeneratorT::MOC, error); + RegisterJobError(); + } + // Do not push this job in since the included moc file already + // gets generated by an other job. + pushJobHandle = false; + break; } } } - this->LogError(cmQtAutoGen::MOC, emsg); - return false; } - } - - // (Re)generate moc_predefs.h on demand - if (!this->MocPredefsCmd.empty()) { - if (this->MocSettingsChanged || - !cmSystemTools::FileExists(this->MocPredefsFileAbs)) { - if (this->GetVerbose()) { - this->LogBold("Generating MOC predefs " + this->MocPredefsFileRel); - } - - std::string output; - { - // Compose command - std::vector<std::string> cmd = this->MocPredefsCmd; - // Add includes - cmd.insert(cmd.end(), this->MocIncludes.begin(), - this->MocIncludes.end()); - // Add definitions - for (std::string const& def : this->MocDefinitions) { - cmd.push_back("-D" + def); - } - // Execute command - if (!this->RunCommand(cmd, output)) { - this->LogCommandError(cmQtAutoGen::MOC, - "moc_predefs generation failed", cmd, output); - return false; - } - } - - // (Re)write predefs file only on demand - if (this->FileDiffers(this->MocPredefsFileAbs, output)) { - if (this->FileWrite(cmQtAutoGen::MOC, this->MocPredefsFileAbs, - output)) { - this->MocPredefsChanged = true; - } else { - this->LogFileError(cmQtAutoGen::MOC, this->MocPredefsFileAbs, - "moc_predefs file writing failed"); - return false; - } - } else { - // Touch to update the time stamp - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, - "Touching moc_predefs " + this->MocPredefsFileRel); - } - cmSystemTools::Touch(this->MocPredefsFileAbs, false); - } - } - - // Add moc_predefs.h to moc file dependencies - for (auto const& item : this->MocJobsIncluded) { - item->Depends.insert(this->MocPredefsFileAbs); - } - for (auto const& item : this->MocJobsAuto) { - item->Depends.insert(this->MocPredefsFileAbs); - } - } - - // Generate moc files that are included by source files. - for (auto const& item : this->MocJobsIncluded) { - if (!this->MocGenerateFile(*item)) { - return false; - } - } - // Generate moc files that are _not_ included by source files. - bool autoNameGenerated = false; - for (auto const& item : this->MocJobsAuto) { - if (!this->MocGenerateFile(*item, &autoNameGenerated)) { - return false; - } - } - - // Compose mocs compilation file content - { - std::string mocs = - "// This file is autogenerated. Changes will be overwritten.\n"; - if (this->MocJobsAuto.empty()) { - // Placeholder content - mocs += - "// No files found that require moc or the moc files are included\n"; - mocs += "enum some_compilers { need_more_than_nothing };\n"; - } else { - // Valid content - for (const auto& item : this->MocJobsAuto) { - mocs += "#include \""; - mocs += item->BuildFileRel; - mocs += "\"\n"; - } - } - - if (this->FileDiffers(this->MocCompFileAbs, mocs)) { - // Actually write mocs compilation file - if (this->GetVerbose()) { - this->LogBold("Generating MOC compilation " + this->MocCompFileRel); - } - if (!this->FileWrite(cmQtAutoGen::MOC, this->MocCompFileAbs, mocs)) { - this->LogFileError(cmQtAutoGen::MOC, this->MocCompFileAbs, - "mocs compilation file writing failed"); - return false; - } - } else if (autoNameGenerated) { - // Only touch mocs compilation file - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, - "Touching mocs compilation " + this->MocCompFileRel); - } - cmSystemTools::Touch(this->MocCompFileAbs, false); + // Push job on demand + if (pushJobHandle) { + JobQueues_.Moc.emplace_back(std::move(jobHandle)); } } - - return true; + return !JobError_; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::MocGenerateFile(const MocJobAuto& mocJob, - bool* generated) +bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle) { - bool success = true; - - std::string const mocFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, mocJob.BuildFileRel); - - bool generate = false; - std::string generateReason; - if (!generate && !cmSystemTools::FileExists(mocFileAbs.c_str())) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from its source file "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because it doesn't exist"; - } - generate = true; - } - if (!generate && this->MocSettingsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because the MOC settings changed"; - } - generate = true; - } - if (!generate && this->MocPredefsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because moc_predefs.h changed"; - } - generate = true; - } - if (!generate) { - std::string error; - if (FileIsOlderThan(mocFileAbs, mocJob.SourceFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " because it's older than its source file "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - } - generate = true; - } else { - if (!error.empty()) { - this->LogError(cmQtAutoGen::MOC, error); - success = false; - } - } - } - if (success && !generate) { - // Test if a dependency file is newer - std::string error; - for (std::string const& depFile : mocJob.Depends) { - if (FileIsOlderThan(mocFileAbs, depFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because it is older than "; - generateReason += cmQtAutoGen::Quoted(depFile); + 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 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(GeneratorT::UIC, error); + RegisterJobError(); } - generate = true; - break; - } - if (!error.empty()) { - this->LogError(cmQtAutoGen::MOC, error); - success = false; + // Do not push this job in since the uic file already + // gets generated by an other job. + pushJobHandle = false; break; } } - } - - if (generate) { - // Log - if (this->GetVerbose()) { - this->LogBold("Generating MOC source " + mocJob.BuildFileRel); - this->LogInfo(cmQtAutoGen::MOC, generateReason); - } - - // Make sure the parent directory exists - if (this->MakeParentDirectory(cmQtAutoGen::MOC, mocFileAbs)) { - // Compose moc command - std::vector<std::string> cmd; - cmd.push_back(this->MocExecutable); - // Add options - cmd.insert(cmd.end(), this->MocAllOptions.begin(), - this->MocAllOptions.end()); - // Add predefs include - if (!this->MocPredefsFileAbs.empty()) { - cmd.push_back("--include"); - cmd.push_back(this->MocPredefsFileAbs); - } - cmd.push_back("-o"); - cmd.push_back(mocFileAbs); - cmd.push_back(mocJob.SourceFile); - - // Execute moc command - std::string output; - if (this->RunCommand(cmd, output)) { - // Success - if (generated != nullptr) { - *generated = true; - } - } else { - // Moc command failed - { - std::string emsg = "moc failed for\n "; - emsg += cmQtAutoGen::Quoted(mocJob.SourceFile); - this->LogCommandError(cmQtAutoGen::MOC, emsg, cmd, output); - } - cmSystemTools::RemoveFile(mocFileAbs); - success = false; - } - } else { - // Parent directory creation failed - success = false; + if (pushJobHandle) { + JobQueues_.Uic.emplace_back(std::move(jobHandle)); } } - return success; + return !JobError_; } -/** - * @brief Tests if the file name is in the skip list - */ -bool cmQtAutoGeneratorMocUic::UicSkip(std::string const& absFilename) const +bool cmQtAutoGeneratorMocUic::ParallelMocIncluded( + std::string const& sourceFile) { - if (this->UicEnabled()) { - // Test if the file name is on the skip list - if (!ListContains(this->UicSkipList, absFilename)) { - return false; - } - } - return true; + std::lock_guard<std::mutex> mocLock(JobsMutex_); + return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); } -bool cmQtAutoGeneratorMocUic::UicParseContent(std::string const& absFilename, - std::string const& contentText) +void cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( + std::string const& mocFile) { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::UIC, "Checking: " + absFilename); - } - - std::vector<std::string> includes; - // Extracte includes - { - const char* contentChars = contentText.c_str(); - if (strstr(contentChars, "ui_") != nullptr) { - while (this->UicRegExpInclude.find(contentChars)) { - includes.push_back(this->UicRegExpInclude.match(1)); - contentChars += this->UicRegExpInclude.end(); - } - } - } - - for (std::string const& includeString : includes) { - std::string uiInputFile; - if (!UicFindIncludedFile(uiInputFile, absFilename, includeString)) { - return false; - } - // Check if this file should be skipped - if (this->UicSkip(uiInputFile)) { - continue; - } - // Check if the job already exists - bool jobExists = false; - for (const auto& job : this->UicJobs) { - if ((job->SourceFile == uiInputFile) && - (job->IncludeString == includeString)) { - jobExists = true; - break; - } - } - if (!jobExists) { - auto job = cm::make_unique<UicJob>(); - job->SourceFile = uiInputFile; - job->BuildFileRel = this->AutogenIncludeDir; - job->BuildFileRel += includeString; - job->Includer = absFilename; - job->IncludeString = includeString; - this->UicJobs.push_back(std::move(job)); - } - } - - return true; + std::lock_guard<std::mutex> mocLock(JobsMutex_); + MocAutoFiles_.emplace(mocFile); } -bool cmQtAutoGeneratorMocUic::UicFindIncludedFile( - std::string& absFile, std::string const& sourceFile, - std::string const& includeString) +void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated() { - bool success = false; - std::string searchFile = - cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3); - searchFile += ".ui"; - // Collect search paths list - std::vector<std::string> testFiles; - { - std::string const searchPath = SubDirPrefix(includeString); - - std::string searchFileFull; - if (!searchPath.empty()) { - searchFileFull = searchPath; - searchFileFull += searchFile; - } - // Vicinity of the source - { - std::string const sourcePath = SubDirPrefix(sourceFile); - testFiles.push_back(sourcePath + searchFile); - if (!searchPath.empty()) { - testFiles.push_back(sourcePath + searchFileFull); - } - } - // AUTOUIC search paths - if (!this->UicSearchPaths.empty()) { - for (std::string const& sPath : this->UicSearchPaths) { - testFiles.push_back((sPath + "/").append(searchFile)); - } - if (!searchPath.empty()) { - for (std::string const& sPath : this->UicSearchPaths) { - testFiles.push_back((sPath + "/").append(searchFileFull)); - } - } - } - } - - // Search for the .ui file! - for (std::string const& testFile : testFiles) { - if (cmSystemTools::FileExists(testFile.c_str())) { - absFile = cmSystemTools::GetRealPath(testFile); - success = true; - break; - } - } - - // Log error - if (!success) { - std::string emsg = "Could not find "; - emsg += cmQtAutoGen::Quoted(searchFile); - emsg += " in\n"; - for (std::string const& testFile : testFiles) { - emsg += " "; - emsg += cmQtAutoGen::Quoted(testFile); - emsg += "\n"; - } - this->LogFileError(cmQtAutoGen::UIC, sourceFile, emsg); - } - - return success; + std::lock_guard<std::mutex> mocLock(JobsMutex_); + MocAutoFileUpdated_ = true; } -bool cmQtAutoGeneratorMocUic::UicGenerateAll() +void cmQtAutoGeneratorMocUic::MocGenerateCompilation() { - if (!this->UicEnabled()) { - return true; - } - - // Look for name collisions in included uic files - { - bool collision = false; - std::map<std::string, std::vector<UicJob const*>> collisions; - for (auto const& job : this->UicJobs) { - auto& list = collisions[job->IncludeString]; - if (!list.empty()) { - collision = true; - } - list.push_back(job.get()); - } - if (collision) { - std::string emsg = - "Included uic files with the same name will be " - "generated from different sources.\n" - "Consider 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" - "Include conflicts\n" - "-----------------\n"; - const auto& colls = collisions; - for (auto const& coll : colls) { - if (coll.second.size() > 1) { - emsg += cmQtAutoGen::Quoted(coll.first); - emsg += " included in\n"; - for (const UicJob* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->Includer); - emsg += "\n"; - } - emsg += "would be generated from\n"; - for (const UicJob* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->SourceFile); - emsg += "\n"; - } + std::lock_guard<std::mutex> mocLock(JobsMutex_); + if (!JobThreadsAbort_ && Moc().Enabled) { + // 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 + for (std::string const& mocfile : MocAutoFiles_) { + content += "#include \""; + content += mocfile; + content += "\"\n"; } } - this->LogError(cmQtAutoGen::UIC, emsg); - return false; - } - } - - // Generate ui header files - for (const auto& item : this->UicJobs) { - if (!this->UicGenerateFile(*item)) { - return false; - } - } - - return true; -} - -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::UicGenerateFile(const UicJob& uicJob) -{ - bool success = true; - - std::string const uicFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, uicJob.BuildFileRel); - - bool generate = false; - std::string generateReason; - if (!generate && !cmSystemTools::FileExists(uicFileAbs.c_str())) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " from its source file "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - generateReason += " because it doesn't exist"; - } - generate = true; - } - if (!generate && this->UicSettingsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - generateReason += " because the UIC settings changed"; - } - generate = true; - } - if (!generate) { - std::string error; - if (FileIsOlderThan(uicFileAbs, uicJob.SourceFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " because it's older than its source file "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - } - generate = true; - } else { - if (!error.empty()) { - this->LogError(cmQtAutoGen::UIC, error); - success = false; - } - } - } - if (generate) { - // Log - if (this->GetVerbose()) { - this->LogBold("Generating UIC header " + uicJob.BuildFileRel); - this->LogInfo(cmQtAutoGen::UIC, generateReason); - } - // Make sure the parent directory exists - if (this->MakeParentDirectory(cmQtAutoGen::UIC, uicFileAbs)) { - // Compose uic command - std::vector<std::string> cmd; - cmd.push_back(this->UicExecutable); - { - std::vector<std::string> allOpts = this->UicTargetOptions; - auto optionIt = this->UicOptions.find(uicJob.SourceFile); - if (optionIt != this->UicOptions.end()) { - cmQtAutoGen::UicMergeOptions(allOpts, optionIt->second, - (this->QtVersionMajor == 5)); + std::string const& compRel = Moc().CompFileRel; + std::string const& compAbs = Moc().CompFileAbs; + if (FileSys().FileDiffers(compAbs, content)) { + // Actually write mocs compilation file + if (Log().Verbose()) { + Log().Info(GeneratorT::MOC, "Generating MOC compilation " + compRel); } - cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); - } - cmd.push_back("-o"); - cmd.push_back(uicFileAbs); - cmd.push_back(uicJob.SourceFile); - - std::string output; - if (this->RunCommand(cmd, output)) { - // Success - } else { - // Command failed - { - std::string emsg = "uic failed for\n "; - emsg += cmQtAutoGen::Quoted(uicJob.SourceFile); - emsg += "\nincluded by\n "; - emsg += cmQtAutoGen::Quoted(uicJob.Includer); - this->LogCommandError(cmQtAutoGen::UIC, emsg, cmd, output); + if (!FileSys().FileWrite(GeneratorT::MOC, compAbs, content)) { + Log().ErrorFile(GeneratorT::MOC, compAbs, + "mocs compilation file writing failed"); + RegisterJobError(); + return; } - cmSystemTools::RemoveFile(uicFileAbs); - success = false; + } else if (MocAutoFileUpdated_) { + // Only touch mocs compilation file + if (Log().Verbose()) { + Log().Info(GeneratorT::MOC, "Touching mocs compilation " + compRel); + } + FileSys().Touch(compAbs); } - } else { - // Parent directory creation failed - success = false; } } - return success; -} - -/** - * @brief Tries to find the header file to the given file base path by - * appending different header extensions - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::FindHeader(std::string& header, - std::string const& testBasePath) const -{ - for (std::string const& ext : this->HeaderExtensions) { - std::string testFilePath(testBasePath); - testFilePath.push_back('.'); - testFilePath += ext; - if (cmSystemTools::FileExists(testFilePath.c_str())) { - header = testFilePath; - return true; - } - } - return false; } |