/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGeneratorMocUic.h" #include #include #include #include #include #include #include #include #include #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmMakefile.h" #include "cmQtAutoGen.h" #include "cmSystemTools.h" #include "cmake.h" #if defined(__APPLE__) # include #endif // -- Class methods std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( std::string const& relativePath) const { return FileSys->CollapseCombinedPath(AutogenBuildDir, relativePath); } /** * @brief Tries to find the header file to the given file base path by * appending different header extensions * @return True on success */ bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader( std::string& header, std::string const& testBasePath) const { for (std::string const& ext : HeaderExtensions) { std::string testFilePath(testBasePath); testFilePath.push_back('.'); testFilePath += ext; if (FileSys->FileExists(testFilePath)) { header = testFilePath; return true; } } return false; } bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped( std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } /** * @brief Returns the first relevant Qt macro name found in the given C++ code * @return The name of the Qt macro or an empty string */ std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro( std::string const& content) const { for (KeyExpT const& filter : MacroFilters) { // Run a simple find string operation before the expensive // regular expression check if (content.find(filter.Key) != std::string::npos) { cmsys::RegularExpressionMatch match; if (filter.Exp.find(content.c_str(), match)) { // Return macro name on demand return filter.Key; } } } return std::string(); } std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const { std::string res; const auto itB = MacroFilters.cbegin(); const auto itE = MacroFilters.cend(); const auto itL = itE - 1; auto itC = itB; for (; itC != itE; ++itC) { // Separator if (itC != itB) { if (itC != itL) { res += ", "; } else { res += " or "; } } // Key res += itC->Key; } return res; } std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile( std::string const& sourcePath, std::string const& includeString) const { // Search in vicinity of the source { std::string testPath = sourcePath; testPath += includeString; if (FileSys->FileExists(testPath)) { return FileSys->GetRealPath(testPath); } } // Search in include directories for (std::string const& path : IncludePaths) { std::string fullPath = path; fullPath.push_back('/'); fullPath += includeString; if (FileSys->FileExists(fullPath)) { return FileSys->GetRealPath(fullPath); } } // Return empty string return std::string(); } void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies( std::string const& content, std::set& depends) const { if (!DependFilters.empty() && !content.empty()) { for (KeyExpT const& filter : DependFilters) { // Run a simple find string check if (content.find(filter.Key) != std::string::npos) { // Run the expensive regular expression check loop const char* contentChars = content.c_str(); cmsys::RegularExpressionMatch match; while (filter.Exp.find(contentChars, match)) { { std::string dep = match.match(1); if (!dep.empty()) { depends.emplace(std::move(dep)); } } contentChars += match.end(); } } } } } bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped( std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk) { if (AutoMoc && Header) { // Don't parse header for moc if the file is included by a source already if (wrk.Gen().ParallelMocIncluded(FileName)) { AutoMoc = false; } } if (AutoMoc || AutoUic) { std::string error; MetaT meta; if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) { if (!meta.Content.empty()) { meta.FileDir = wrk.FileSys().SubDirPrefix(FileName); meta.FileBase = wrk.FileSys().GetFilenameWithoutLastExtension(FileName); bool success = true; if (AutoMoc) { if (Header) { success = ParseMocHeader(wrk, meta); } else { success = ParseMocSource(wrk, meta); } } if (AutoUic && success) { ParseUic(wrk, meta); } } else { wrk.LogFileWarning(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 mocIncsUsc; std::deque mocIncsDot; { if (meta.Content.find("moc") != std::string::npos) { const char* contentChars = meta.Content.c_str(); cmsys::RegularExpressionMatch match; while (wrk.Moc().RegExpInclude.find(contentChars, match)) { std::string incString = match.match(2); std::string incDir(wrk.FileSys().SubDirPrefix(incString)); std::string incBase = wrk.FileSys().GetFilenameWithoutLastExtension(incString); if (cmHasLiteralPrefix(incBase, "moc_")) { // moc_.cxx // Remove the moc_ part from the base name mocIncsUsc.emplace_back(MocInclude{ std::move(incString), std::move(incDir), incBase.substr(4) }); } else { // .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 jobs; // Process moc_.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 .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 .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 dependencies from this source static_cast(*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 dependencies from this source static_cast(*jobHandle).FindDependencies(wrk, meta.Content); success = wrk.Gen().ParallelJobPushMoc(jobHandle); } return success; } std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders( WorkerT& wrk, std::string const& fileBase) const { std::string res = fileBase; res += ".{"; res += cmJoin(wrk.Base().HeaderExtensions, ","); res += "}"; return res; } std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader( WorkerT& wrk, std::string const& includerDir, std::string const& includeBase) { std::string header; // Search in vicinity of the source if (!wrk.Base().FindHeader(header, includerDir + includeBase)) { // Search in include directories for (std::string const& path : wrk.Moc().IncludePaths) { std::string fullPath = path; fullPath.push_back('/'); fullPath += includeBase; if (wrk.Base().FindHeader(header, fullPath)) { break; } } } // Sanitize if (!header.empty()) { header = wrk.FileSys().GetRealPath(header); } return header; } bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk, MetaT const& meta) { bool success = true; if (meta.Content.find("ui_") != std::string::npos) { const char* contentChars = meta.Content.c_str(); cmsys::RegularExpressionMatch match; while (wrk.Uic().RegExpInclude.find(contentChars, match)) { if (!ParseUicInclude(wrk, meta, match.match(2))) { success = false; break; } contentChars += match.end(); } } return success; } bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude( WorkerT& wrk, MetaT const& meta, std::string&& includeString) { bool success = false; std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString); if (!uiInputFile.empty()) { if (!wrk.Uic().skipped(uiInputFile)) { JobHandleT jobHandle(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 = wrk.FileSys().GetFilenameWithoutLastExtension(includeString).substr(3); searchFile += ".ui"; // Collect search paths list std::deque testFiles; { std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString); std::string searchFileFull; if (!searchPath.empty()) { searchFileFull = searchPath; searchFileFull += searchFile; } // Vicinity of the source { std::string const sourcePath = meta.FileDir; testFiles.push_back(sourcePath + searchFile); if (!searchPath.empty()) { testFiles.push_back(sourcePath + searchFileFull); } } // AUTOUIC search paths if (!wrk.Uic().SearchPaths.empty()) { for (std::string const& sPath : wrk.Uic().SearchPaths) { testFiles.push_back((sPath + "/").append(searchFile)); } if (!searchPath.empty()) { for (std::string const& sPath : wrk.Uic().SearchPaths) { testFiles.push_back((sPath + "/").append(searchFileFull)); } } } } // Search for the .ui file! for (std::string const& testFile : testFiles) { if (wrk.FileSys().FileExists(testFile)) { res = wrk.FileSys().GetRealPath(testFile); break; } } // Log error if (res.empty()) { std::string emsg = "Could not find "; emsg += Quoted(searchFile); emsg += " in\n"; for (std::string const& testFile : testFiles) { emsg += " "; emsg += Quoted(testFile); emsg += "\n"; } wrk.LogFileError(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 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().AutogenIncludeDir; BuildFile += '/'; BuildFile += IncludeString; } else { // Relative build path std::string relPath = wrk.FileSys().GetFilePathChecksum(SourceFile); relPath += "/moc_"; relPath += wrk.FileSys().GetFilenameWithoutLastExtension(SourceFile); // Register relative file path with duplication check relPath = wrk.Gen().ParallelMocAutoRegister(relPath); // Absolute build path if (wrk.Base().MultiConfig) { BuildFile = wrk.Base().AutogenIncludeDir; BuildFile += '/'; BuildFile += relPath; } else { BuildFile = wrk.Base().AbsoluteBuildPath(relPath); } } if (UpdateRequired(wrk)) { GenerateMoc(wrk); } } bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) { bool const verbose = wrk.Gen().Log().Verbose(); // Test if the build file exists if (!wrk.FileSys().FileExists(BuildFile)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from its source file "; reason += Quoted(SourceFile); reason += " because it doesn't exist"; wrk.LogInfo(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 = wrk.FileSys().SubDirPrefix(SourceFile); for (std::string const& depFileRel : Depends) { std::string depFileAbs = wrk.Moc().FindIncludedFile(sourceDir, depFileRel); if (!depFileAbs.empty()) { if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from "; reason += Quoted(SourceFile); reason += " because it is older than it's dependency file "; reason += Quoted(depFileAbs); wrk.LogInfo(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 cmd; cmd.push_back(wrk.Moc().Executable); // Add options cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(), wrk.Moc().AllOptions.end()); // Add predefs include if (!wrk.Moc().PredefsFileAbs.empty()) { cmd.emplace_back("--include"); cmd.push_back(wrk.Moc().PredefsFileAbs); } cmd.emplace_back("-o"); cmd.push_back(BuildFile); cmd.push_back(SourceFile); // Execute moc command ProcessResultT result; if (wrk.RunProcess(GeneratorT::MOC, result, cmd)) { // Moc command success // Print moc output if (!result.StdOut.empty()) { wrk.LogInfo(GeneratorT::MOC, result.StdOut); } // Notify the generator that a not included file changed (on demand) if (IncludeString.empty()) { wrk.Gen().ParallelMocAutoUpdated(); } } else { // Moc command failed { std::string emsg = "The moc process failed to compile\n "; emsg += Quoted(SourceFile); emsg += "\ninto\n "; emsg += Quoted(BuildFile); emsg += ".\n"; emsg += result.ErrorMessage; wrk.LogCommandError(GeneratorT::MOC, emsg, cmd, result.StdOut); } wrk.FileSys().FileRemove(BuildFile); } } } void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk) { // Compute build file name BuildFile = wrk.Base().AutogenIncludeDir; BuildFile += '/'; BuildFile += IncludeString; if (UpdateRequired(wrk)) { GenerateUic(wrk); } } bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) { bool const verbose = wrk.Gen().Log().Verbose(); // Test if the build file exists if (!wrk.FileSys().FileExists(BuildFile)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from its source file "; reason += Quoted(SourceFile); reason += " because it doesn't exist"; wrk.LogInfo(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 cmd; cmd.push_back(wrk.Uic().Executable); { std::vector allOpts = wrk.Uic().TargetOptions; auto optionIt = wrk.Uic().Options.find(SourceFile); if (optionIt != wrk.Uic().Options.end()) { UicMergeOptions(allOpts, optionIt->second, (wrk.Base().QtVersionMajor == 5)); } cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); } cmd.emplace_back("-o"); cmd.push_back(BuildFile); cmd.push_back(SourceFile); ProcessResultT result; if (wrk.RunProcess(GeneratorT::UIC, result, cmd)) { // Uic command success // Print uic output if (!result.StdOut.empty()) { wrk.LogInfo(GeneratorT::UIC, result.StdOut); } } else { // Uic command failed { std::string emsg = "The uic process failed to compile\n "; emsg += Quoted(SourceFile); emsg += "\ninto\n "; emsg += Quoted(BuildFile); emsg += "\nincluded by\n "; emsg += Quoted(IncluderFile); emsg += ".\n"; emsg += result.ErrorMessage; wrk.LogCommandError(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 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 const& command) { if (command.empty()) { return false; } // Create process instance { std::lock_guard lock(ProcessMutex_); Process_ = cm::make_unique(); 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 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(handle->data); { std::lock_guard lock(wrk.ProcessMutex_); if (wrk.Process_ && !wrk.Process_->IsStarted()) { wrk.Process_->start(handle->loop, [&wrk] { wrk.UVProcessFinished(); }); } } } void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished() { { std::lock_guard lock(ProcessMutex_); if (Process_ && Process_->IsFinished()) { Process_.reset(); } } // Notify idling thread ProcessCondition_.notify_one(); } cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() : Base_(&FileSys()) , Moc_(&FileSys()) { // Precompile regular expressions Moc_.RegExpInclude.compile( "(^|\n)[ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); // Initialize libuv asynchronous iteration request UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this); } cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() = default; bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) { // -- Meta Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); // Utility lambdas auto InfoGet = [makefile](const char* key) { return makefile->GetSafeDefinition(key); }; auto InfoGetBool = [makefile](const char* key) { return makefile->IsOn(key); }; auto InfoGetList = [makefile](const char* key) -> std::vector { std::vector list; cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list); return list; }; auto InfoGetLists = [makefile](const char* key) -> std::vector> { std::vector> lists; { std::string const value = makefile->GetSafeDefinition(key); std::string::size_type pos = 0; while (pos < value.size()) { std::string::size_type next = value.find(ListSep, pos); std::string::size_type length = (next != std::string::npos) ? next - pos : value.size() - pos; // Remove enclosing braces if (length >= 2) { std::string::const_iterator itBeg = value.begin() + (pos + 1); std::string::const_iterator itEnd = itBeg + (length - 2); { std::string subValue(itBeg, itEnd); std::vector list; cmSystemTools::ExpandListArgument(subValue, list); lists.push_back(std::move(list)); } } pos += length; pos += ListSep.size(); } } return lists; }; auto InfoGetConfig = [makefile, this](const char* key) -> std::string { const char* valueConf = nullptr; { std::string keyConf = key; keyConf += '_'; keyConf += InfoConfig(); valueConf = makefile->GetDefinition(keyConf); } if (valueConf == nullptr) { return makefile->GetSafeDefinition(key); } return std::string(valueConf); }; auto InfoGetConfigList = [&InfoGetConfig](const char* key) -> std::vector { std::vector list; cmSystemTools::ExpandListArgument(InfoGetConfig(key), list); return list; }; // -- Read info file if (!makefile->ReadListFile(InfoFile())) { Log().ErrorFile(GeneratorT::GEN, InfoFile(), "File processing failed"); return false; } // -- Meta Log().RaiseVerbosity(InfoGet("AM_VERBOSITY")); Base_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG"); { unsigned long num = Base_.NumThreads; if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL").c_str(), &num)) { num = std::max(num, 1); num = std::min(num, ParallelMax); Base_.NumThreads = static_cast(num); } } // - Files and directories Base_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); Base_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); Base_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); Base_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); Base_.IncludeProjectDirsBefore = InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); if (Base_.AutogenBuildDir.empty()) { Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Autogen build directory missing"); return false; } // include directory Base_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR"); if (Base_.AutogenIncludeDir.empty()) { Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Autogen include directory missing"); return false; } // - Files SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE"); if (SettingsFile_.empty()) { Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Settings file name missing"); return false; } // - Qt environment { unsigned long qtv = Base_.QtVersionMajor; if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(), &qtv)) { Base_.QtVersionMajor = static_cast(qtv); } } // - Moc 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"); Moc_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); Moc_.Options = InfoGetList("AM_MOC_OPTIONS"); Moc_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) { Moc_.MacroFilters.emplace_back( item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]")); } { auto pushFilter = [this](std::string const& key, std::string const& exp, std::string& error) { if (!key.empty()) { if (!exp.empty()) { Moc_.DependFilters.emplace_back(); KeyExpT& filter(Moc_.DependFilters.back()); if (filter.Exp.compile(exp)) { filter.Key = key; } else { error = "Regular expression compiling failed"; } } else { error = "Regular expression is empty"; } } else { error = "Key is empty"; } if (!error.empty()) { error = ("AUTOMOC_DEPEND_FILTERS: " + error); error += "\n"; error += " Key: "; error += Quoted(key); error += "\n"; error += " Exp: "; error += Quoted(exp); error += "\n"; } }; std::string error; // Insert default filter for Q_PLUGIN_METADATA if (Base().QtVersionMajor != 4) { pushFilter("Q_PLUGIN_METADATA", "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" "[^\\)]*FILE[ \t]*\"([^\"]+)\"", error); } // Insert user defined dependency filters { std::vector flts = InfoGetList("AM_MOC_DEPEND_FILTERS"); if ((flts.size() % 2) == 0) { for (std::vector::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; } } if (!error.empty()) { Log().ErrorFile(GeneratorT::MOC, InfoFile(), error); return false; } } Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); // Install moc predefs job if (!Moc().PredefsCmd.empty()) { JobQueues_.MocPredefs.emplace_back(new JobMocPredefsT()); } } // - Uic 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() << "/" << options.size() << ")"; Log().ErrorFile(GeneratorT::UIC, InfoFile(), ost.str()); return false; } auto fitEnd = sources.cend(); auto fit = sources.begin(); auto oit = options.begin(); while (fit != fitEnd) { Uic_.Options[*fit] = std::move(*oit); ++fit; ++oit; } } } // Initialize source file jobs { std::hash stringHash; std::set uniqueHeaders; // Add header jobs for (std::string& hdr : InfoGetList("AM_HEADERS")) { 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 sources = InfoGetList("AM_SOURCES"); // Add header(s) for the source file 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 bases; bases[0] = FileSys().SubDirPrefix(src); bases[0] += FileSys().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 job JobQueues_.Sources.emplace_back( new JobParseT(std::move(src), srcMoc, srcUic)); } } } // Init derived information // ------------------------ // Init file path checksum generator FileSys().setupFilePathChecksum( Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir, Base().ProjectBinaryDir); // Moc variables if (Moc().Enabled) { // Mocs compilation file Moc_.CompFileAbs = Base().AbsoluteBuildPath("mocs_compilation.cpp"); // Moc predefs file if (!Moc_.PredefsCmd.empty()) { Moc_.PredefsFileRel = "moc_predefs"; if (Base_.MultiConfig) { Moc_.PredefsFileRel += '_'; Moc_.PredefsFileRel += InfoConfig(); } Moc_.PredefsFileRel += ".h"; Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel); } // Sort include directories on demand if (Base().IncludeProjectDirsBefore) { // Move strings to temporary list std::list includes; includes.insert(includes.end(), Moc().IncludePaths.begin(), Moc().IncludePaths.end()); Moc_.IncludePaths.clear(); Moc_.IncludePaths.reserve(includes.size()); // Append project directories only { std::array const movePaths = { { &Base().ProjectBinaryDir, &Base().ProjectSourceDir } }; for (std::string const* ppath : movePaths) { std::list::iterator it = includes.begin(); while (it != includes.end()) { std::string const& path = *it; if (cmSystemTools::StringStartsWith(path, ppath->c_str())) { Moc_.IncludePaths.push_back(path); it = includes.erase(it); } else { ++it; } } } } // Append remaining directories Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(), includes.end()); } // Compose moc includes list { std::set frameworkPaths; for (std::string const& path : Moc().IncludePaths) { Moc_.Includes.push_back("-I" + path); // Extract framework path if (cmHasLiteralSuffix(path, ".framework/Headers")) { // Go up twice to get to the framework root std::vector pathComponents; FileSys().SplitPath(path, pathComponents); std::string frameworkPath = FileSys().JoinPath( pathComponents.begin(), pathComponents.end() - 2); frameworkPaths.insert(frameworkPath); } } // Append framework includes for (std::string const& path : frameworkPaths) { Moc_.Includes.emplace_back("-F"); Moc_.Includes.push_back(path); } } // Setup single list with all options { // Add includes Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(), Moc().Includes.end()); // Add definitions for (std::string const& def : Moc().Definitions) { Moc_.AllOptions.push_back("-D" + def); } // Add options Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(), Moc().Options.end()); } } return true; } bool cmQtAutoGeneratorMocUic::Process() { // Run libuv event loop UVRequest().send(); if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) { if (JobError_) { return false; } } else { return false; } return true; } void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle) { reinterpret_cast(handle->data)->PollStage(); } void cmQtAutoGeneratorMocUic::PollStage() { switch (Stage_) { case StageT::SETTINGS_READ: SettingsFileRead(); SetStage(StageT::CREATE_DIRECTORIES); break; case StageT::CREATE_DIRECTORIES: CreateDirectories(); SetStage(StageT::PARSE_SOURCES); break; case StageT::PARSE_SOURCES: if (ThreadsStartJobs(JobQueues_.Sources)) { SetStage(StageT::PARSE_HEADERS); } break; case StageT::PARSE_HEADERS: if (ThreadsStartJobs(JobQueues_.Headers)) { SetStage(StageT::MOC_PREDEFS); } break; case StageT::MOC_PREDEFS: if (ThreadsStartJobs(JobQueues_.MocPredefs)) { SetStage(StageT::MOC_PROCESS); } break; case StageT::MOC_PROCESS: if (ThreadsStartJobs(JobQueues_.Moc)) { SetStage(StageT::MOCS_COMPILATION); } break; case StageT::MOCS_COMPILATION: if (ThreadsJobsDone()) { MocGenerateCompilation(); SetStage(StageT::UIC_PROCESS); } break; case StageT::UIC_PROCESS: if (ThreadsStartJobs(JobQueues_.Uic)) { SetStage(StageT::SETTINGS_WRITE); } break; case StageT::SETTINGS_WRITE: SettingsFileWrite(); SetStage(StageT::FINISH); break; case StageT::FINISH: if (ThreadsJobsDone()) { // Clear all libuv handles ThreadsStop(); UVRequest().reset(); // Set highest END stage manually Stage_ = StageT::END; } break; case StageT::END: break; } } void cmQtAutoGeneratorMocUic::SetStage(StageT stage) { if (JobError_) { stage = StageT::FINISH; } // Only allow to increase the stage if (Stage_ < stage) { Stage_ = stage; UVRequest().send(); } } void cmQtAutoGeneratorMocUic::SettingsFileRead() { // Compose current settings strings { cmCryptoHash crypt(cmCryptoHash::AlgoSHA256); std::string const sep(" ~~~ "); if (Moc_.Enabled) { std::string str; str += Moc().Executable; str += sep; str += cmJoin(Moc().AllOptions, ";"); str += sep; str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE"; str += sep; str += cmJoin(Moc().PredefsCmd, ";"); str += sep; SettingsStringMoc_ = crypt.HashString(str); } if (Uic().Enabled) { std::string str; str += Uic().Executable; str += sep; str += cmJoin(Uic().TargetOptions, ";"); for (const auto& item : Uic().Options) { str += sep; str += item.first; str += sep; str += cmJoin(item.second, ";"); } str += sep; SettingsStringUic_ = crypt.HashString(str); } } // Read old settings and compare { std::string content; if (FileSys().FileRead(content, SettingsFile_)) { if (Moc().Enabled) { if (SettingsStringMoc_ != SettingsFind(content, "moc")) { Moc_.SettingsChanged = true; } } if (Uic().Enabled) { if (SettingsStringUic_ != SettingsFind(content, "uic")) { Uic_.SettingsChanged = true; } } // In case any setting changed remove the old settings file. // This triggers a full rebuild on the next run if the current // build is aborted before writing the current settings in the end. if (Moc().SettingsChanged || Uic().SettingsChanged) { FileSys().FileRemove(SettingsFile_); } } else { // Settings file read failed if (Moc().Enabled) { Moc_.SettingsChanged = true; } if (Uic().Enabled) { Uic_.SettingsChanged = true; } } } } void cmQtAutoGeneratorMocUic::SettingsFileWrite() { std::lock_guard jobsLock(JobsMutex_); // Only write if any setting changed if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) { if (Log().Verbose()) { Log().Info(GeneratorT::GEN, "Writing settings file " + Quoted(SettingsFile_)); } // Compose settings file content std::string content; { auto SettingAppend = [&content](const char* key, std::string const& value) { if (!value.empty()) { content += key; content += ':'; content += value; content += '\n'; } }; SettingAppend("moc", SettingsStringMoc_); SettingAppend("uic", SettingsStringUic_); } // Write settings file if (!FileSys().FileWrite(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 FileSys().FileRemove(SettingsFile_); RegisterJobError(); } } } void cmQtAutoGeneratorMocUic::CreateDirectories() { // Create AUTOGEN include directory if (!FileSys().MakeDirectory(GeneratorT::GEN, Base().AutogenIncludeDir)) { RegisterJobError(); } } bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue) { bool done = false; std::size_t queueSize = queue.size(); // Change the active queue { std::lock_guard jobsLock(JobsMutex_); // Check if there are still unfinished jobs from the previous queue if (JobsRemain_ == 0) { if (!JobThreadsAbort_) { JobQueue_.swap(queue); JobsRemain_ = queueSize; } else { // Abort requested queue.clear(); queueSize = 0; } done = true; } } if (done && (queueSize != 0)) { // Start new threads on demand if (Workers_.empty()) { Workers_.resize(Base().NumThreads); for (auto& item : Workers_) { item = cm::make_unique(this, UVLoop()); } } else { // Notify threads if (queueSize == 1) { JobsConditionRead_.notify_one(); } else { JobsConditionRead_.notify_all(); } } } return done; } void cmQtAutoGeneratorMocUic::ThreadsStop() { if (!Workers_.empty()) { // Clear all jobs { std::lock_guard jobsLock(JobsMutex_); JobThreadsAbort_ = true; JobsRemain_ -= JobQueue_.size(); JobQueue_.clear(); JobQueues_.Sources.clear(); JobQueues_.Headers.clear(); JobQueues_.MocPredefs.clear(); JobQueues_.Moc.clear(); JobQueues_.Uic.clear(); } // Wake threads JobsConditionRead_.notify_all(); // Join and clear threads Workers_.clear(); } } bool cmQtAutoGeneratorMocUic::ThreadsJobsDone() { std::lock_guard jobsLock(JobsMutex_); return (JobsRemain_ == 0); } void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle) { bool const jobProcessed(jobHandle); if (jobProcessed) { jobHandle.reset(nullptr); } { std::unique_lock jobsLock(JobsMutex_); // Reduce the remaining job count and notify the libuv loop // when all jobs are done if (jobProcessed) { --JobsRemain_; if (JobsRemain_ == 0) { UVRequest().send(); } } // Wait for new jobs while (!JobThreadsAbort_ && JobQueue_.empty()) { JobsConditionRead_.wait(jobsLock); } // Try to pick up a new job handle if (!JobThreadsAbort_ && !JobQueue_.empty()) { jobHandle = std::move(JobQueue_.front()); JobQueue_.pop_front(); } } } void cmQtAutoGeneratorMocUic::ParallelRegisterJobError() { std::lock_guard jobsLock(JobsMutex_); RegisterJobError(); } // Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be // locked void cmQtAutoGeneratorMocUic::RegisterJobError() { JobError_ = true; if (!JobThreadsAbort_) { JobThreadsAbort_ = true; // Clear remaining jobs if (JobsRemain_ != 0) { JobsRemain_ -= JobQueue_.size(); JobQueue_.clear(); } } } bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle) { std::lock_guard jobsLock(JobsMutex_); if (!JobThreadsAbort_) { bool pushJobHandle = true; // Do additional tests if this is an included moc job const JobMocT& mocJob(static_cast(*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(*otherHandle)); if (otherJob.IncludeString == mocJob.IncludeString) { // Check if the same moc file would be generated from different // source files which is an error. if (otherJob.SourceFile != mocJob.SourceFile) { // Include string collision std::string error = "The two source files\n "; error += Quoted(mocJob.IncluderFile); error += " and\n "; error += Quoted(otherJob.IncluderFile); error += "\ncontain the same moc include string "; error += Quoted(mocJob.IncludeString); error += "\nbut the moc file would be generated from different " "source files\n "; error += Quoted(mocJob.SourceFile); error += " and\n "; error += Quoted(otherJob.SourceFile); error += ".\nConsider to\n" "- not include the \"moc_.cpp\" file\n" "- add a directory prefix to a \".moc\" include " "(e.g \"sub/.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; } } } } // Push job on demand if (pushJobHandle) { JobQueues_.Moc.emplace_back(std::move(jobHandle)); } } return !JobError_; } bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle) { std::lock_guard jobsLock(JobsMutex_); if (!JobThreadsAbort_) { bool pushJobHandle = true; // Look for include collisions. const JobUicT& uicJob(static_cast(*jobHandle)); for (const JobHandleT& otherHandle : JobQueues_.Uic) { const JobUicT& otherJob(static_cast(*otherHandle)); if (otherJob.IncludeString == uicJob.IncludeString) { // Check if the same uic file would be generated from different // source files which would be an error. if (otherJob.SourceFile != uicJob.SourceFile) { // Include string collision std::string error = "The two source files\n "; error += Quoted(uicJob.IncluderFile); error += " and\n "; error += Quoted(otherJob.IncluderFile); error += "\ncontain the same uic include string "; error += Quoted(uicJob.IncludeString); error += "\nbut the uic file would be generated from different " "source files\n "; error += Quoted(uicJob.SourceFile); error += " and\n "; error += Quoted(otherJob.SourceFile); error += ".\nConsider to\n" "- add a directory prefix to a \"ui_.h\" include " "(e.g \"sub/ui_.h\")\n" "- rename the .ui file(s) and adjust the \"ui_.h\" " "include(s)\n"; Log().Error(GeneratorT::UIC, error); RegisterJobError(); } // Do not push this job in since the uic file already // gets generated by an other job. pushJobHandle = false; break; } } if (pushJobHandle) { JobQueues_.Uic.emplace_back(std::move(jobHandle)); } } return !JobError_; } bool cmQtAutoGeneratorMocUic::ParallelMocIncluded( std::string const& sourceFile) { std::lock_guard mocLock(JobsMutex_); return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); } std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( std::string const& baseName) { std::string res; { std::lock_guard mocLock(JobsMutex_); res = baseName; res += ".cpp"; if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) { MocAutoFiles_.emplace(res); } else { // Append number suffix to the file name for (unsigned int ii = 2; ii != 1024; ++ii) { res = baseName; res += '_'; res += std::to_string(ii); res += ".cpp"; if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) { MocAutoFiles_.emplace(res); break; } } } } return res; } void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated() { std::lock_guard mocLock(JobsMutex_); MocAutoFileUpdated_ = true; } void cmQtAutoGeneratorMocUic::MocGenerateCompilation() { std::lock_guard mocLock(JobsMutex_); if (!JobError_ && Moc().Enabled) { // Write mocs compilation build file { // Compose mocs compilation file content std::string content = "// This file is autogenerated. Changes will be overwritten.\n"; if (MocAutoFiles_.empty()) { // Placeholder content content += "// No files found that require moc or the moc files are " "included\n"; content += "enum some_compilers { need_more_than_nothing };\n"; } else { // Valid content char const sbeg = Base().MultiConfig ? '<' : '"'; char const send = Base().MultiConfig ? '>' : '"'; for (std::string const& mocfile : MocAutoFiles_) { content += "#include "; content += sbeg; content += mocfile; content += send; content += '\n'; } } std::string const& compAbs = Moc().CompFileAbs; if (FileSys().FileDiffers(compAbs, content)) { // Actually write mocs compilation file if (Log().Verbose()) { Log().Info(GeneratorT::MOC, "Generating MOC compilation " + compAbs); } if (!FileSys().FileWrite(GeneratorT::MOC, compAbs, content)) { Log().ErrorFile(GeneratorT::MOC, compAbs, "mocs compilation file writing failed"); RegisterJobError(); return; } } else if (MocAutoFileUpdated_) { // Only touch mocs compilation file if (Log().Verbose()) { Log().Info(GeneratorT::MOC, "Touching mocs compilation " + compAbs); } FileSys().Touch(compAbs); } } // Write mocs compilation wrapper file if (Base().MultiConfig) { } } }