/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoMocUic.h" #include #include #include #include #include #include #include #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmGeneratedFileStream.h" #include "cmMakefile.h" #include "cmQtAutoGen.h" #include "cmSystemTools.h" #include "cmake.h" #include "cmsys/FStream.hxx" #if defined(__APPLE__) # include #endif static constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_" static constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_" cmQtAutoMocUic::IncludeKeyT::IncludeKeyT(std::string const& key, std::size_t basePrefixLength) : Key(key) , Dir(SubDirPrefix(key)) , Base(cmSystemTools::GetFilenameWithoutLastExtension(key)) { if (basePrefixLength != 0) { Base = Base.substr(basePrefixLength); } } void cmQtAutoMocUic::ParseCacheT::FileT::Clear() { Moc.Macro.clear(); Moc.Include.Underscore.clear(); Moc.Include.Dot.clear(); Moc.Depends.clear(); Uic.Include.clear(); Uic.Depends.clear(); } cmQtAutoMocUic::ParseCacheT::FileHandleT cmQtAutoMocUic::ParseCacheT::Get( std::string const& fileName) const { auto it = Map_.find(fileName); if (it != Map_.end()) { return it->second; } return FileHandleT(); } cmQtAutoMocUic::ParseCacheT::GetOrInsertT cmQtAutoMocUic::ParseCacheT::GetOrInsert(std::string const& fileName) { // Find existing entry { auto it = Map_.find(fileName); if (it != Map_.end()) { return GetOrInsertT{ it->second, false }; } } // Insert new entry return GetOrInsertT{ Map_.emplace(fileName, std::make_shared()).first->second, true }; } cmQtAutoMocUic::ParseCacheT::ParseCacheT() = default; cmQtAutoMocUic::ParseCacheT::~ParseCacheT() = default; void cmQtAutoMocUic::ParseCacheT::Clear() { Map_.clear(); } bool cmQtAutoMocUic::ParseCacheT::ReadFromFile(std::string const& fileName) { cmsys::ifstream fin(fileName.c_str()); if (!fin) { return false; } FileHandleT fileHandle; std::string line; while (std::getline(fin, line)) { // Check if this an empty or a comment line if (line.empty() || line.front() == '#') { continue; } // Drop carriage return character at the end if (line.back() == '\r') { line.pop_back(); if (line.empty()) { continue; } } // Check if this a file name line if (line.front() != ' ') { fileHandle = GetOrInsert(line).first; continue; } // Bad line or bad file handle if (!fileHandle || (line.size() < 6)) { continue; } constexpr std::size_t offset = 5; if (cmHasLiteralPrefix(line, " mmc:")) { fileHandle->Moc.Macro = line.substr(offset); continue; } if (cmHasLiteralPrefix(line, " miu:")) { fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset), MocUnderscoreLength); continue; } if (cmHasLiteralPrefix(line, " mid:")) { fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0); continue; } if (cmHasLiteralPrefix(line, " mdp:")) { fileHandle->Moc.Depends.emplace_back(line.substr(offset)); continue; } if (cmHasLiteralPrefix(line, " uic:")) { fileHandle->Uic.Include.emplace_back(line.substr(offset), UiUnderscoreLength); continue; } if (cmHasLiteralPrefix(line, " udp:")) { fileHandle->Uic.Depends.emplace_back(line.substr(offset)); continue; } } return true; } bool cmQtAutoMocUic::ParseCacheT::WriteToFile(std::string const& fileName) { cmGeneratedFileStream ofs(fileName); if (!ofs) { return false; } ofs << "# Generated by CMake. Changes will be overwritten." << std::endl; for (auto const& pair : Map_) { ofs << pair.first << std::endl; FileT const& file = *pair.second; if (!file.Moc.Macro.empty()) { ofs << " mmc:" << file.Moc.Macro << std::endl; } for (IncludeKeyT const& item : file.Moc.Include.Underscore) { ofs << " miu:" << item.Key << std::endl; } for (IncludeKeyT const& item : file.Moc.Include.Dot) { ofs << " mid:" << item.Key << std::endl; } for (std::string const& item : file.Moc.Depends) { ofs << " mdp:" << item << std::endl; } for (IncludeKeyT const& item : file.Uic.Include) { ofs << " uic:" << item.Key << std::endl; } for (std::string const& item : file.Uic.Depends) { ofs << " udp:" << item << std::endl; } } return ofs.Close(); } cmQtAutoMocUic::BaseSettingsT::BaseSettingsT() = default; cmQtAutoMocUic::BaseSettingsT::~BaseSettingsT() = default; cmQtAutoMocUic::MocSettingsT::MocSettingsT() { RegExpInclude.compile( "(^|\n)[ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); } cmQtAutoMocUic::MocSettingsT::~MocSettingsT() = default; bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const { std::string res; const auto itB = MacroFilters.cbegin(); const auto itE = MacroFilters.cend(); const auto itL = itE - 1; auto itC = itB; for (; itC != itE; ++itC) { // Separator if (itC != itB) { if (itC != itL) { res += ", "; } else { res += " or "; } } // Key res += itC->Key; } return res; } cmQtAutoMocUic::UicSettingsT::UicSettingsT() { RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); } cmQtAutoMocUic::UicSettingsT::~UicSettingsT() = default; bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } void cmQtAutoMocUic::JobT::LogError(GenT genType, std::string const& message) const { Gen()->AbortError(); Gen()->Log().Error(genType, message); } void cmQtAutoMocUic::JobT::LogFileError(GenT genType, std::string const& filename, std::string const& message) const { Gen()->AbortError(); Gen()->Log().ErrorFile(genType, filename, message); } void cmQtAutoMocUic::JobT::LogCommandError( GenT genType, std::string const& message, std::vector const& command, std::string const& output) const { Gen()->AbortError(); Gen()->Log().ErrorCommand(genType, message, command, output); } bool cmQtAutoMocUic::JobT::RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, std::vector const& command, std::string* infoMessage) { // Log command if (Log().Verbose()) { std::string msg; if ((infoMessage != nullptr) && !infoMessage->empty()) { msg = *infoMessage; if (msg.back() != '\n') { msg += '\n'; } } msg += QuotedCommand(command); msg += '\n'; Log().Info(genType, msg); } return cmWorkerPool::JobT::RunProcess(result, command, BaseConst().AutogenBuildDir); } void cmQtAutoMocUic::JobMocPredefsT::Process() { // (Re)generate moc_predefs.h on demand std::unique_ptr reason; if (Log().Verbose()) { reason = cm::make_unique(); } if (!Update(reason.get())) { return; } std::string const& predefsFileRel = MocConst().PredefsFileRel; std::string const& predefsFileAbs = MocConst().PredefsFileAbs; { cmWorkerPool::ProcessResultT result; { // Compose command std::vector cmd = MocConst().PredefsCmd; // Add includes cmAppend(cmd, MocConst().Includes); // Add definitions for (std::string const& def : MocConst().Definitions) { cmd.emplace_back("-D" + def); } // Execute command if (!RunProcess(GenT::MOC, result, cmd, reason.get())) { std::string msg = "The content generation command for "; msg += Quoted(predefsFileRel); msg += " failed.\n"; msg += result.ErrorMessage; LogCommandError(GenT::MOC, msg, cmd, result.StdOut); return; } } // (Re)write predefs file only on demand if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) { if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) { std::string msg = "Writing "; msg += Quoted(predefsFileRel); msg += " failed."; LogFileError(GenT::MOC, predefsFileAbs, msg); return; } } else { // Touch to update the time stamp if (Log().Verbose()) { Log().Info(GenT::MOC, "Touching " + Quoted(predefsFileRel)); } if (!cmSystemTools::Touch(predefsFileAbs, false)) { std::string msg = "Touching "; msg += Quoted(predefsFileAbs); msg += " failed."; LogFileError(GenT::MOC, predefsFileAbs, msg); return; } } } // Read file time afterwards if (!MocEval().PredefsTime.Load(predefsFileAbs)) { LogFileError(GenT::MOC, predefsFileAbs, "File time reading failed."); return; } } bool cmQtAutoMocUic::JobMocPredefsT::Update(std::string* reason) const { // Test if the file exists if (!MocEval().PredefsTime.Load(MocConst().PredefsFileAbs)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(MocConst().PredefsFileRel); *reason += ", because it doesn't exist."; } return true; } // Test if the settings changed if (MocConst().SettingsChanged) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(MocConst().PredefsFileRel); *reason += ", because the moc settings changed."; } return true; } // Test if the executable is newer { std::string const& exec = MocConst().PredefsCmd.at(0); cmFileTime execTime; if (execTime.Load(exec)) { if (MocEval().PredefsTime.Older(execTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(MocConst().PredefsFileRel); *reason += " because it is older than "; *reason += Quoted(exec); *reason += "."; } return true; } } } return false; } bool cmQtAutoMocUic::JobParseT::ReadFile() { // Clear old parse information FileHandle->ParseData->Clear(); std::string const& fileName = FileHandle->FileName; // Write info if (Log().Verbose()) { Log().Info(GenT::GEN, "Parsing " + Quoted(fileName)); } // Read file content { std::string error; if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) { LogFileError(GenT::GEN, fileName, "Could not read the file: " + error); return false; } } // Warn if empty if (Content.empty()) { Log().WarningFile(GenT::GEN, fileName, "The file is empty."); return false; } return true; } void cmQtAutoMocUic::JobParseT::CreateKeys(std::vector& container, std::set const& source, std::size_t basePrefixLength) { if (source.empty()) { return; } container.reserve(source.size()); for (std::string const& src : source) { container.emplace_back(src, basePrefixLength); } } void cmQtAutoMocUic::JobParseT::MocMacro() { for (KeyExpT const& filter : MocConst().MacroFilters) { // Run a simple find string check if (Content.find(filter.Key) == std::string::npos) { continue; } // Run the expensive regular expression check loop cmsys::RegularExpressionMatch match; if (filter.Exp.find(Content.c_str(), match)) { // Keep detected macro name FileHandle->ParseData->Moc.Macro = filter.Key; return; } } } void cmQtAutoMocUic::JobParseT::MocDependecies() { if (MocConst().DependFilters.empty()) { return; } // Find dependency strings std::set parseDepends; for (KeyExpT const& filter : MocConst().DependFilters) { // Run a simple find string check if (Content.find(filter.Key) == std::string::npos) { continue; } // Run the expensive regular expression check loop const char* contentChars = Content.c_str(); cmsys::RegularExpressionMatch match; while (filter.Exp.find(contentChars, match)) { { std::string dep = match.match(1); if (!dep.empty()) { parseDepends.emplace(std::move(dep)); } } contentChars += match.end(); } } // Store dependency strings { auto& Depends = FileHandle->ParseData->Moc.Depends; Depends.reserve(parseDepends.size()); for (std::string const& item : parseDepends) { Depends.emplace_back(item); // Replace end of line characters in filenames std::string& path = Depends.back(); std::replace(path.begin(), path.end(), '\n', ' '); std::replace(path.begin(), path.end(), '\r', ' '); } } } void cmQtAutoMocUic::JobParseT::MocIncludes() { if (Content.find("moc") == std::string::npos) { return; } std::set underscore; std::set dot; { const char* contentChars = Content.c_str(); cmsys::RegularExpression const& regExp = MocConst().RegExpInclude; cmsys::RegularExpressionMatch match; while (regExp.find(contentChars, match)) { std::string incString = match.match(2); std::string const incBase = cmSystemTools::GetFilenameWithoutLastExtension(incString); if (cmHasLiteralPrefix(incBase, "moc_")) { // moc_.cpp // Remove the moc_ part from the base name underscore.emplace(std::move(incString)); } else { // .moc dot.emplace(std::move(incString)); } // Forward content pointer contentChars += match.end(); } } auto& Include = FileHandle->ParseData->Moc.Include; CreateKeys(Include.Underscore, underscore, MocUnderscoreLength); CreateKeys(Include.Dot, dot, 0); } void cmQtAutoMocUic::JobParseT::UicIncludes() { if (Content.find("ui_") == std::string::npos) { return; } std::set includes; { const char* contentChars = Content.c_str(); cmsys::RegularExpression const& regExp = UicConst().RegExpInclude; cmsys::RegularExpressionMatch match; while (regExp.find(contentChars, match)) { includes.emplace(match.match(2)); // Forward content pointer contentChars += match.end(); } } CreateKeys(FileHandle->ParseData->Uic.Include, includes, UiUnderscoreLength); } void cmQtAutoMocUic::JobParseHeaderT::Process() { if (!ReadFile()) { return; } // Moc parsing if (FileHandle->Moc) { MocMacro(); MocDependecies(); } // Uic parsing if (FileHandle->Uic) { UicIncludes(); } } void cmQtAutoMocUic::JobParseSourceT::Process() { if (!ReadFile()) { return; } // Moc parsing if (FileHandle->Moc) { MocMacro(); MocDependecies(); MocIncludes(); } // Uic parsing if (FileHandle->Uic) { UicIncludes(); } } void cmQtAutoMocUic::JobEvaluateT::Process() { // Evaluate for moc if (MocConst().Enabled) { // Evaluate headers for (auto const& pair : BaseEval().Headers) { if (!MocEvalHeader(pair.second)) { return; } } // Evaluate sources for (auto const& pair : BaseEval().Sources) { if (!MocEvalSource(pair.second)) { return; } } } // Evaluate for uic if (UicConst().Enabled) { if (!UicEval(BaseEval().Headers) || !UicEval(BaseEval().Sources)) { return; } } // Add discovered header parse jobs Gen()->CreateParseJobs(MocEval().HeadersDiscovered); // Add generate job after Gen()->WorkerPool().EmplaceJob(); } bool cmQtAutoMocUic::JobEvaluateT::MocEvalHeader(SourceFileHandleT source) { SourceFileT const& sourceFile = *source; auto const& parseData = sourceFile.ParseData->Moc; if (!source->Moc) { return true; } if (!parseData.Macro.empty()) { // Create a new mapping MappingHandleT handle = std::make_shared(); handle->SourceFile = std::move(source); // Absolute build path if (BaseConst().MultiConfig) { handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath); } else { handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath); } // Register mapping in headers map MocRegisterMapping(handle, true); } return true; } bool cmQtAutoMocUic::JobEvaluateT::MocEvalSource( SourceFileHandleT const& source) { SourceFileT const& sourceFile = *source; auto const& parseData = sourceFile.ParseData->Moc; if (!sourceFile.Moc || (parseData.Macro.empty() && parseData.Include.Underscore.empty() && parseData.Include.Dot.empty())) { return true; } std::string const sourceDir = SubDirPrefix(sourceFile.FileName); std::string const sourceBase = cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName); // For relaxed mode check if the own "moc_" or ".moc" file is included bool const relaxedMode = MocConst().RelaxedMode; bool sourceIncludesMocUnderscore = false; bool sourceIncludesDotMoc = false; // Check if the sources own "moc_" or ".moc" file is included if (relaxedMode) { for (IncludeKeyT const& incKey : parseData.Include.Underscore) { if (incKey.Base == sourceBase) { sourceIncludesMocUnderscore = true; break; } } } for (IncludeKeyT const& incKey : parseData.Include.Dot) { if (incKey.Base == sourceBase) { sourceIncludesDotMoc = true; break; } } // Check if this source needs to be moc processed but doesn't. if (!sourceIncludesDotMoc && !parseData.Macro.empty() && !(relaxedMode && sourceIncludesMocUnderscore)) { { std::string emsg = "The file contains a "; emsg += Quoted(parseData.Macro); emsg += " macro, but does not include "; emsg += Quoted(sourceBase + ".moc"); emsg += "!\nConsider to\n - add #include \""; emsg += sourceBase; emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; LogFileError(GenT::MOC, sourceFile.FileName, emsg); } return false; } // Evaluate "moc_" includes for (IncludeKeyT const& incKey : parseData.Include.Underscore) { std::string const headerBase = incKey.Dir + incKey.Base; SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase); if (!header) { { std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += ",\nbut the header could not be found " "in the following locations\n"; msg += MocMessageTestHeaders(headerBase); LogFileError(GenT::MOC, sourceFile.FileName, msg); } return false; } // The include might be handled differently in relaxed mode if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() && (incKey.Base == sourceBase)) { // The .cpp file includes a Qt macro but does not include the // .moc file. In this case, the moc_.cpp should probably // be generated from .cpp instead of .h, because otherwise // it won't build. But warn, since this is not how it is supposed to be // used. This is for KDE4 compatibility. { // Issue a warning std::string msg = "The file contains a "; msg += Quoted(parseData.Macro); msg += " macro, but does not include "; msg += Quoted(sourceBase + ".moc"); msg += ".\nInstead it includes "; msg += Quoted(incKey.Key); msg += ".\nRunning moc on the source\n "; msg += Quoted(sourceFile.FileName); msg += "!\nBetter include "; msg += Quoted(sourceBase + ".moc"); msg += " for compatibility with regular mode.\n"; msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"; Log().WarningFile(GenT::MOC, sourceFile.FileName, msg); } // Create mapping if (!MocRegisterIncluded(incKey.Key, source, source, false)) { return false; } continue; } // Check if header is skipped if (MocConst().skipped(header->FileName)) { continue; } // Create mapping if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) { return false; } } // Evaluate ".moc" includes if (relaxedMode) { // Relaxed mode for (IncludeKeyT const& incKey : parseData.Include.Dot) { // Check if this is the sources own .moc file bool const ownMoc = (incKey.Base == sourceBase); if (ownMoc && !parseData.Macro.empty()) { // Create mapping for the regular use case if (!MocRegisterIncluded(incKey.Key, source, source, false)) { return false; } continue; } // Try to find a header instead but issue a warning. // This is for KDE4 compatibility. std::string const headerBase = incKey.Dir + incKey.Base; SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase); if (!header) { std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += ",\nwhich seems to be the moc file from a different source " "file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a matching header" "could not be found in the following locations\n"; msg += MocMessageTestHeaders(headerBase); LogFileError(GenT::MOC, sourceFile.FileName, msg); return false; } // Check if header is skipped if (MocConst().skipped(header->FileName)) { continue; } // Issue a warning if (ownMoc && parseData.Macro.empty()) { std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += ", but does not contain a\n"; msg += MocConst().MacrosString(); msg += " macro.\nRunning moc on the header\n "; msg += Quoted(header->FileName); msg += "!\nBetter include "; msg += Quoted("moc_" + incKey.Base + ".cpp"); msg += " for a compatibility with regular mode.\n"; msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"; Log().WarningFile(GenT::MOC, sourceFile.FileName, msg); } else { std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += " instead of "; msg += Quoted("moc_" + incKey.Base + ".cpp"); msg += ".\nRunning moc on the header\n "; msg += Quoted(header->FileName); msg += "!\nBetter include "; msg += Quoted("moc_" + incKey.Base + ".cpp"); msg += " for compatibility with regular mode.\n"; msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"; Log().WarningFile(GenT::MOC, sourceFile.FileName, msg); } // Create mapping if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) { return false; } } } else { // Strict mode for (IncludeKeyT const& incKey : parseData.Include.Dot) { // Check if this is the sources own .moc file bool const ownMoc = (incKey.Base == sourceBase); if (!ownMoc) { // Don't allow .moc include other than own in regular mode std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += ",\nwhich seems to be the moc file from a different " "source file.\nThis is not supported. Include "; msg += Quoted(sourceBase + ".moc"); msg += " to run moc on this source file."; LogFileError(GenT::MOC, sourceFile.FileName, msg); return false; } // Accept but issue a warning if moc isn't required if (parseData.Macro.empty()) { std::string msg = "The file includes the moc file "; msg += Quoted(incKey.Key); msg += ", but does not contain a "; msg += MocConst().MacrosString(); msg += " macro."; Log().WarningFile(GenT::MOC, sourceFile.FileName, msg); } // Create mapping if (!MocRegisterIncluded(incKey.Key, source, source, false)) { return false; } } } return true; } cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindIncludedHeader( std::string const& includerDir, std::string const& includeBase) const { // Search in vicinity of the source { SourceFileHandleT res = MocFindHeader(includerDir + includeBase); if (res) { return res; } } // Search in include directories for (std::string const& path : MocConst().IncludePaths) { std::string testPath = path; testPath += '/'; testPath += includeBase; SourceFileHandleT res = MocFindHeader(testPath); if (res) { return res; } } // Return without success return SourceFileHandleT(); } cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindHeader( std::string const& basePath) const { std::string testPath; testPath.reserve(basePath.size() + 8); for (std::string const& ext : BaseConst().HeaderExtensions) { testPath.clear(); testPath += basePath; testPath += '.'; testPath += ext; cmFileTime fileTime; if (fileTime.Load(testPath)) { // Compute real path of the file testPath = cmSystemTools::GetRealPath(testPath); // Return a known file if it exists already { auto it = BaseEval().Headers.find(testPath); if (it != BaseEval().Headers.end()) { return it->second; } } // Created and return discovered file entry SourceFileHandleT& res = MocEval().HeadersDiscovered[testPath]; if (!res) { res = std::make_shared(testPath); res->FileTime = fileTime; res->Moc = true; } return res; } } // Return without success return SourceFileHandleT(); } std::string cmQtAutoMocUic::JobEvaluateT::MocMessageTestHeaders( std::string const& fileBase) const { std::ostringstream res; { std::string exts = ".{"; exts += cmJoin(BaseConst().HeaderExtensions, ","); exts += '}'; // Compose result string res << " " << fileBase << exts << '\n'; for (std::string const& path : MocConst().IncludePaths) { res << " " << path << '/' << fileBase << exts << '\n'; } } return res.str(); } bool cmQtAutoMocUic::JobEvaluateT::MocRegisterIncluded( std::string const& includeString, SourceFileHandleT includerFileHandle, SourceFileHandleT sourceFileHandle, bool sourceIsHeader) const { // Check if this file is already included MappingHandleT& handle = MocEval().Includes[includeString]; if (handle) { // Check if the output file would be generated from different source files if (handle->SourceFile != sourceFileHandle) { std::string msg = "The source files\n "; msg += Quoted(includerFileHandle->FileName); msg += '\n'; for (auto const& item : handle->IncluderFiles) { msg += " "; msg += Quoted(item->FileName); msg += '\n'; } msg += "contain the same include string "; msg += Quoted(includeString); msg += ", but\nthe moc file would be generated from different " "source files\n "; msg += Quoted(sourceFileHandle->FileName); msg += " and\n "; msg += Quoted(handle->SourceFile->FileName); msg += ".\nConsider to\n" " - not include the \"moc_.cpp\" file\n" " - add a directory prefix to a \".moc\" include " "(e.g \"sub/.moc\")\n" " - rename the source file(s)\n"; LogError(GenT::MOC, msg); return false; } // The same mapping already exists. Just add to the includers list. handle->IncluderFiles.emplace_back(std::move(includerFileHandle)); return true; } // Create a new mapping handle = std::make_shared(); handle->IncludeString = includeString; handle->IncluderFiles.emplace_back(std::move(includerFileHandle)); handle->SourceFile = std::move(sourceFileHandle); handle->OutputFile += Gen()->AbsoluteIncludePath(includeString); // Register mapping in sources/headers map MocRegisterMapping(handle, sourceIsHeader); return true; } void cmQtAutoMocUic::JobEvaluateT::MocRegisterMapping( MappingHandleT mappingHandle, bool sourceIsHeader) const { auto& regMap = sourceIsHeader ? MocEval().HeaderMappings : MocEval().SourceMappings; // Check if source file already gets mapped auto& regHandle = regMap[mappingHandle->SourceFile->FileName]; if (!regHandle) { // Yet unknown mapping regHandle = std::move(mappingHandle); } else { // Mappings with include string override those without if (!mappingHandle->IncludeString.empty()) { regHandle = std::move(mappingHandle); } } } bool cmQtAutoMocUic::JobEvaluateT::UicEval(SourceFileMapT const& fileMap) { for (auto const& pair : fileMap) { if (!UicEvalFile(pair.second)) { return false; } } return true; } bool cmQtAutoMocUic::JobEvaluateT::UicEvalFile( SourceFileHandleT sourceFileHandle) { SourceFileT const& sourceFile = *sourceFileHandle; auto const& Include = sourceFile.ParseData->Uic.Include; if (!sourceFile.Uic || Include.empty()) { return true; } std::string const sourceDir = SubDirPrefix(sourceFile.FileName); for (IncludeKeyT const& incKey : Include) { // Find .ui file name SourceFileHandleT uiFileHandle = UicFindIncludedUi(sourceFile.FileName, sourceDir, incKey); if (!uiFileHandle || UicConst().skipped(uiFileHandle->FileName)) { continue; } // Register mapping if (!UicRegisterMapping(incKey.Key, std::move(uiFileHandle), std::move(sourceFileHandle))) { return false; } } return true; } bool cmQtAutoMocUic::JobEvaluateT::UicRegisterMapping( std::string const& includeString, SourceFileHandleT uiFileHandle, SourceFileHandleT includerFileHandle) { auto& Includes = Gen()->UicEval().Includes; auto it = Includes.find(includeString); if (it != Includes.end()) { MappingHandleT const& handle = it->second; if (handle->SourceFile != uiFileHandle) { // The output file already gets generated - from a different .ui file! std::string msg = "The source files\n "; msg += Quoted(includerFileHandle->FileName); msg += '\n'; for (auto const& item : handle->IncluderFiles) { msg += " "; msg += Quoted(item->FileName); msg += '\n'; } msg += "contain the same include string "; msg += Quoted(includeString); msg += ", but\nthe uic file would be generated from different " "user interface files\n "; msg += Quoted(uiFileHandle->FileName); msg += " and\n "; msg += Quoted(handle->SourceFile->FileName); msg += ".\nConsider to\n" " - add a directory prefix to a \"ui_.h\" include " "(e.g \"sub/ui_.h\")\n" " - rename the .ui file(s) and adjust the \"ui_.h\" " "include(s)\n"; LogError(GenT::UIC, msg); return false; } // Add includer file to existing mapping handle->IncluderFiles.emplace_back(std::move(includerFileHandle)); } else { // New mapping handle MappingHandleT handle = std::make_shared(); handle->IncludeString = includeString; handle->IncluderFiles.emplace_back(std::move(includerFileHandle)); handle->SourceFile = std::move(uiFileHandle); handle->OutputFile += Gen()->AbsoluteIncludePath(includeString); // Register mapping Includes.emplace(includeString, std::move(handle)); } return true; } cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::UicFindIncludedUi( std::string const& sourceFile, std::string const& sourceDir, IncludeKeyT const& incKey) const { std::string searchFileName = incKey.Base; searchFileName += ".ui"; // Collect search paths list std::vector testFiles; { auto& searchPaths = UicConst().SearchPaths; testFiles.reserve((searchPaths.size() + 1) * 2); // Vicinity of the source testFiles.emplace_back(sourceDir + searchFileName); if (!incKey.Dir.empty()) { std::string path = sourceDir; path += incKey.Dir; path += searchFileName; testFiles.emplace_back(path); } // AUTOUIC search paths if (!searchPaths.empty()) { for (std::string const& sPath : searchPaths) { std::string path = sPath; path += '/'; path += searchFileName; testFiles.emplace_back(std::move(path)); } if (!incKey.Dir.empty()) { for (std::string const& sPath : searchPaths) { std::string path = sPath; path += '/'; path += incKey.Dir; path += searchFileName; testFiles.emplace_back(std::move(path)); } } } } // Search for the .ui file! for (std::string const& testFile : testFiles) { cmFileTime fileTime; if (fileTime.Load(testFile)) { // .ui file found in files system! std::string realPath = cmSystemTools::GetRealPath(testFile); // Get or create .ui file handle SourceFileHandleT& handle = Gen()->UicEval().UiFiles[realPath]; if (!handle) { // The file wasn't registered, yet handle = std::make_shared(realPath); handle->FileTime = fileTime; } return handle; } } // Log error { std::string msg = "The file includes the uic file "; msg += Quoted(incKey.Key); msg += ",\nbut the user interface file "; msg += Quoted(searchFileName); msg += "\ncould not be found in the following locations\n"; for (std::string const& testFile : testFiles) { msg += " "; msg += Quoted(testFile); msg += '\n'; } LogFileError(GenT::UIC, sourceFile, msg); } return SourceFileHandleT(); } void cmQtAutoMocUic::JobGenerateT::Process() { // Add moc compile jobs if (MocConst().Enabled) { for (auto const& pair : MocEval().HeaderMappings) { // Register if this mapping is a candidate for mocs_compilation.cpp bool const compFile = pair.second->IncludeString.empty(); if (compFile) { MocEval().CompFiles.emplace_back(pair.second->SourceFile->BuildPath); } if (!MocGenerate(pair.second, compFile)) { return; } } for (auto const& pair : MocEval().SourceMappings) { if (!MocGenerate(pair.second, false)) { return; } } // Add mocs compilations job on demand Gen()->WorkerPool().EmplaceJob(); } // Add uic compile jobs if (UicConst().Enabled) { for (auto const& pair : Gen()->UicEval().Includes) { if (!UicGenerate(pair.second)) { return; } } } // Add finish job Gen()->WorkerPool().EmplaceJob(); } bool cmQtAutoMocUic::JobGenerateT::MocGenerate(MappingHandleT const& mapping, bool compFile) const { std::unique_ptr reason; if (Log().Verbose()) { reason = cm::make_unique(); } if (MocUpdate(*mapping, reason.get())) { // Create the parent directory if (!MakeParentDirectory(mapping->OutputFile)) { LogFileError(GenT::MOC, mapping->OutputFile, "Could not create parent directory."); return false; } // Add moc job Gen()->WorkerPool().EmplaceJob(mapping, std::move(reason)); // Check if a moc job for a mocs_compilation.cpp entry was generated if (compFile) { MocEval().CompUpdated = true; } } return true; } bool cmQtAutoMocUic::JobGenerateT::MocUpdate(MappingT const& mapping, std::string* reason) const { std::string const& sourceFile = mapping.SourceFile->FileName; std::string const& outputFile = mapping.OutputFile; // Test if the output file exists cmFileTime outputFileTime; if (!outputFileTime.Load(outputFile)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it doesn't exist, from "; *reason += Quoted(sourceFile); } return true; } // Test if any setting changed if (MocConst().SettingsChanged) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because the uic settings changed, from "; *reason += Quoted(sourceFile); } return true; } // Test if the source file is newer if (outputFileTime.Older(mapping.SourceFile->FileTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it's older than its source file, from "; *reason += Quoted(sourceFile); } return true; } // Test if the moc_predefs file is newer if (!MocConst().PredefsFileAbs.empty()) { if (outputFileTime.Older(MocEval().PredefsTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it's older than "; *reason += Quoted(MocConst().PredefsFileAbs); *reason += ", from "; *reason += Quoted(sourceFile); } return true; } } // Test if the moc executable is newer if (outputFileTime.Older(MocConst().ExecutableTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it's older than the moc executable, from "; *reason += Quoted(sourceFile); } return true; } // Test if a dependency file is newer { // Check dependency timestamps std::string const sourceDir = SubDirPrefix(sourceFile); for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) { // Find dependency file auto const depMatch = MocFindDependency(sourceDir, dep); if (depMatch.first.empty()) { Log().WarningFile(GenT::MOC, sourceFile, "Could not find dependency file " + Quoted(dep)); continue; } // Test if dependency file is older if (outputFileTime.Older(depMatch.second)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it's older than its dependency file "; *reason += Quoted(depMatch.first); *reason += ", from "; *reason += Quoted(sourceFile); } return true; } } } return false; } std::pair cmQtAutoMocUic::JobGenerateT::MocFindDependency( std::string const& sourceDir, std::string const& includeString) const { typedef std::pair ResPair; // Search in vicinity of the source { ResPair res{ sourceDir + includeString, {} }; if (res.second.Load(res.first)) { return res; } } // Search in include directories for (std::string const& includePath : MocConst().IncludePaths) { ResPair res{ includePath, {} }; res.first += '/'; res.first += includeString; if (res.second.Load(res.first)) { return res; } } // Return empty return ResPair(); } bool cmQtAutoMocUic::JobGenerateT::UicGenerate( MappingHandleT const& mapping) const { std::unique_ptr reason; if (Log().Verbose()) { reason = cm::make_unique(); } if (UicUpdate(*mapping, reason.get())) { // Create the parent directory if (!MakeParentDirectory(mapping->OutputFile)) { LogFileError(GenT::UIC, mapping->OutputFile, "Could not create parent directory."); return false; } // Add uic job Gen()->WorkerPool().EmplaceJob(mapping, std::move(reason)); } return true; } bool cmQtAutoMocUic::JobGenerateT::UicUpdate(MappingT const& mapping, std::string* reason) const { std::string const& sourceFile = mapping.SourceFile->FileName; std::string const& outputFile = mapping.OutputFile; // Test if the build file exists cmFileTime outputFileTime; if (!outputFileTime.Load(outputFile)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it doesn't exist, from "; *reason += Quoted(sourceFile); } return true; } // Test if the uic settings changed if (UicConst().SettingsChanged) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because the uic settings changed, from "; *reason += Quoted(sourceFile); } return true; } // Test if the source file is newer if (outputFileTime.Older(mapping.SourceFile->FileTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += " because it's older than the source file "; *reason += Quoted(sourceFile); } return true; } // Test if the uic executable is newer if (outputFileTime.Older(UicConst().ExecutableTime)) { if (reason != nullptr) { *reason = "Generating "; *reason += Quoted(outputFile); *reason += ", because it's older than the uic executable, from "; *reason += Quoted(sourceFile); } return true; } return false; } void cmQtAutoMocUic::JobMocT::Process() { std::string const& sourceFile = Mapping->SourceFile->FileName; std::string const& outputFile = Mapping->OutputFile; // Compose moc command std::vector cmd; cmd.push_back(MocConst().Executable); // Add options cmAppend(cmd, MocConst().AllOptions); // Add predefs include if (!MocConst().PredefsFileAbs.empty()) { cmd.emplace_back("--include"); cmd.push_back(MocConst().PredefsFileAbs); } cmd.emplace_back("-o"); cmd.push_back(outputFile); cmd.push_back(sourceFile); // Execute moc command cmWorkerPool::ProcessResultT result; if (RunProcess(GenT::MOC, result, cmd, Reason.get())) { // Moc command success. Print moc output. if (!result.StdOut.empty()) { Log().Info(GenT::MOC, result.StdOut); } } else { // Moc command failed std::string msg = "The moc process failed to compile\n "; msg += Quoted(sourceFile); msg += "\ninto\n "; msg += Quoted(outputFile); if (Mapping->IncluderFiles.empty()) { msg += ".\n"; } else { msg += "\nincluded by\n"; for (auto const& item : Mapping->IncluderFiles) { msg += " "; msg += Quoted(item->FileName); msg += '\n'; } } msg += result.ErrorMessage; LogCommandError(GenT::MOC, msg, cmd, result.StdOut); } } void cmQtAutoMocUic::JobUicT::Process() { std::string const& sourceFile = Mapping->SourceFile->FileName; std::string const& outputFile = Mapping->OutputFile; // Compose uic command std::vector cmd; cmd.push_back(UicConst().Executable); { std::vector allOpts = UicConst().TargetOptions; auto optionIt = UicConst().Options.find(sourceFile); if (optionIt != UicConst().Options.end()) { UicMergeOptions(allOpts, optionIt->second, (BaseConst().QtVersionMajor == 5)); } cmAppend(cmd, allOpts); } cmd.emplace_back("-o"); cmd.emplace_back(outputFile); cmd.emplace_back(sourceFile); cmWorkerPool::ProcessResultT result; if (RunProcess(GenT::UIC, result, cmd, Reason.get())) { // Uic command success // Print uic output if (!result.StdOut.empty()) { Log().Info(GenT::UIC, result.StdOut); } } else { // Uic command failed std::string msg = "The uic process failed to compile\n "; msg += Quoted(sourceFile); msg += "\ninto\n "; msg += Quoted(outputFile); msg += "\nincluded by\n"; for (auto const& item : Mapping->IncluderFiles) { msg += " "; msg += Quoted(item->FileName); msg += '\n'; } msg += result.ErrorMessage; LogCommandError(GenT::UIC, msg, cmd, result.StdOut); } } void cmQtAutoMocUic::JobMocsCompilationT::Process() { // Compose mocs compilation file content std::string content = "// This file is autogenerated. Changes will be overwritten.\n"; if (MocEval().CompFiles.empty()) { // Placeholder content content += "// No files found that require moc or the moc files are " "included\n"; content += "enum some_compilers { need_more_than_nothing };\n"; } else { // Valid content char const clampB = BaseConst().MultiConfig ? '<' : '"'; char const clampE = BaseConst().MultiConfig ? '>' : '"'; for (std::string const& mocfile : MocEval().CompFiles) { content += "#include "; content += clampB; content += mocfile; content += clampE; content += '\n'; } } std::string const& compAbs = MocConst().CompFileAbs; if (cmQtAutoGenerator::FileDiffers(compAbs, content)) { // Actually write mocs compilation file if (Log().Verbose()) { Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs); } if (!FileWrite(compAbs, content)) { LogFileError(GenT::MOC, compAbs, "mocs compilation file writing failed."); } } else if (MocEval().CompUpdated) { // Only touch mocs compilation file if (Log().Verbose()) { Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs); } if (!cmSystemTools::Touch(compAbs, false)) { LogFileError(GenT::MOC, compAbs, "mocs compilation file touching failed."); } } } void cmQtAutoMocUic::JobFinishT::Process() { Gen()->AbortSuccess(); } cmQtAutoMocUic::cmQtAutoMocUic() = default; cmQtAutoMocUic::~cmQtAutoMocUic() = default; bool cmQtAutoMocUic::Init(cmMakefile* makefile) { // Utility lambdas auto InfoGet = [makefile](const char* key) { return makefile->GetSafeDefinition(key); }; auto InfoGetBool = [makefile](const char* key) { return makefile->IsOn(key); }; auto InfoGetList = [makefile](const char* key) -> std::vector { std::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; }; auto LogInfoError = [this](std::string const& msg) -> bool { std::ostringstream err; err << "In " << Quoted(this->InfoFile()) << ":\n" << msg; this->Log().Error(GenT::GEN, err.str()); return false; }; auto MatchSizes = [&LogInfoError](const char* keyA, const char* keyB, std::size_t sizeA, std::size_t sizeB) -> bool { if (sizeA == sizeB) { return true; } std::ostringstream err; err << "Lists sizes mismatch " << keyA << '(' << sizeA << ") " << keyB << '(' << sizeB << ')'; return LogInfoError(err.str()); }; // -- Read info file if (!makefile->ReadListFile(InfoFile())) { return LogInfoError("File processing failed"); } // -- Meta Logger_.RaiseVerbosity(InfoGet("AM_VERBOSITY")); BaseConst_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG"); { unsigned long num = 1; if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL").c_str(), &num)) { num = std::max(num, 1); num = std::min(num, ParallelMax); } WorkerPool_.SetThreadCount(static_cast(num)); } BaseConst_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); // - Files and directories BaseConst_.IncludeProjectDirsBefore = InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); BaseConst_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); BaseConst_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); BaseConst_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); BaseConst_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); BaseConst_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); if (BaseConst_.AutogenBuildDir.empty()) { return LogInfoError("Autogen build directory missing."); } BaseConst_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR"); if (BaseConst_.AutogenIncludeDir.empty()) { return LogInfoError("Autogen include directory missing."); } BaseConst_.CMakeExecutable = InfoGetConfig("AM_CMAKE_EXECUTABLE"); if (BaseConst_.CMakeExecutable.empty()) { return LogInfoError("CMake executable file name missing."); } if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) { std::string error = "The CMake executable "; error += Quoted(BaseConst_.CMakeExecutable); error += " does not exist."; return LogInfoError(error); } BaseConst_.ParseCacheFile = InfoGetConfig("AM_PARSE_CACHE_FILE"); if (BaseConst_.ParseCacheFile.empty()) { return LogInfoError("Parse cache file name missing."); } // - Settings file SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE"); if (SettingsFile_.empty()) { return LogInfoError("Settings file name missing."); } // - Qt environment { unsigned long qtv = BaseConst_.QtVersionMajor; if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(), &qtv)) { BaseConst_.QtVersionMajor = static_cast(qtv); } } // - Moc MocConst_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE"); if (!MocConst().Executable.empty()) { MocConst_.Enabled = true; // Load the executable file time if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) { std::string error = "The moc executable "; error += Quoted(MocConst_.Executable); error += " does not exist."; return LogInfoError(error); } for (std::string& sfl : InfoGetList("AM_MOC_SKIP")) { MocConst_.SkipList.insert(std::move(sfl)); } MocConst_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); MocConst_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); MocConst_.Options = InfoGetList("AM_MOC_OPTIONS"); MocConst_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) { MocConst_.MacroFilters.emplace_back( item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]")); } { auto addFilter = [this, &LogInfoError](std::string const& key, std::string const& exp) -> bool { auto filterErr = [&LogInfoError, &key, &exp](const char* err) -> bool { std::ostringstream ferr; ferr << "AUTOMOC_DEPEND_FILTERS: " << err << '\n'; ferr << " Key: " << Quoted(key) << '\n'; ferr << " Exp: " << Quoted(exp) << '\n'; return LogInfoError(ferr.str()); }; if (key.empty()) { return filterErr("Key is empty"); } if (exp.empty()) { return filterErr("Regular expression is empty"); } this->MocConst_.DependFilters.emplace_back(key, exp); if (!this->MocConst_.DependFilters.back().Exp.is_valid()) { return filterErr("Regular expression compiling failed"); } return true; }; // Insert default filter for Q_PLUGIN_METADATA if (BaseConst().QtVersionMajor != 4) { if (!addFilter("Q_PLUGIN_METADATA", "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" "[^\\)]*FILE[ \t]*\"([^\"]+)\"")) { return false; } } // Insert user defined dependency filters std::vector flts = InfoGetList("AM_MOC_DEPEND_FILTERS"); if ((flts.size() % 2) != 0) { return LogInfoError( "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); } for (auto itC = flts.begin(), itE = flts.end(); itC != itE; itC += 2) { if (!addFilter(*itC, *(itC + 1))) { return false; } } } MocConst_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); } // - Uic UicConst_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE"); if (!UicConst().Executable.empty()) { UicConst_.Enabled = true; // Load the executable file time if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) { std::string error = "The uic executable "; error += Quoted(UicConst_.Executable); error += " does not exist."; return LogInfoError(error); } for (std::string& sfl : InfoGetList("AM_UIC_SKIP")) { UicConst_.SkipList.insert(std::move(sfl)); } UicConst_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); UicConst_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS"); { const char* keyFiles = "AM_UIC_OPTIONS_FILES"; const char* keyOpts = "AM_UIC_OPTIONS_OPTIONS"; auto sources = InfoGetList(keyFiles); auto options = InfoGetLists(keyOpts); if (!MatchSizes(keyFiles, keyOpts, sources.size(), options.size())) { return false; } auto fitEnd = sources.cend(); auto fit = sources.begin(); auto oit = options.begin(); while (fit != fitEnd) { UicConst_.Options[*fit] = std::move(*oit); ++fit; ++oit; } } } // - Headers and sources { auto makeSource = [&LogInfoError](std::string const& fileName, std::string const& fileFlags) -> SourceFileHandleT { if (fileFlags.size() != 2) { LogInfoError("Invalid file flags string size"); return SourceFileHandleT(); } cmFileTime fileTime; if (!fileTime.Load(fileName)) { LogInfoError("The source file " + cmQtAutoGen::Quoted(fileName) + " does not exist."); return SourceFileHandleT(); } SourceFileHandleT sfh = std::make_shared(fileName); sfh->FileTime = fileTime; sfh->Moc = (fileFlags[0] == 'M'); sfh->Uic = (fileFlags[1] == 'U'); return sfh; }; // Headers { // Get file lists const char *keyFiles = "AM_HEADERS", *keyFlags = "AM_HEADERS_FLAGS"; std::vector files = InfoGetList(keyFiles); std::vector flags = InfoGetList(keyFlags); std::vector builds; if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) { return false; } if (MocConst().Enabled) { const char* keyPaths = "AM_HEADERS_BUILD_PATHS"; builds = InfoGetList(keyPaths); if (!MatchSizes(keyFiles, keyPaths, files.size(), builds.size())) { return false; } } // Process file lists for (std::size_t ii = 0; ii != files.size(); ++ii) { std::string& fileName(files[ii]); SourceFileHandleT sfh = makeSource(fileName, flags[ii]); if (!sfh) { return false; } if (MocConst().Enabled) { sfh->BuildPath = std::move(builds[ii]); if (sfh->BuildPath.empty()) { Log().ErrorFile(GenT::GEN, this->InfoFile(), "Header file build path is empty"); return false; } } BaseEval().Headers.emplace(std::move(fileName), std::move(sfh)); } } // Sources { const char *keyFiles = "AM_SOURCES", *keyFlags = "AM_SOURCES_FLAGS"; std::vector files = InfoGetList(keyFiles); std::vector flags = InfoGetList(keyFlags); if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) { return false; } // Process file lists for (std::size_t ii = 0; ii != files.size(); ++ii) { std::string& fileName(files[ii]); SourceFileHandleT sfh = makeSource(fileName, flags[ii]); if (!sfh) { return false; } BaseEval().Sources.emplace(std::move(fileName), std::move(sfh)); } } } // Init derived information // ------------------------ // Moc variables if (MocConst().Enabled) { // Mocs compilation file MocConst_.CompFileAbs = AbsoluteBuildPath("mocs_compilation.cpp"); // Moc predefs file if (!MocConst_.PredefsCmd.empty()) { MocConst_.PredefsFileRel = "moc_predefs"; if (BaseConst_.MultiConfig) { MocConst_.PredefsFileRel += '_'; MocConst_.PredefsFileRel += InfoConfig(); } MocConst_.PredefsFileRel += ".h"; MocConst_.PredefsFileAbs = AbsoluteBuildPath(MocConst().PredefsFileRel); } // Sort include directories on demand if (BaseConst().IncludeProjectDirsBefore) { // Move strings to temporary list std::list includes(MocConst().IncludePaths.begin(), MocConst().IncludePaths.end()); MocConst_.IncludePaths.clear(); MocConst_.IncludePaths.reserve(includes.size()); // Append project directories only { std::array const movePaths = { { &BaseConst().ProjectBinaryDir, &BaseConst().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())) { MocConst_.IncludePaths.push_back(path); it = includes.erase(it); } else { ++it; } } } } // Append remaining directories MocConst_.IncludePaths.insert(MocConst_.IncludePaths.end(), includes.begin(), includes.end()); } // Compose moc includes list { std::set frameworkPaths; for (std::string const& path : MocConst().IncludePaths) { MocConst_.Includes.push_back("-I" + path); // Extract framework path if (cmHasLiteralSuffix(path, ".framework/Headers")) { // Go up twice to get to the framework root std::vector pathComponents; cmSystemTools::SplitPath(path, pathComponents); frameworkPaths.emplace(cmSystemTools::JoinPath( pathComponents.begin(), pathComponents.end() - 2)); } } // Append framework includes for (std::string const& path : frameworkPaths) { MocConst_.Includes.emplace_back("-F"); MocConst_.Includes.push_back(path); } } // Setup single list with all options { // Add includes MocConst_.AllOptions.insert(MocConst_.AllOptions.end(), MocConst().Includes.begin(), MocConst().Includes.end()); // Add definitions for (std::string const& def : MocConst().Definitions) { MocConst_.AllOptions.push_back("-D" + def); } // Add options MocConst_.AllOptions.insert(MocConst_.AllOptions.end(), MocConst().Options.begin(), MocConst().Options.end()); } } return true; } template void cmQtAutoMocUic::CreateParseJobs(SourceFileMapT const& sourceMap) { cmFileTime const parseCacheTime = BaseEval().ParseCacheTime; ParseCacheT& parseCache = BaseEval().ParseCache; for (auto& src : sourceMap) { // Get or create the file parse data reference ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first); src.second->ParseData = std::move(cacheEntry.first); // Create a parse job if the cache file was missing or is older if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) { BaseEval().ParseCacheChanged = true; WorkerPool().EmplaceJob(src.second); } } } void cmQtAutoMocUic::InitJobs() { // Add moc_predefs.h job if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) { WorkerPool().EmplaceJob(); } // Add header parse jobs CreateParseJobs(BaseEval().Headers); // Add source parse jobs CreateParseJobs(BaseEval().Sources); // Add evaluate job WorkerPool().EmplaceJob(); } bool cmQtAutoMocUic::Process() { SettingsFileRead(); ParseCacheRead(); if (!CreateDirectories()) { return false; } InitJobs(); if (!WorkerPool_.Process(this)) { return false; } if (JobError_) { return false; } if (!ParseCacheWrite()) { return false; } if (!SettingsFileWrite()) { return false; } return true; } void cmQtAutoMocUic::SettingsFileRead() { // Compose current settings strings { cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256); std::string const sep(";"); auto cha = [&cryptoHash, &sep](std::string const& value) { cryptoHash.Append(value); cryptoHash.Append(sep); }; if (MocConst_.Enabled) { cryptoHash.Initialize(); cha(MocConst().Executable); for (auto const& value : MocConst().AllOptions) { cha(value); } cha(BaseConst().IncludeProjectDirsBefore ? "TRUE" : "FALSE"); for (auto const& value : MocConst().PredefsCmd) { cha(value); } for (auto const& filter : MocConst().DependFilters) { cha(filter.Key); } for (auto const& filter : MocConst().MacroFilters) { cha(filter.Key); } SettingsStringMoc_ = cryptoHash.FinalizeHex(); } if (UicConst().Enabled) { cryptoHash.Initialize(); cha(UicConst().Executable); for (auto const& value : UicConst().TargetOptions) { cha(value); } for (const auto& item : UicConst().Options) { cha(item.first); for (auto const& svalue : item.second) { cha(svalue); } } SettingsStringUic_ = cryptoHash.FinalizeHex(); } } // Read old settings and compare { std::string content; if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) { if (MocConst().Enabled) { if (SettingsStringMoc_ != SettingsFind(content, "moc")) { MocConst_.SettingsChanged = true; } } if (UicConst().Enabled) { if (SettingsStringUic_ != SettingsFind(content, "uic")) { UicConst_.SettingsChanged = true; } } // In case any setting changed remove the old settings file. // This triggers a full rebuild on the next run if the current // build is aborted before writing the current settings in the end. if (MocConst().SettingsChanged || UicConst().SettingsChanged) { cmSystemTools::RemoveFile(SettingsFile_); } } else { // Settings file read failed if (MocConst().Enabled) { MocConst_.SettingsChanged = true; } if (UicConst().Enabled) { UicConst_.SettingsChanged = true; } } } } bool cmQtAutoMocUic::SettingsFileWrite() { // Only write if any setting changed if (MocConst().SettingsChanged || UicConst().SettingsChanged) { if (Log().Verbose()) { Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_)); } // Compose settings file content std::string content; { auto SettingAppend = [&content](const char* key, std::string const& value) { if (!value.empty()) { content += key; content += ':'; content += value; content += '\n'; } }; SettingAppend("moc", SettingsStringMoc_); SettingAppend("uic", SettingsStringUic_); } // Write settings file std::string error; if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) { Log().ErrorFile(GenT::GEN, SettingsFile_, "Settings file writing failed. " + error); // Remove old settings file to trigger a full rebuild on the next run cmSystemTools::RemoveFile(SettingsFile_); return false; } } return true; } void cmQtAutoMocUic::ParseCacheRead() { const char* reason = nullptr; // Don't read the cache if it is invalid if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) { reason = "Refreshing parse cache because it doesn't exist."; } else if (MocConst().SettingsChanged || UicConst().SettingsChanged) { reason = "Refreshing parse cache because the settings changed."; } else if (BaseEval().ParseCacheTime.Older( BaseConst().CMakeExecutableTime)) { reason = "Refreshing parse cache because it is older than the CMake executable."; } if (reason != nullptr) { // Don't read but refresh the complete parse cache if (Log().Verbose()) { Log().Info(GenT::GEN, reason); } BaseEval().ParseCacheChanged = true; } else { // Read parse cache BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile); } } bool cmQtAutoMocUic::ParseCacheWrite() { if (BaseEval().ParseCacheChanged) { if (Log().Verbose()) { Log().Info(GenT::GEN, "Writing parse cache file " + Quoted(BaseConst().ParseCacheFile)); } if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) { Log().ErrorFile(GenT::GEN, BaseConst().ParseCacheFile, "Parse cache file writing failed."); return false; } } return true; } bool cmQtAutoMocUic::CreateDirectories() { // Create AUTOGEN include directory if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) { Log().ErrorFile(GenT::GEN, BaseConst().AutogenIncludeDir, "Could not create directory."); return false; } return true; } void cmQtAutoMocUic::Abort(bool error) { if (error) { JobError_.store(true); } WorkerPool_.Abort(); } std::string cmQtAutoMocUic::AbsoluteBuildPath( std::string const& relativePath) const { std::string res(BaseConst().AutogenBuildDir); res += '/'; res += relativePath; return res; } std::string cmQtAutoMocUic::AbsoluteIncludePath( std::string const& relativePath) const { std::string res(BaseConst().AutogenIncludeDir); res += '/'; res += relativePath; return res; }