| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmQtAutoGeneratorMocUic.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <functional> |
| #include <list> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| #include <utility> |
| |
| #include "cmAlgorithms.h" |
| #include "cmCryptoHash.h" |
| #include "cmMakefile.h" |
| #include "cmQtAutoGen.h" |
| #include "cmSystemTools.h" |
| #include "cmake.h" |
| |
| #if defined(__APPLE__) |
| # include <unistd.h> |
| #endif |
| |
| // -- Class methods |
| |
| std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( |
| std::string const& relativePath) const |
| { |
| return FileSys->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<std::string>& depends) const |
| { |
| if (!DependFilters.empty() && !content.empty()) { |
| for (KeyExpT const& filter : DependFilters) { |
| // Run a simple find string check |
| if (content.find(filter.Key) != std::string::npos) { |
| // Run the expensive regular expression check loop |
| const char* contentChars = content.c_str(); |
| cmsys::RegularExpressionMatch match; |
| while (filter.Exp.find(contentChars, match)) { |
| { |
| std::string dep = match.match(1); |
| if (!dep.empty()) { |
| depends.emplace(std::move(dep)); |
| } |
| } |
| contentChars += match.end(); |
| } |
| } |
| } |
| } |
| } |
| |
| bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped( |
| std::string const& fileName) const |
| { |
| return (!Enabled || (SkipList.find(fileName) != SkipList.end())); |
| } |
| |
| void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk) |
| { |
| if (AutoMoc && Header) { |
| // Don't parse header for moc if the file is included by a source already |
| if (wrk.Gen().ParallelMocIncluded(FileName)) { |
| AutoMoc = false; |
| } |
| } |
| |
| if (AutoMoc || AutoUic) { |
| std::string error; |
| MetaT meta; |
| if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) { |
| if (!meta.Content.empty()) { |
| meta.FileDir = wrk.FileSys().SubDirPrefix(FileName); |
| meta.FileBase = |
| wrk.FileSys().GetFilenameWithoutLastExtension(FileName); |
| |
| bool success = true; |
| if (AutoMoc) { |
| if (Header) { |
| success = ParseMocHeader(wrk, meta); |
| } else { |
| success = ParseMocSource(wrk, meta); |
| } |
| } |
| if (AutoUic && success) { |
| ParseUic(wrk, meta); |
| } |
| } else { |
| wrk.LogFileWarning(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(2); |
| std::string incDir(wrk.FileSys().SubDirPrefix(incString)); |
| std::string incBase = |
| wrk.FileSys().GetFilenameWithoutLastExtension(incString); |
| if (cmHasLiteralPrefix(incBase, "moc_")) { |
| // moc_<BASE>.cxx |
| // Remove the moc_ part from the base name |
| mocIncsUsc.emplace_back(MocInclude{ |
| std::move(incString), std::move(incDir), incBase.substr(4) }); |
| } else { |
| // <BASE>.moc |
| mocIncsDot.emplace_back(MocInclude{ |
| std::move(incString), std::move(incDir), std::move(incBase) }); |
| } |
| // Forward content pointer |
| contentChars += match.end(); |
| } |
| } |
| } |
| |
| // Check if there is anything to do |
| if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) { |
| return true; |
| } |
| |
| bool ownDotMocIncluded = false; |
| bool ownMocUscIncluded = false; |
| std::deque<JobPre> jobs; |
| |
| // Process moc_<BASE>.cxx includes |
| for (const MocInclude& mocInc : mocIncsUsc) { |
| std::string const header = |
| MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base); |
| if (!header.empty()) { |
| // Check if header is skipped |
| if (wrk.Moc().skipped(header)) { |
| continue; |
| } |
| // Register moc job |
| const bool ownMoc = (mocInc.Base == meta.FileBase); |
| jobs.emplace_back(JobPre{ ownMoc, true, header, mocInc.Inc }); |
| // Store meta information for relaxed mode |
| if (ownMoc) { |
| ownMocUscIncluded = true; |
| } |
| } else { |
| { |
| std::string emsg = "The file includes the moc file "; |
| emsg += Quoted(mocInc.Inc); |
| emsg += ", but the header "; |
| emsg += Quoted(MocStringHeaders(wrk, mocInc.Base)); |
| emsg += " could not be found."; |
| wrk.LogFileError(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 dependencies from this source |
| static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content); |
| } |
| if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk, |
| MetaT const& meta) |
| { |
| bool success = true; |
| std::string const macroName = wrk.Moc().FindMacro(meta.Content); |
| if (!macroName.empty()) { |
| JobHandleT jobHandle( |
| new JobMocT(std::string(FileName), std::string(), std::string())); |
| // Read dependencies from this source |
| static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content); |
| success = wrk.Gen().ParallelJobPushMoc(jobHandle); |
| } |
| return success; |
| } |
| |
| std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders( |
| WorkerT& wrk, std::string const& fileBase) const |
| { |
| std::string res = fileBase; |
| res += ".{"; |
| res += cmJoin(wrk.Base().HeaderExtensions, ","); |
| res += "}"; |
| return res; |
| } |
| |
| std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader( |
| WorkerT& wrk, std::string const& includerDir, std::string const& includeBase) |
| { |
| std::string header; |
| // Search in vicinity of the source |
| if (!wrk.Base().FindHeader(header, includerDir + includeBase)) { |
| // Search in include directories |
| for (std::string const& path : wrk.Moc().IncludePaths) { |
| std::string fullPath = path; |
| fullPath.push_back('/'); |
| fullPath += includeBase; |
| if (wrk.Base().FindHeader(header, fullPath)) { |
| break; |
| } |
| } |
| } |
| // Sanitize |
| if (!header.empty()) { |
| header = wrk.FileSys().GetRealPath(header); |
| } |
| return header; |
| } |
| |
| bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk, |
| MetaT const& meta) |
| { |
| bool success = true; |
| if (meta.Content.find("ui_") != std::string::npos) { |
| const char* contentChars = meta.Content.c_str(); |
| cmsys::RegularExpressionMatch match; |
| while (wrk.Uic().RegExpInclude.find(contentChars, match)) { |
| if (!ParseUicInclude(wrk, meta, match.match(2))) { |
| success = false; |
| break; |
| } |
| contentChars += match.end(); |
| } |
| } |
| return success; |
| } |
| |
| bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude( |
| WorkerT& wrk, MetaT const& meta, std::string&& includeString) |
| { |
| bool success = false; |
| std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString); |
| if (!uiInputFile.empty()) { |
| if (!wrk.Uic().skipped(uiInputFile)) { |
| JobHandleT jobHandle(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<std::string> testFiles; |
| { |
| std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString); |
| |
| std::string searchFileFull; |
| if (!searchPath.empty()) { |
| searchFileFull = searchPath; |
| searchFileFull += searchFile; |
| } |
| // Vicinity of the source |
| { |
| std::string const sourcePath = meta.FileDir; |
| testFiles.push_back(sourcePath + searchFile); |
| if (!searchPath.empty()) { |
| testFiles.push_back(sourcePath + searchFileFull); |
| } |
| } |
| // AUTOUIC search paths |
| if (!wrk.Uic().SearchPaths.empty()) { |
| for (std::string const& sPath : wrk.Uic().SearchPaths) { |
| testFiles.push_back((sPath + "/").append(searchFile)); |
| } |
| if (!searchPath.empty()) { |
| for (std::string const& sPath : wrk.Uic().SearchPaths) { |
| testFiles.push_back((sPath + "/").append(searchFileFull)); |
| } |
| } |
| } |
| } |
| |
| // Search for the .ui file! |
| for (std::string const& testFile : testFiles) { |
| if (wrk.FileSys().FileExists(testFile)) { |
| res = wrk.FileSys().GetRealPath(testFile); |
| break; |
| } |
| } |
| |
| // Log error |
| if (res.empty()) { |
| std::string emsg = "Could not find "; |
| emsg += Quoted(searchFile); |
| emsg += " in\n"; |
| for (std::string const& testFile : testFiles) { |
| emsg += " "; |
| emsg += Quoted(testFile); |
| emsg += "\n"; |
| } |
| wrk.LogFileError(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().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<std::string> cmd; |
| cmd.push_back(wrk.Moc().Executable); |
| // Add options |
| cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(), |
| wrk.Moc().AllOptions.end()); |
| // Add predefs include |
| if (!wrk.Moc().PredefsFileAbs.empty()) { |
| cmd.emplace_back("--include"); |
| cmd.push_back(wrk.Moc().PredefsFileAbs); |
| } |
| cmd.emplace_back("-o"); |
| cmd.push_back(BuildFile); |
| cmd.push_back(SourceFile); |
| |
| // Execute moc command |
| ProcessResultT result; |
| if (wrk.RunProcess(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<std::string> cmd; |
| cmd.push_back(wrk.Uic().Executable); |
| { |
| std::vector<std::string> allOpts = wrk.Uic().TargetOptions; |
| auto optionIt = wrk.Uic().Options.find(SourceFile); |
| if (optionIt != wrk.Uic().Options.end()) { |
| UicMergeOptions(allOpts, optionIt->second, |
| (wrk.Base().QtVersionMajor == 5)); |
| } |
| cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); |
| } |
| cmd.emplace_back("-o"); |
| cmd.push_back(BuildFile); |
| cmd.push_back(SourceFile); |
| |
| ProcessResultT result; |
| if (wrk.RunProcess(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<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, [&wrk] { wrk.UVProcessFinished(); }); |
| } |
| } |
| } |
| |
| 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() |
| : Base_(&FileSys()) |
| , Moc_(&FileSys()) |
| { |
| // Precompile regular expressions |
| Moc_.RegExpInclude.compile( |
| "(^|\n)[ \t]*#[ \t]*include[ \t]+" |
| "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); |
| Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" |
| "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); |
| |
| // Initialize libuv asynchronous iteration request |
| UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this); |
| } |
| |
| cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() = default; |
| |
| bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) |
| { |
| // -- Meta |
| Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); |
| |
| // Utility lambdas |
| auto InfoGet = [makefile](const char* key) { |
| return makefile->GetSafeDefinition(key); |
| }; |
| auto InfoGetBool = [makefile](const char* key) { |
| return makefile->IsOn(key); |
| }; |
| auto InfoGetList = [makefile](const char* key) -> std::vector<std::string> { |
| std::vector<std::string> list; |
| cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list); |
| return list; |
| }; |
| auto InfoGetLists = |
| [makefile](const char* key) -> std::vector<std::vector<std::string>> { |
| std::vector<std::vector<std::string>> lists; |
| { |
| std::string const value = makefile->GetSafeDefinition(key); |
| std::string::size_type pos = 0; |
| while (pos < value.size()) { |
| std::string::size_type next = value.find(ListSep, pos); |
| std::string::size_type length = |
| (next != std::string::npos) ? next - pos : value.size() - pos; |
| // Remove enclosing braces |
| if (length >= 2) { |
| std::string::const_iterator itBeg = value.begin() + (pos + 1); |
| std::string::const_iterator itEnd = itBeg + (length - 2); |
| { |
| std::string subValue(itBeg, itEnd); |
| std::vector<std::string> list; |
| cmSystemTools::ExpandListArgument(subValue, list); |
| lists.push_back(std::move(list)); |
| } |
| } |
| pos += length; |
| pos += ListSep.size(); |
| } |
| } |
| return lists; |
| }; |
| auto InfoGetConfig = [makefile, this](const char* key) -> std::string { |
| const char* valueConf = nullptr; |
| { |
| std::string keyConf = key; |
| keyConf += '_'; |
| keyConf += InfoConfig(); |
| valueConf = makefile->GetDefinition(keyConf); |
| } |
| if (valueConf == nullptr) { |
| return makefile->GetSafeDefinition(key); |
| } |
| return std::string(valueConf); |
| }; |
| auto InfoGetConfigList = |
| [&InfoGetConfig](const char* key) -> std::vector<std::string> { |
| std::vector<std::string> list; |
| cmSystemTools::ExpandListArgument(InfoGetConfig(key), list); |
| return list; |
| }; |
| |
| // -- Read info file |
| if (!makefile->ReadListFile(InfoFile())) { |
| Log().ErrorFile(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<unsigned long>(num, 1); |
| num = std::min<unsigned long>(num, ParallelMax); |
| Base_.NumThreads = static_cast<unsigned int>(num); |
| } |
| } |
| |
| // - Files and directories |
| Base_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); |
| Base_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); |
| Base_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); |
| Base_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); |
| Base_.IncludeProjectDirsBefore = |
| InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); |
| Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); |
| if (Base_.AutogenBuildDir.empty()) { |
| Log().ErrorFile(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<unsigned int>(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<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; |
| } |
| } |
| 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<std::string> stringHash; |
| std::set<std::size_t> 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<std::string> 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<std::string, 2> 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<std::string> includes; |
| includes.insert(includes.end(), Moc().IncludePaths.begin(), |
| Moc().IncludePaths.end()); |
| Moc_.IncludePaths.clear(); |
| Moc_.IncludePaths.reserve(includes.size()); |
| // Append project directories only |
| { |
| std::array<std::string const*, 2> const movePaths = { |
| { &Base().ProjectBinaryDir, &Base().ProjectSourceDir } |
| }; |
| for (std::string const* ppath : movePaths) { |
| std::list<std::string>::iterator it = includes.begin(); |
| while (it != includes.end()) { |
| std::string const& path = *it; |
| if (cmSystemTools::StringStartsWith(path, ppath->c_str())) { |
| Moc_.IncludePaths.push_back(path); |
| it = includes.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| } |
| // Append remaining directories |
| Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(), |
| includes.end()); |
| } |
| // Compose moc includes list |
| { |
| std::set<std::string> frameworkPaths; |
| for (std::string const& path : Moc().IncludePaths) { |
| Moc_.Includes.push_back("-I" + path); |
| // Extract framework path |
| if (cmHasLiteralSuffix(path, ".framework/Headers")) { |
| // Go up twice to get to the framework root |
| std::vector<std::string> pathComponents; |
| FileSys().SplitPath(path, pathComponents); |
| std::string frameworkPath = FileSys().JoinPath( |
| pathComponents.begin(), pathComponents.end() - 2); |
| frameworkPaths.insert(frameworkPath); |
| } |
| } |
| // Append framework includes |
| for (std::string const& path : frameworkPaths) { |
| Moc_.Includes.emplace_back("-F"); |
| Moc_.Includes.push_back(path); |
| } |
| } |
| // Setup single list with all options |
| { |
| // Add includes |
| Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(), |
| Moc().Includes.end()); |
| // Add definitions |
| for (std::string const& def : Moc().Definitions) { |
| Moc_.AllOptions.push_back("-D" + def); |
| } |
| // Add options |
| Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(), |
| Moc().Options.end()); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool cmQtAutoGeneratorMocUic::Process() |
| { |
| // Run libuv event loop |
| UVRequest().send(); |
| if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) { |
| if (JobError_) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle) |
| { |
| reinterpret_cast<cmQtAutoGeneratorMocUic*>(handle->data)->PollStage(); |
| } |
| |
| void cmQtAutoGeneratorMocUic::PollStage() |
| { |
| switch (Stage_) { |
| case StageT::SETTINGS_READ: |
| SettingsFileRead(); |
| SetStage(StageT::CREATE_DIRECTORIES); |
| break; |
| case StageT::CREATE_DIRECTORIES: |
| CreateDirectories(); |
| SetStage(StageT::PARSE_SOURCES); |
| break; |
| case StageT::PARSE_SOURCES: |
| if (ThreadsStartJobs(JobQueues_.Sources)) { |
| SetStage(StageT::PARSE_HEADERS); |
| } |
| break; |
| case StageT::PARSE_HEADERS: |
| if (ThreadsStartJobs(JobQueues_.Headers)) { |
| SetStage(StageT::MOC_PREDEFS); |
| } |
| break; |
| case StageT::MOC_PREDEFS: |
| if (ThreadsStartJobs(JobQueues_.MocPredefs)) { |
| SetStage(StageT::MOC_PROCESS); |
| } |
| break; |
| case StageT::MOC_PROCESS: |
| if (ThreadsStartJobs(JobQueues_.Moc)) { |
| SetStage(StageT::MOCS_COMPILATION); |
| } |
| break; |
| case StageT::MOCS_COMPILATION: |
| if (ThreadsJobsDone()) { |
| MocGenerateCompilation(); |
| SetStage(StageT::UIC_PROCESS); |
| } |
| break; |
| case StageT::UIC_PROCESS: |
| if (ThreadsStartJobs(JobQueues_.Uic)) { |
| SetStage(StageT::SETTINGS_WRITE); |
| } |
| break; |
| case StageT::SETTINGS_WRITE: |
| SettingsFileWrite(); |
| SetStage(StageT::FINISH); |
| break; |
| case StageT::FINISH: |
| if (ThreadsJobsDone()) { |
| // Clear all libuv handles |
| ThreadsStop(); |
| UVRequest().reset(); |
| // Set highest END stage manually |
| Stage_ = StageT::END; |
| } |
| break; |
| case StageT::END: |
| break; |
| } |
| } |
| |
| void cmQtAutoGeneratorMocUic::SetStage(StageT stage) |
| { |
| if (JobError_) { |
| stage = StageT::FINISH; |
| } |
| // Only allow to increase the stage |
| if (Stage_ < stage) { |
| Stage_ = stage; |
| UVRequest().send(); |
| } |
| } |
| |
| void cmQtAutoGeneratorMocUic::SettingsFileRead() |
| { |
| // Compose current settings strings |
| { |
| cmCryptoHash crypt(cmCryptoHash::AlgoSHA256); |
| std::string const sep(" ~~~ "); |
| if (Moc_.Enabled) { |
| std::string str; |
| str += Moc().Executable; |
| str += sep; |
| str += cmJoin(Moc().AllOptions, ";"); |
| str += sep; |
| str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE"; |
| str += sep; |
| str += cmJoin(Moc().PredefsCmd, ";"); |
| str += sep; |
| SettingsStringMoc_ = crypt.HashString(str); |
| } |
| if (Uic().Enabled) { |
| std::string str; |
| str += Uic().Executable; |
| str += sep; |
| str += cmJoin(Uic().TargetOptions, ";"); |
| for (const auto& item : Uic().Options) { |
| str += sep; |
| str += item.first; |
| str += sep; |
| str += cmJoin(item.second, ";"); |
| } |
| str += sep; |
| SettingsStringUic_ = crypt.HashString(str); |
| } |
| } |
| |
| // Read old settings and compare |
| { |
| std::string content; |
| if (FileSys().FileRead(content, SettingsFile_)) { |
| if (Moc().Enabled) { |
| if (SettingsStringMoc_ != SettingsFind(content, "moc")) { |
| Moc_.SettingsChanged = true; |
| } |
| } |
| if (Uic().Enabled) { |
| if (SettingsStringUic_ != SettingsFind(content, "uic")) { |
| Uic_.SettingsChanged = true; |
| } |
| } |
| // In case any setting changed remove the old settings file. |
| // This triggers a full rebuild on the next run if the current |
| // build is aborted before writing the current settings in the end. |
| if (Moc().SettingsChanged || Uic().SettingsChanged) { |
| FileSys().FileRemove(SettingsFile_); |
| } |
| } else { |
| // Settings file read failed |
| if (Moc().Enabled) { |
| Moc_.SettingsChanged = true; |
| } |
| if (Uic().Enabled) { |
| Uic_.SettingsChanged = true; |
| } |
| } |
| } |
| } |
| |
| void cmQtAutoGeneratorMocUic::SettingsFileWrite() |
| { |
| std::lock_guard<std::mutex> jobsLock(JobsMutex_); |
| // Only write if any setting changed |
| if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) { |
| if (Log().Verbose()) { |
| Log().Info(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<std::mutex> jobsLock(JobsMutex_); |
| // Check if there are still unfinished jobs from the previous queue |
| if (JobsRemain_ == 0) { |
| if (!JobThreadsAbort_) { |
| JobQueue_.swap(queue); |
| JobsRemain_ = queueSize; |
| } else { |
| // Abort requested |
| queue.clear(); |
| queueSize = 0; |
| } |
| done = true; |
| } |
| } |
| |
| if (done && (queueSize != 0)) { |
| // Start new threads on demand |
| if (Workers_.empty()) { |
| Workers_.resize(Base().NumThreads); |
| for (auto& item : Workers_) { |
| item = cm::make_unique<WorkerT>(this, UVLoop()); |
| } |
| } else { |
| // Notify threads |
| if (queueSize == 1) { |
| JobsConditionRead_.notify_one(); |
| } else { |
| JobsConditionRead_.notify_all(); |
| } |
| } |
| } |
| |
| return done; |
| } |
| |
| void cmQtAutoGeneratorMocUic::ThreadsStop() |
| { |
| if (!Workers_.empty()) { |
| // Clear all jobs |
| { |
| std::lock_guard<std::mutex> jobsLock(JobsMutex_); |
| JobThreadsAbort_ = true; |
| JobsRemain_ -= JobQueue_.size(); |
| JobQueue_.clear(); |
| |
| JobQueues_.Sources.clear(); |
| JobQueues_.Headers.clear(); |
| JobQueues_.MocPredefs.clear(); |
| JobQueues_.Moc.clear(); |
| JobQueues_.Uic.clear(); |
| } |
| // Wake threads |
| JobsConditionRead_.notify_all(); |
| // Join and clear threads |
| Workers_.clear(); |
| } |
| } |
| |
| bool cmQtAutoGeneratorMocUic::ThreadsJobsDone() |
| { |
| std::lock_guard<std::mutex> jobsLock(JobsMutex_); |
| return (JobsRemain_ == 0); |
| } |
| |
| void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle) |
| { |
| bool const jobProcessed(jobHandle); |
| if (jobProcessed) { |
| jobHandle.reset(nullptr); |
| } |
| { |
| std::unique_lock<std::mutex> jobsLock(JobsMutex_); |
| // Reduce the remaining job count and notify the libuv loop |
| // when all jobs are done |
| if (jobProcessed) { |
| --JobsRemain_; |
| if (JobsRemain_ == 0) { |
| UVRequest().send(); |
| } |
| } |
| // Wait for new jobs |
| while (!JobThreadsAbort_ && JobQueue_.empty()) { |
| JobsConditionRead_.wait(jobsLock); |
| } |
| // Try to pick up a new job handle |
| if (!JobThreadsAbort_ && !JobQueue_.empty()) { |
| jobHandle = std::move(JobQueue_.front()); |
| JobQueue_.pop_front(); |
| } |
| } |
| } |
| |
| void cmQtAutoGeneratorMocUic::ParallelRegisterJobError() |
| { |
| std::lock_guard<std::mutex> jobsLock(JobsMutex_); |
| RegisterJobError(); |
| } |
| |
| // Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be |
| // locked |
| void cmQtAutoGeneratorMocUic::RegisterJobError() |
| { |
| JobError_ = true; |
| if (!JobThreadsAbort_) { |
| JobThreadsAbort_ = true; |
| // Clear remaining jobs |
| if (JobsRemain_ != 0) { |
| JobsRemain_ -= JobQueue_.size(); |
| JobQueue_.clear(); |
| } |
| } |
| } |
| |
| bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle) |
| { |
| std::lock_guard<std::mutex> jobsLock(JobsMutex_); |
| if (!JobThreadsAbort_) { |
| bool pushJobHandle = true; |
| // Do additional tests if this is an included moc job |
| const JobMocT& mocJob(static_cast<JobMocT&>(*jobHandle)); |
| if (!mocJob.IncludeString.empty()) { |
| // Register included moc file and look for collisions |
| MocIncludedFiles_.emplace(mocJob.SourceFile); |
| if (!MocIncludedStrings_.emplace(mocJob.IncludeString).second) { |
| // Another source file includes the same moc file! |
| for (const JobHandleT& otherHandle : JobQueues_.Moc) { |
| const JobMocT& otherJob(static_cast<JobMocT&>(*otherHandle)); |
| if (otherJob.IncludeString == mocJob.IncludeString) { |
| // Check if the same moc file would be generated from different |
| // source files which is an error. |
| if (otherJob.SourceFile != mocJob.SourceFile) { |
| // Include string collision |
| std::string error = "The two source files\n "; |
| error += Quoted(mocJob.IncluderFile); |
| error += " and\n "; |
| error += Quoted(otherJob.IncluderFile); |
| error += "\ncontain the same moc include string "; |
| error += Quoted(mocJob.IncludeString); |
| error += "\nbut the moc file would be generated from different " |
| "source files\n "; |
| error += Quoted(mocJob.SourceFile); |
| error += " and\n "; |
| error += Quoted(otherJob.SourceFile); |
| error += ".\nConsider to\n" |
| "- not include the \"moc_<NAME>.cpp\" file\n" |
| "- add a directory prefix to a \"<NAME>.moc\" include " |
| "(e.g \"sub/<NAME>.moc\")\n" |
| "- rename the source file(s)\n"; |
| Log().Error(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<std::mutex> jobsLock(JobsMutex_); |
| if (!JobThreadsAbort_) { |
| bool pushJobHandle = true; |
| // Look for include collisions. |
| const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle)); |
| for (const JobHandleT& otherHandle : JobQueues_.Uic) { |
| const JobUicT& otherJob(static_cast<JobUicT&>(*otherHandle)); |
| if (otherJob.IncludeString == uicJob.IncludeString) { |
| // Check if the same uic file would be generated from different |
| // source files which would be an error. |
| if (otherJob.SourceFile != uicJob.SourceFile) { |
| // Include string collision |
| std::string error = "The two source files\n "; |
| error += Quoted(uicJob.IncluderFile); |
| error += " and\n "; |
| error += Quoted(otherJob.IncluderFile); |
| error += "\ncontain the same uic include string "; |
| error += Quoted(uicJob.IncludeString); |
| error += "\nbut the uic file would be generated from different " |
| "source files\n "; |
| error += Quoted(uicJob.SourceFile); |
| error += " and\n "; |
| error += Quoted(otherJob.SourceFile); |
| error += |
| ".\nConsider to\n" |
| "- add a directory prefix to a \"ui_<NAME>.h\" include " |
| "(e.g \"sub/ui_<NAME>.h\")\n" |
| "- rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" " |
| "include(s)\n"; |
| Log().Error(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<std::mutex> mocLock(JobsMutex_); |
| return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); |
| } |
| |
| std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( |
| std::string const& baseName) |
| { |
| std::string res; |
| { |
| std::lock_guard<std::mutex> mocLock(JobsMutex_); |
| res = baseName; |
| res += ".cpp"; |
| if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) { |
| MocAutoFiles_.emplace(res); |
| } else { |
| // Append number suffix to the file name |
| for (unsigned int ii = 2; ii != 1024; ++ii) { |
| res = baseName; |
| res += '_'; |
| res += std::to_string(ii); |
| res += ".cpp"; |
| if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) { |
| MocAutoFiles_.emplace(res); |
| break; |
| } |
| } |
| } |
| } |
| return res; |
| } |
| |
| void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated() |
| { |
| std::lock_guard<std::mutex> mocLock(JobsMutex_); |
| MocAutoFileUpdated_ = true; |
| } |
| |
| void cmQtAutoGeneratorMocUic::MocGenerateCompilation() |
| { |
| std::lock_guard<std::mutex> mocLock(JobsMutex_); |
| if (!JobError_ && Moc().Enabled) { |
| // Write mocs compilation build file |
| { |
| // Compose mocs compilation file content |
| std::string content = |
| "// This file is autogenerated. Changes will be overwritten.\n"; |
| if (MocAutoFiles_.empty()) { |
| // Placeholder content |
| content += "// No files found that require moc or the moc files are " |
| "included\n"; |
| content += "enum some_compilers { need_more_than_nothing };\n"; |
| } else { |
| // Valid content |
| char const sbeg = Base().MultiConfig ? '<' : '"'; |
| char const send = Base().MultiConfig ? '>' : '"'; |
| for (std::string const& mocfile : MocAutoFiles_) { |
| content += "#include "; |
| content += sbeg; |
| content += mocfile; |
| content += send; |
| content += '\n'; |
| } |
| } |
| |
| std::string const& compAbs = Moc().CompFileAbs; |
| if (FileSys().FileDiffers(compAbs, content)) { |
| // Actually write mocs compilation file |
| if (Log().Verbose()) { |
| Log().Info(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) { |
| } |
| } |
| } |