diff options
author | Sebastian Holtermann <sebholt@xwmw.org> | 2019-05-02 09:03:13 (GMT) |
---|---|---|
committer | Sebastian Holtermann <sebholt@xwmw.org> | 2019-05-07 10:42:19 (GMT) |
commit | 7d50e1c6118b292ea3061ec9fee0230c5d50a5ff (patch) | |
tree | 48d3f4c459c88cc2c02fa78b0279324e2fbb5cce /Source/cmQtAutoMocUic.cxx | |
parent | c6f6e2b3053de02de149e80bd6a0f686a6731f68 (diff) | |
download | CMake-7d50e1c6118b292ea3061ec9fee0230c5d50a5ff.zip CMake-7d50e1c6118b292ea3061ec9fee0230c5d50a5ff.tar.gz CMake-7d50e1c6118b292ea3061ec9fee0230c5d50a5ff.tar.bz2 |
Autogen: Refactor AUTOMOC and AUTOUIC and add source file parse data caching
New features
------------
CMake's `AUTOMOC` and `AUTOUIC` now cache information extracted when parsing
source files in `CMakeFiles/<ORIGIN>_autogen.dir/ParseCache.txt`.
This leads to faster `<ORIGIN>_autogen` target rebuilds, because source files
will be parsed again only if they're newer than the `ParseCache.txt` file.
The parse cache will be recomputed if it is older than the CMake executable.
`AUTOMOC` and `AUTOUIC` now check if `moc` or `uic` output files are older
than the `moc` or `uic` executable. If an output file is older than the
compiler, it will be regenerated. Therefore if a new `moc` or `uic` version
is installed, all output files will be regenerated.
`AUTOMOC` and `AUTOUIC` error and warning messages are more detailed.
Internal changes
----------------
`moc` and `uic` output file names are not computed in the `_autogen`
target anymore but in `cmQtAutoGenInitializer`. This makes the available at
the configuration stage for improved dependency computations (to be done).
In `AutogenInfo.cmake`, equally sized lists for "source file names",
"source file flags" and "compiler output file names" are passed to the
`_autogen` target. This replaces the separate file lists for
`AUTOMOC` and `AUTOUIC`.
Files times are read from the file system only once by using `cmFileTime`
instances instead of `cmQtAutoGenerator::FileSystem::FileIsOlderThan` calls.
All calls to not thread safe file system functions are moved to non concurrent
fence jobs (see `cmWorkerPool::JobT::IsFence()`). This renders the
`cmQtAutoGenerator::FileSystem` wrapper class obsolete and it is removed.
Instead of composing a single large settings string that is fed to the
`cmCryptoHash`, now all setting sub strings are fed one by one to the
`cmCryptoHash` and the finalized result is stored.
The `std::mutex` in `cmQtAutoGenerator::Logger` is tagged `mutable` and most
`cmQtAutoGenerator::Logger` methods become `const`.
Outlook
-------
This patch provides the framework required to
- extract dependencies from `.ui` files in `AUTOUIC`.
These will help to address issue
#15420 "AUTOUIC: Track uic external inputs".
- generate adaptive `make` and `ninja` files in the `_autogen` target.
These will help to address issue
#16776 "AUTOUIC: Ninja needs two passes to correctly build Qt project".
- generate (possibly empty) `moc` and `uic` files for all headers instead of a
`mocs_compilation.cpp` file.
This will help to address issue
#17277 "AUTOMOC: Provide a option to allow AUTOMOC to compile individual "
"moc_x.cxx instead of including all in mocs_compilation.cxx"
Diffstat (limited to 'Source/cmQtAutoMocUic.cxx')
-rw-r--r-- | Source/cmQtAutoMocUic.cxx | 2725 |
1 files changed, 1588 insertions, 1137 deletions
diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index 005c27d..36794d6 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -4,7 +4,6 @@ #include <algorithm> #include <array> -#include <deque> #include <list> #include <memory> #include <set> @@ -13,67 +12,187 @@ #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 <unistd.h> #endif -// -- Class methods +static constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_" +static constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_" -std::string cmQtAutoMocUic::BaseSettingsT::AbsoluteBuildPath( - std::string const& relativePath) const +cmQtAutoMocUic::IncludeKeyT::IncludeKeyT(std::string const& key, + std::size_t basePrefixLength) + : Key(key) + , Dir(SubDirPrefix(key)) + , Base(cmSystemTools::GetFilenameWithoutLastExtension(key)) { - return FileSys->CollapseFullPath(relativePath, AutogenBuildDir); + if (basePrefixLength != 0) { + Base = Base.substr(basePrefixLength); + } } -/** - * @brief Tries to find the header file to the given file base path by - * appending different header extensions - * @return True on success - */ -bool cmQtAutoMocUic::BaseSettingsT::FindHeader( - std::string& header, std::string const& testBasePath) const +void cmQtAutoMocUic::ParseCacheT::FileT::Clear() { - for (std::string const& ext : HeaderExtensions) { - std::string testFilePath(testBasePath); - testFilePath.push_back('.'); - testFilePath += ext; - if (FileSys->FileExists(testFilePath)) { - header = testFilePath; - return true; + 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 }; } } - return false; + + // Insert new entry + return GetOrInsertT{ + Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true + }; } -bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const +cmQtAutoMocUic::ParseCacheT::ParseCacheT() = default; +cmQtAutoMocUic::ParseCacheT::~ParseCacheT() = default; + +void cmQtAutoMocUic::ParseCacheT::Clear() { - return (!Enabled || (SkipList.find(fileName) != SkipList.end())); + Map_.clear(); } -/** - * @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 cmQtAutoMocUic::MocSettingsT::FindMacro( - std::string const& content) const +bool cmQtAutoMocUic::ParseCacheT::ReadFromFile(std::string const& fileName) { - 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; + 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 std::string(); + 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 @@ -98,53 +217,13 @@ std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const return res; } -std::string cmQtAutoMocUic::MocSettingsT::FindIncludedFile( - std::string const& sourcePath, std::string const& includeString) const +cmQtAutoMocUic::UicSettingsT::UicSettingsT() { - // 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(); + RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" + "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); } -void cmQtAutoMocUic::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(); - } - } - } - } -} +cmQtAutoMocUic::UicSettingsT::~UicSettingsT() = default; bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const { @@ -176,498 +255,844 @@ void cmQtAutoMocUic::JobT::LogCommandError( bool cmQtAutoMocUic::JobT::RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, - std::vector<std::string> const& command) + std::vector<std::string> const& command, + std::string* infoMessage) { // Log command if (Log().Verbose()) { - std::string msg = "Running command:\n"; + 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, - Gen()->Base().AutogenBuildDir); + BaseConst().AutogenBuildDir); } void cmQtAutoMocUic::JobMocPredefsT::Process() { // (Re)generate moc_predefs.h on demand - bool generate(false); - bool fileExists(FileSys().FileExists(Gen()->Moc().PredefsFileAbs)); - if (!fileExists) { - if (Log().Verbose()) { - std::string reason = "Generating "; - reason += Quoted(Gen()->Moc().PredefsFileRel); - reason += " because it doesn't exist"; - Log().Info(GenT::MOC, reason); - } - generate = true; - } else if (Gen()->Moc().SettingsChanged) { - if (Log().Verbose()) { - std::string reason = "Generating "; - reason += Quoted(Gen()->Moc().PredefsFileRel); - reason += " because the settings changed."; - Log().Info(GenT::MOC, reason); - } - generate = true; + std::unique_ptr<std::string> reason; + if (Log().Verbose()) { + reason = cm::make_unique<std::string>(); + } + if (!Update(reason.get())) { + return; } - if (generate) { + std::string const& predefsFileRel = MocConst().PredefsFileRel; + std::string const& predefsFileAbs = MocConst().PredefsFileAbs; + { cmWorkerPool::ProcessResultT result; { // Compose command - std::vector<std::string> cmd = Gen()->Moc().PredefsCmd; + std::vector<std::string> cmd = MocConst().PredefsCmd; // Add includes - cmd.insert(cmd.end(), Gen()->Moc().Includes.begin(), - Gen()->Moc().Includes.end()); + cmd.insert(cmd.end(), MocConst().Includes.begin(), + MocConst().Includes.end()); // Add definitions - for (std::string const& def : Gen()->Moc().Definitions) { + for (std::string const& def : MocConst().Definitions) { cmd.push_back("-D" + def); } // Execute command - if (!RunProcess(GenT::MOC, result, cmd)) { - std::string emsg = "The content generation command for "; - emsg += Quoted(Gen()->Moc().PredefsFileRel); - emsg += " failed.\n"; - emsg += result.ErrorMessage; - LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); + 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 (!result.error()) { - if (!fileExists || - FileSys().FileDiffers(Gen()->Moc().PredefsFileAbs, result.StdOut)) { - if (FileSys().FileWrite(Gen()->Moc().PredefsFileAbs, result.StdOut)) { - // Success - } else { - std::string emsg = "Writing "; - emsg += Quoted(Gen()->Moc().PredefsFileRel); - emsg += " failed."; - LogFileError(GenT::MOC, Gen()->Moc().PredefsFileAbs, emsg); - } - } else { - // Touch to update the time stamp - if (Log().Verbose()) { - std::string msg = "Touching "; - msg += Quoted(Gen()->Moc().PredefsFileRel); - msg += "."; - Log().Info(GenT::MOC, msg); - } - FileSys().Touch(Gen()->Moc().PredefsFileAbs); + 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; + } } -void cmQtAutoMocUic::JobParseT::Process() +bool cmQtAutoMocUic::JobMocPredefsT::Update(std::string* reason) const { - if (AutoMoc && Header) { - // Don't parse header for moc if the file is included by a source already - if (Gen()->ParallelMocIncluded(FileName)) { - AutoMoc = false; + // 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; } - if (AutoMoc || AutoUic) { - std::string error; - MetaT meta; - if (FileSys().FileRead(meta.Content, FileName, &error)) { - if (!meta.Content.empty()) { - meta.FileDir = FileSys().SubDirPrefix(FileName); - meta.FileBase = FileSys().GetFilenameWithoutLastExtension(FileName); - - bool success = true; - if (AutoMoc) { - if (Header) { - success = ParseMocHeader(meta); - } else { - success = ParseMocSource(meta); - } - } - if (AutoUic && success) { - ParseUic(meta); + // 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 += "."; } - } else { - Log().WarningFile(GenT::GEN, FileName, "The source file is empty"); + return true; } - } else { - LogFileError(GenT::GEN, FileName, "Could not read the file: " + error); } } + + return false; } -bool cmQtAutoMocUic::JobParseT::ParseMocSource(MetaT const& meta) +bool cmQtAutoMocUic::JobParseT::ReadFile() { - struct JobPre + // 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 { - bool self; // source file is self - bool underscore; // "moc_" style include - std::string SourceFile; - std::string IncludeString; - }; + 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<IncludeKeyT>& container, + std::set<std::string> 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; + } - struct MocInclude + // Find dependency strings + std::set<std::string> 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 { - std::string Inc; // full include string - std::string Dir; // include string directory - std::string Base; // include string file base - }; + 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', ' '); + } + } +} - // Check if this source file contains a relevant macro - std::string const ownMacro = Gen()->Moc().FindMacro(meta.Content); +void cmQtAutoMocUic::JobParseT::MocIncludes() +{ + if (Content.find("moc") == std::string::npos) { + return; + } - // Extract moc includes from file - std::deque<MocInclude> mocIncsUsc; - std::deque<MocInclude> mocIncsDot; + std::set<std::string> underscore; + std::set<std::string> dot; { - if (meta.Content.find("moc") != std::string::npos) { - const char* contentChars = meta.Content.c_str(); - cmsys::RegularExpressionMatch match; - while (Gen()->Moc().RegExpInclude.find(contentChars, match)) { - std::string incString = match.match(2); - std::string incDir(FileSys().SubDirPrefix(incString)); - std::string incBase = - 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(); + 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_<BASE>.cpp + // Remove the moc_ part from the base name + underscore.emplace(std::move(incString)); + } else { + // <BASE>.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<std::string> 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; + } + } - // Check if there is anything to do - if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) { + // Add discovered header parse jobs + Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered); + // Add generate job after + Gen()->WorkerPool().EmplaceJob<JobGenerateT>(); +} + +bool cmQtAutoMocUic::JobEvaluateT::MocEvalHeader(SourceFileHandleT source) +{ + SourceFileT const& sourceFile = *source; + auto const& parseData = sourceFile.ParseData->Moc; + if (!source->Moc) { return true; } - bool ownDotMocIncluded = false; - bool ownMocUscIncluded = false; - std::deque<JobPre> jobs; + if (!parseData.Macro.empty()) { + // Create a new mapping + MappingHandleT handle = std::make_shared<MappingT>(); + handle->SourceFile = std::move(source); - // Process moc_<BASE>.cxx includes - for (const MocInclude& mocInc : mocIncsUsc) { - std::string const header = - MocFindIncludedHeader(meta.FileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (Gen()->Moc().skipped(header)) { - continue; + // 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; } - // 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; + } + } + 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); } - } else { + return false; + } + // The include might be handled differently in relaxed mode + if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() && + (incKey.Base == sourceBase)) { + // The <BASE>.cpp file includes a Qt macro but does not include the + // <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably + // be generated from <BASE>.cpp instead of <BASE>.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. { - std::string emsg = "The file includes the moc file "; - emsg += Quoted(mocInc.Inc); - emsg += ", but the header "; - emsg += Quoted(MocStringHeaders(mocInc.Base)); - emsg += " could not be found."; - LogFileError(GenT::MOC, FileName, emsg); + // 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 strict 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; } } - // Process <BASE>.moc includes - for (const MocInclude& mocInc : mocIncsDot) { - const bool ownMoc = (mocInc.Base == meta.FileBase); - if (Gen()->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(meta.FileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (Gen()->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 += Gen()->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"; - Log().WarningFile(GenT::MOC, FileName, emsg); - } else { - std::string emsg = "The file includes the moc file "; - emsg += Quoted(mocInc.Inc); - emsg += " instead of "; - emsg += Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += ".\nRunning moc on\n "; - emsg += Quoted(header); - emsg += "!\nBetter include "; - emsg += Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += " for compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - Log().WarningFile(GenT::MOC, FileName, emsg); - } - } - } else { - { - std::string emsg = "The file includes the moc file "; - emsg += Quoted(mocInc.Inc); - emsg += ", which seems to be the moc file from a different " - "source file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a " - "matching header "; - emsg += Quoted(MocStringHeaders(mocInc.Base)); - emsg += " could not be found."; - LogFileError(GenT::MOC, FileName, emsg); - } + // 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; } - } 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 += Gen()->Moc().MacrosString(); - emsg += " macro."; - Log().WarningFile(GenT::MOC, FileName, emsg); - } + // 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 strict mode.\n"; + msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"; + Log().WarningFile(GenT::MOC, sourceFile.FileName, msg); } 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."; - LogFileError(GenT::MOC, FileName, emsg); - } + 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 strict 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; } } - } - - 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 (Gen()->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; - } - } + } 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 <BASE>.moc include other than own in strict 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; } - // 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)"; - Log().WarningFile(GenT::MOC, FileName, emsg); - } - // Add own source job - jobs.emplace_back( - JobPre{ true, false, FileName, uscJobPre.IncludeString }); - } else { - // Otherwise always error out since it will not compile. - { - std::string emsg = "The file contains a "; - emsg += ownMacro; - emsg += " macro, but does not include "; - emsg += Quoted(meta.FileBase + ".moc"); - emsg += "!\nConsider to\n - add #include \""; - emsg += meta.FileBase; - emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; - LogFileError(GenT::MOC, FileName, emsg); + // 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 false; } } - // Convert pre jobs to actual jobs - for (JobPre& jobPre : jobs) { - cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>( - std::move(jobPre.SourceFile), FileName, std::move(jobPre.IncludeString)); - if (jobPre.self) { - // Read dependencies from this source - JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle); - Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends); - jobMoc.DependsValid = true; + 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; } - if (!Gen()->ParallelJobPushMoc(std::move(jobHandle))) { - return false; + } + // 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 true; + // Return without success + return SourceFileHandleT(); } -bool cmQtAutoMocUic::JobParseT::ParseMocHeader(MetaT const& meta) +cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindHeader( + std::string const& basePath) const { - bool success = true; - std::string const macroName = Gen()->Moc().FindMacro(meta.Content); - if (!macroName.empty()) { - cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>( - std::string(FileName), std::string(), std::string()); - // Read dependencies from this source - { - JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle); - Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends); - jobMoc.DependsValid = true; + 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<SourceFileT>(testPath); + res->FileTime = fileTime; + res->Moc = true; + } + return res; } - success = Gen()->ParallelJobPushMoc(std::move(jobHandle)); } - return success; + // Return without success + return SourceFileHandleT(); } -std::string cmQtAutoMocUic::JobParseT::MocStringHeaders( +std::string cmQtAutoMocUic::JobEvaluateT::MocMessageTestHeaders( std::string const& fileBase) const { - std::string res = fileBase; - res += ".{"; - res += cmJoin(Gen()->Base().HeaderExtensions, ","); - res += "}"; - return res; + 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(); } -std::string cmQtAutoMocUic::JobParseT::MocFindIncludedHeader( - std::string const& includerDir, std::string const& includeBase) +bool cmQtAutoMocUic::JobEvaluateT::MocRegisterIncluded( + std::string const& includeString, SourceFileHandleT includerFileHandle, + SourceFileHandleT sourceFileHandle, bool sourceIsHeader) const { - std::string header; - // Search in vicinity of the source - if (!Gen()->Base().FindHeader(header, includerDir + includeBase)) { - // Search in include directories - for (std::string const& path : Gen()->Moc().IncludePaths) { - std::string fullPath = path; - fullPath.push_back('/'); - fullPath += includeBase; - if (Gen()->Base().FindHeader(header, fullPath)) { - break; + // 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_<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"; + 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; } - // Sanitize - if (!header.empty()) { - header = FileSys().GetRealPath(header); + + // Create a new mapping + handle = std::make_shared<MappingT>(); + 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); + } } - return header; } -bool cmQtAutoMocUic::JobParseT::ParseUic(MetaT const& meta) +bool cmQtAutoMocUic::JobEvaluateT::UicEval(SourceFileMapT const& fileMap) { - bool success = true; - if (meta.Content.find("ui_") != std::string::npos) { - const char* contentChars = meta.Content.c_str(); - cmsys::RegularExpressionMatch match; - while (Gen()->Uic().RegExpInclude.find(contentChars, match)) { - if (!ParseUicInclude(meta, match.match(2))) { - success = false; - break; - } - contentChars += match.end(); + for (auto const& pair : fileMap) { + if (!UicEvalFile(pair.second)) { + return false; } } - return success; + return true; } -bool cmQtAutoMocUic::JobParseT::ParseUicInclude(MetaT const& meta, - std::string&& includeString) +bool cmQtAutoMocUic::JobEvaluateT::UicEvalFile( + SourceFileHandleT sourceFileHandle) { - bool success = false; - std::string uiInputFile = UicFindIncludedFile(meta, includeString); - if (!uiInputFile.empty()) { - if (!Gen()->Uic().skipped(uiInputFile)) { - cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobUicT>( - std::move(uiInputFile), FileName, std::move(includeString)); - success = Gen()->ParallelJobPushUic(std::move(jobHandle)); - } else { - // A skipped file is successful - success = true; + 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 success; + + return true; } -std::string cmQtAutoMocUic::JobParseT::UicFindIncludedFile( - MetaT const& meta, std::string const& includeString) +bool cmQtAutoMocUic::JobEvaluateT::UicRegisterMapping( + std::string const& includeString, SourceFileHandleT uiFileHandle, + SourceFileHandleT includerFileHandle) { - std::string res; - std::string searchFile = - FileSys().GetFilenameWithoutLastExtension(includeString).substr(3); - searchFile += ".ui"; + 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_<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"; + 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<MappingT>(); + 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::deque<std::string> testFiles; + std::vector<std::string> testFiles; { - std::string const searchPath = FileSys().SubDirPrefix(includeString); + auto& searchPaths = UicConst().SearchPaths; + testFiles.reserve((searchPaths.size() + 1) * 2); - 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); - } + 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 (!Gen()->Uic().SearchPaths.empty()) { - for (std::string const& sPath : Gen()->Uic().SearchPaths) { - testFiles.push_back((sPath + "/").append(searchFile)); + if (!searchPaths.empty()) { + for (std::string const& sPath : searchPaths) { + std::string path = sPath; + path += '/'; + path += searchFileName; + testFiles.emplace_back(std::move(path)); } - if (!searchPath.empty()) { - for (std::string const& sPath : Gen()->Uic().SearchPaths) { - testFiles.push_back((sPath + "/").append(searchFileFull)); + 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)); } } } @@ -675,243 +1100,189 @@ std::string cmQtAutoMocUic::JobParseT::UicFindIncludedFile( // Search for the .ui file! for (std::string const& testFile : testFiles) { - if (FileSys().FileExists(testFile)) { - res = FileSys().GetRealPath(testFile); - break; + 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<SourceFileT>(realPath); + handle->FileTime = fileTime; + } + return handle; } } // Log error - if (res.empty()) { - std::string emsg = "Could not find "; - emsg += Quoted(searchFile); - emsg += " in\n"; + { + 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) { - emsg += " "; - emsg += Quoted(testFile); - emsg += "\n"; + msg += " "; + msg += Quoted(testFile); + msg += '\n'; } - LogFileError(GenT::UIC, FileName, emsg); + LogFileError(GenT::UIC, sourceFile, msg); } - return res; + return SourceFileHandleT(); } -void cmQtAutoMocUic::JobPostParseT::Process() +void cmQtAutoMocUic::JobGenerateT::Process() { - if (Gen()->Moc().Enabled) { - // Add mocs compilations fence job + // 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<JobMocsCompilationT>(); } + + // 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<JobFinishT>(); } -void cmQtAutoMocUic::JobMocsCompilationT::Process() +bool cmQtAutoMocUic::JobGenerateT::MocGenerate(MappingHandleT const& mapping, + bool compFile) const { - // Compose mocs compilation file content - std::string content = - "// This file is autogenerated. Changes will be overwritten.\n"; - if (Gen()->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 = Gen()->Base().MultiConfig ? '<' : '"'; - char const send = Gen()->Base().MultiConfig ? '>' : '"'; - for (std::string const& mocfile : Gen()->MocAutoFiles()) { - content += "#include "; - content += sbeg; - content += mocfile; - content += send; - content += '\n'; - } + std::unique_ptr<std::string> reason; + if (Log().Verbose()) { + reason = cm::make_unique<std::string>(); } - - std::string const& compAbs = Gen()->Moc().CompFileAbs; - if (FileSys().FileDiffers(compAbs, content)) { - // Actually write mocs compilation file - if (Log().Verbose()) { - Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs); - } - if (!FileSys().FileWrite(compAbs, content)) { - LogFileError(GenT::MOC, compAbs, - "mocs compilation file writing failed."); + 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; } - } else if (Gen()->MocAutoFileUpdated()) { - // Only touch mocs compilation file - if (Log().Verbose()) { - Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs); + // Add moc job + Gen()->WorkerPool().EmplaceJob<JobMocT>(mapping, std::move(reason)); + // Check if a moc job for a mocs_compilation.cpp entry was generated + if (compFile) { + MocEval().CompUpdated = true; } - FileSys().Touch(compAbs); } + return true; } -void cmQtAutoMocUic::JobMocT::FindDependencies(std::string const& content) -{ - Gen()->Moc().FindDependencies(content, Depends); - DependsValid = true; -} - -void cmQtAutoMocUic::JobMocT::Process() +bool cmQtAutoMocUic::JobGenerateT::MocUpdate(MappingT const& mapping, + std::string* reason) const { - // Compute build file name - if (!IncludeString.empty()) { - BuildFile = Gen()->Base().AutogenIncludeDir; - BuildFile += '/'; - BuildFile += IncludeString; - } else { - // Relative build path - std::string relPath = FileSys().GetFilePathChecksum(SourceFile); - relPath += "/moc_"; - relPath += FileSys().GetFilenameWithoutLastExtension(SourceFile); - - // Register relative file path with duplication check - relPath = Gen()->ParallelMocAutoRegister(relPath); - - // Absolute build path - if (Gen()->Base().MultiConfig) { - BuildFile = Gen()->Base().AutogenIncludeDir; - BuildFile += '/'; - BuildFile += relPath; - } else { - BuildFile = Gen()->Base().AbsoluteBuildPath(relPath); + 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; } - if (UpdateRequired()) { - GenerateMoc(); - } -} - -bool cmQtAutoMocUic::JobMocT::UpdateRequired() -{ - bool const verbose = Log().Verbose(); - - // Test if the build file exists - if (!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"; - Log().Info(GenT::MOC, reason); + // 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 any setting changed - if (Gen()->Moc().SettingsChanged) { - if (verbose) { - std::string reason = "Generating "; - reason += Quoted(BuildFile); - reason += " from "; - reason += Quoted(SourceFile); - reason += " because the MOC settings changed"; - Log().Info(GenT::MOC, reason); + // 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 (!Gen()->Moc().PredefsFileAbs.empty()) { - bool isOlder = false; - { - std::string error; - isOlder = FileSys().FileIsOlderThan(BuildFile, - Gen()->Moc().PredefsFileAbs, &error); - if (!isOlder && !error.empty()) { - LogError(GenT::MOC, error); - return false; - } - } - if (isOlder) { - if (verbose) { - std::string reason = "Generating "; - reason += Quoted(BuildFile); - reason += " because it's older than: "; - reason += Quoted(Gen()->Moc().PredefsFileAbs); - Log().Info(GenT::MOC, reason); + 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 source file is newer - { - bool isOlder = false; - { - std::string error; - isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); - if (!isOlder && !error.empty()) { - LogError(GenT::MOC, error); - return false; - } - } - if (isOlder) { - if (verbose) { - std::string reason = "Generating "; - reason += Quoted(BuildFile); - reason += " because it's older than its source file "; - reason += Quoted(SourceFile); - Log().Info(GenT::MOC, reason); - } - 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 { - // Read dependencies on demand - if (!DependsValid) { - std::string content; - { - std::string error; - if (!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; - LogError(GenT::MOC, emsg); - return false; - } - } - FindDependencies(content); - } // Check dependency timestamps - std::string error; - std::string sourceDir = FileSys().SubDirPrefix(SourceFile); - for (std::string const& depFileRel : Depends) { - std::string depFileAbs = - Gen()->Moc().FindIncludedFile(sourceDir, depFileRel); - if (!depFileAbs.empty()) { - if (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); - Log().Info(GenT::MOC, reason); - } - return true; - } - if (!error.empty()) { - LogError(GenT::MOC, error); - return false; + 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); } - } else { - std::string message = "Could not find dependency file "; - message += Quoted(depFileRel); - Log().WarningFile(GenT::MOC, SourceFile, message); + return true; } } } @@ -919,198 +1290,254 @@ bool cmQtAutoMocUic::JobMocT::UpdateRequired() return false; } -void cmQtAutoMocUic::JobMocT::GenerateMoc() +std::pair<std::string, cmFileTime> +cmQtAutoMocUic::JobGenerateT::MocFindDependency( + std::string const& sourceDir, std::string const& includeString) const { - // Make sure the parent directory exists - if (!FileSys().MakeParentDirectory(BuildFile)) { - LogFileError(GenT::MOC, BuildFile, "Could not create parent directory."); - return; - } + typedef std::pair<std::string, cmFileTime> ResPair; + // Search in vicinity of the source { - // Compose moc command - std::vector<std::string> cmd; - cmd.push_back(Gen()->Moc().Executable); - // Add options - cmd.insert(cmd.end(), Gen()->Moc().AllOptions.begin(), - Gen()->Moc().AllOptions.end()); - // Add predefs include - if (!Gen()->Moc().PredefsFileAbs.empty()) { - cmd.emplace_back("--include"); - cmd.push_back(Gen()->Moc().PredefsFileAbs); - } - cmd.emplace_back("-o"); - cmd.push_back(BuildFile); - cmd.push_back(SourceFile); - - // Execute moc command - cmWorkerPool::ProcessResultT result; - if (RunProcess(GenT::MOC, result, cmd)) { - // Moc command success - // Print moc output - if (!result.StdOut.empty()) { - Log().Info(GenT::MOC, result.StdOut); - } - // Notify the generator that a not included file changed (on demand) - if (IncludeString.empty()) { - 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; - LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); - } - FileSys().FileRemove(BuildFile); + 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(); } -void cmQtAutoMocUic::JobUicT::Process() +bool cmQtAutoMocUic::JobGenerateT::UicGenerate( + MappingHandleT const& mapping) const { - // Compute build file name - BuildFile = Gen()->Base().AutogenIncludeDir; - BuildFile += '/'; - BuildFile += IncludeString; - - if (UpdateRequired()) { - GenerateUic(); + std::unique_ptr<std::string> reason; + if (Log().Verbose()) { + reason = cm::make_unique<std::string>(); } + 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<JobUicT>(mapping, std::move(reason)); + } + return true; } -bool cmQtAutoMocUic::JobUicT::UpdateRequired() +bool cmQtAutoMocUic::JobGenerateT::UicUpdate(MappingT const& mapping, + std::string* reason) const { - bool const verbose = Log().Verbose(); + std::string const& sourceFile = mapping.SourceFile->FileName; + std::string const& outputFile = mapping.OutputFile; // Test if the build file exists - if (!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"; - Log().Info(GenT::UIC, reason); + 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 (Gen()->Uic().SettingsChanged) { - if (verbose) { - std::string reason = "Generating "; - reason += Quoted(BuildFile); - reason += " from "; - reason += Quoted(SourceFile); - reason += " because the UIC settings changed"; - Log().Info(GenT::UIC, reason); + 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 - { - bool isOlder = false; - { - std::string error; - isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); - if (!isOlder && !error.empty()) { - LogError(GenT::UIC, error); - return false; - } + 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); } - if (isOlder) { - if (verbose) { - std::string reason = "Generating "; - reason += Quoted(BuildFile); - reason += " because it's older than its source file "; - reason += Quoted(SourceFile); - Log().Info(GenT::UIC, reason); - } - return true; + 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::JobUicT::GenerateUic() +void cmQtAutoMocUic::JobMocT::Process() { - // Make sure the parent directory exists - if (!FileSys().MakeParentDirectory(BuildFile)) { - LogFileError(GenT::UIC, BuildFile, "Could not create parent directory."); - return; + std::string const& sourceFile = Mapping->SourceFile->FileName; + std::string const& outputFile = Mapping->OutputFile; + + // Compose moc command + std::vector<std::string> cmd; + cmd.push_back(MocConst().Executable); + // Add options + cmd.insert(cmd.end(), MocConst().AllOptions.begin(), + MocConst().AllOptions.end()); + // 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<std::string> cmd; + cmd.push_back(UicConst().Executable); { - // Compose uic command - std::vector<std::string> cmd; - cmd.push_back(Gen()->Uic().Executable); - { - std::vector<std::string> allOpts = Gen()->Uic().TargetOptions; - auto optionIt = Gen()->Uic().Options.find(SourceFile); - if (optionIt != Gen()->Uic().Options.end()) { - UicMergeOptions(allOpts, optionIt->second, - (Gen()->Base().QtVersionMajor == 5)); - } - cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); + std::vector<std::string> allOpts = UicConst().TargetOptions; + auto optionIt = UicConst().Options.find(sourceFile); + if (optionIt != UicConst().Options.end()) { + UicMergeOptions(allOpts, optionIt->second, + (BaseConst().QtVersionMajor == 5)); } - cmd.emplace_back("-o"); - cmd.emplace_back(BuildFile); - cmd.emplace_back(SourceFile); + cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); + } + cmd.emplace_back("-o"); + cmd.emplace_back(outputFile); + cmd.emplace_back(sourceFile); - cmWorkerPool::ProcessResultT result; - if (RunProcess(GenT::UIC, result, cmd)) { - // Uic command success - // Print uic output - if (!result.StdOut.empty()) { - Log().Info(GenT::UIC, result.StdOut); - } - } else { - // Uic command failed - { - std::string emsg = "The uic process failed to compile\n "; - emsg += Quoted(SourceFile); - emsg += "\ninto\n "; - emsg += Quoted(BuildFile); - emsg += "\nincluded by\n "; - emsg += Quoted(IncluderFile); - emsg += ".\n"; - emsg += result.ErrorMessage; - LogCommandError(GenT::UIC, emsg, cmd, result.StdOut); - } - FileSys().FileRemove(BuildFile); + 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::JobFinishT::Process() +void cmQtAutoMocUic::JobMocsCompilationT::Process() { - Gen()->AbortSuccess(); + // 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."); + } + } } -cmQtAutoMocUic::cmQtAutoMocUic() - : Base_(&FileSys()) - , Moc_(&FileSys()) +void cmQtAutoMocUic::JobFinishT::Process() { - // Precompile regular expressions - Moc_.RegExpInclude.compile( - "(^|\n)[ \t]*#[ \t]*include[ \t]+" - "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); - Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" - "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); + Gen()->AbortSuccess(); } +cmQtAutoMocUic::cmQtAutoMocUic() = default; cmQtAutoMocUic::~cmQtAutoMocUic() = default; bool cmQtAutoMocUic::Init(cmMakefile* makefile) { - // -- Meta - Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); - // Utility lambdas auto InfoGet = [makefile](const char* key) { return makefile->GetSafeDefinition(key); @@ -1169,171 +1596,185 @@ bool cmQtAutoMocUic::Init(cmMakefile* makefile) 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())) { - Log().ErrorFile(GenT::GEN, InfoFile(), "File processing failed"); - return false; + return LogInfoError("File processing failed"); } // -- Meta - Log().RaiseVerbosity(InfoGet("AM_VERBOSITY")); - Base_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG"); + Logger_.RaiseVerbosity(InfoGet("AM_VERBOSITY")); + BaseConst_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG"); { - unsigned long num = Base_.NumThreads; + unsigned long num = 1; 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); } - WorkerPool_.SetThreadCount(Base_.NumThreads); + WorkerPool_.SetThreadCount(static_cast<unsigned int>(num)); } + BaseConst_.HeaderExtensions = + makefile->GetCMakeInstance()->GetHeaderExtensions(); // - 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 = + BaseConst_.IncludeProjectDirsBefore = InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); - Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); - if (Base_.AutogenBuildDir.empty()) { - Log().ErrorFile(GenT::GEN, InfoFile(), "Autogen build directory missing"); - return false; - } - // include directory - Base_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR"); - if (Base_.AutogenIncludeDir.empty()) { - Log().ErrorFile(GenT::GEN, InfoFile(), - "Autogen include directory missing"); - return false; - } - - // - Files + 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()) { - Log().ErrorFile(GenT::GEN, InfoFile(), "Settings file name missing"); - return false; + return LogInfoError("Settings file name missing."); } // - Qt environment { - unsigned long qtv = Base_.QtVersionMajor; + unsigned long qtv = BaseConst_.QtVersionMajor; if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(), &qtv)) { - Base_.QtVersionMajor = static_cast<unsigned int>(qtv); + BaseConst_.QtVersionMajor = static_cast<unsigned int>(qtv); } } // - Moc - Moc_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE"); - Moc_.Enabled = !Moc().Executable.empty(); - if (Moc().Enabled) { + 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")) { - Moc_.SkipList.insert(std::move(sfl)); + MocConst_.SkipList.insert(std::move(sfl)); } - Moc_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); - Moc_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); - Moc_.Options = InfoGetList("AM_MOC_OPTIONS"); - Moc_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); + 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")) { - Moc_.MacroFilters.emplace_back( + MocConst_.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"; + 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"); } - if (!error.empty()) { - error = ("AUTOMOC_DEPEND_FILTERS: " + error); - error += "\n"; - error += " Key: "; - error += Quoted(key); - error += "\n"; - error += " Exp: "; - error += Quoted(exp); - error += "\n"; + this->MocConst_.DependFilters.emplace_back(key, exp); + if (!this->MocConst_.DependFilters.back().Exp.is_valid()) { + return filterErr("Regular expression compiling failed"); } + return true; }; - 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); + 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<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS"); - if ((flts.size() % 2) == 0) { - for (std::vector<std::string>::iterator itC = flts.begin(), - itE = flts.end(); - itC != itE; itC += 2) { - pushFilter(*itC, *(itC + 1), error); - if (!error.empty()) { - break; - } - } - } else { - Log().ErrorFile( - GenT::MOC, InfoFile(), - "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); + std::vector<std::string> 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; } } - if (!error.empty()) { - Log().ErrorFile(GenT::MOC, InfoFile(), error); - return false; - } - } - Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); - // Install moc predefs job - if (!Moc().PredefsCmd.empty()) { - WorkerPool().EmplaceJob<JobMocPredefsT>(); } + MocConst_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); } // - Uic - Uic_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE"); - Uic_.Enabled = !Uic().Executable.empty(); - if (Uic().Enabled) { + 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")) { - Uic_.SkipList.insert(std::move(sfl)); + UicConst_.SkipList.insert(std::move(sfl)); } - Uic_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); - Uic_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS"); + UicConst_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); + UicConst_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS"); { - auto sources = InfoGetList("AM_UIC_OPTIONS_FILES"); - auto options = InfoGetLists("AM_UIC_OPTIONS_OPTIONS"); - // Compare list sizes - if (sources.size() != options.size()) { - std::ostringstream ost; - ost << "files/options lists sizes mismatch (" << sources.size() << "/" - << options.size() << ")"; - Log().ErrorFile(GenT::UIC, InfoFile(), ost.str()); + 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) { - Uic_.Options[*fit] = std::move(*oit); + UicConst_.Options[*fit] = std::move(*oit); ++fit; ++oit; } @@ -1341,92 +1782,121 @@ bool cmQtAutoMocUic::Init(cmMakefile* makefile) } // - Headers and sources - // Add sources { - auto addSource = [this](std::string&& src, bool moc, bool uic) { - WorkerPool().EmplaceJob<JobParseT>(std::move(src), moc, uic, false); - }; - for (std::string& src : InfoGetList("AM_SOURCES")) { - addSource(std::move(src), true, true); - } - if (Moc().Enabled) { - for (std::string& src : InfoGetList("AM_MOC_SOURCES")) { - addSource(std::move(src), true, false); + auto makeSource = + [&LogInfoError](std::string const& fileName, + std::string const& fileFlags) -> SourceFileHandleT { + if (fileFlags.size() != 2) { + LogInfoError("Invalid file flags string size"); + return SourceFileHandleT(); } - } - if (Uic().Enabled) { - for (std::string& src : InfoGetList("AM_UIC_SOURCES")) { - addSource(std::move(src), false, true); + cmFileTime fileTime; + if (!fileTime.Load(fileName)) { + LogInfoError("The source file " + cmQtAutoGen::Quoted(fileName) + + " does not exist."); + return SourceFileHandleT(); } - } - } - // Add Fence job - WorkerPool().EmplaceJob<JobFenceT>(); - // Add headers - { - auto addHeader = [this](std::string&& hdr, bool moc, bool uic) { - WorkerPool().EmplaceJob<JobParseT>(std::move(hdr), moc, uic, true); + SourceFileHandleT sfh = std::make_shared<SourceFileT>(fileName); + sfh->FileTime = fileTime; + sfh->Moc = (fileFlags[0] == 'M'); + sfh->Uic = (fileFlags[1] == 'U'); + return sfh; }; - for (std::string& hdr : InfoGetList("AM_HEADERS")) { - addHeader(std::move(hdr), true, true); - } - if (Moc().Enabled) { - for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) { - addHeader(std::move(hdr), true, false); + + // Headers + { + // Get file lists + const char *keyFiles = "AM_HEADERS", *keyFlags = "AM_HEADERS_FLAGS"; + std::vector<std::string> files = InfoGetList(keyFiles); + std::vector<std::string> flags = InfoGetList(keyFlags); + std::vector<std::string> 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)); } } - if (Uic().Enabled) { - for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) { - addHeader(std::move(hdr), false, true); + + // Sources + { + const char *keyFiles = "AM_SOURCES", *keyFlags = "AM_SOURCES_FLAGS"; + std::vector<std::string> files = InfoGetList(keyFiles); + std::vector<std::string> 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)); } } } - // Addpost parse fence job - WorkerPool().EmplaceJob<JobPostParseT>(); // Init derived information // ------------------------ - // Init file path checksum generator - FileSys().setupFilePathChecksum( - Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir, - Base().ProjectBinaryDir); - // Moc variables - if (Moc().Enabled) { + if (MocConst().Enabled) { // Mocs compilation file - Moc_.CompFileAbs = Base().AbsoluteBuildPath("mocs_compilation.cpp"); + MocConst_.CompFileAbs = AbsoluteBuildPath("mocs_compilation.cpp"); // Moc predefs file - if (!Moc_.PredefsCmd.empty()) { - Moc_.PredefsFileRel = "moc_predefs"; - if (Base_.MultiConfig) { - Moc_.PredefsFileRel += '_'; - Moc_.PredefsFileRel += InfoConfig(); + if (!MocConst_.PredefsCmd.empty()) { + MocConst_.PredefsFileRel = "moc_predefs"; + if (BaseConst_.MultiConfig) { + MocConst_.PredefsFileRel += '_'; + MocConst_.PredefsFileRel += InfoConfig(); } - Moc_.PredefsFileRel += ".h"; - Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel); + MocConst_.PredefsFileRel += ".h"; + MocConst_.PredefsFileAbs = AbsoluteBuildPath(MocConst().PredefsFileRel); } // Sort include directories on demand - if (Base().IncludeProjectDirsBefore) { + if (BaseConst().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()); + includes.insert(includes.end(), MocConst().IncludePaths.begin(), + MocConst().IncludePaths.end()); + MocConst_.IncludePaths.clear(); + MocConst_.IncludePaths.reserve(includes.size()); // Append project directories only { std::array<std::string const*, 2> const movePaths = { - { &Base().ProjectBinaryDir, &Base().ProjectSourceDir } + { &BaseConst().ProjectBinaryDir, &BaseConst().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); + MocConst_.IncludePaths.push_back(path); it = includes.erase(it); } else { ++it; @@ -1435,124 +1905,176 @@ bool cmQtAutoMocUic::Init(cmMakefile* makefile) } } // Append remaining directories - Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(), - includes.end()); + MocConst_.IncludePaths.insert(MocConst_.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); + 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<std::string> pathComponents; - FileSys().SplitPath(path, pathComponents); - std::string frameworkPath = FileSys().JoinPath( - pathComponents.begin(), pathComponents.end() - 2); - frameworkPaths.insert(frameworkPath); + cmSystemTools::SplitPath(path, pathComponents); + frameworkPaths.emplace(cmSystemTools::JoinPath( + pathComponents.begin(), pathComponents.end() - 2)); } } // Append framework includes for (std::string const& path : frameworkPaths) { - Moc_.Includes.emplace_back("-F"); - Moc_.Includes.push_back(path); + MocConst_.Includes.emplace_back("-F"); + MocConst_.Includes.push_back(path); } } // Setup single list with all options { // Add includes - Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(), - Moc().Includes.end()); + MocConst_.AllOptions.insert(MocConst_.AllOptions.end(), + MocConst().Includes.begin(), + MocConst().Includes.end()); // Add definitions - for (std::string const& def : Moc().Definitions) { - Moc_.AllOptions.push_back("-D" + def); + for (std::string const& def : MocConst().Definitions) { + MocConst_.AllOptions.push_back("-D" + def); } // Add options - Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(), - Moc().Options.end()); + MocConst_.AllOptions.insert(MocConst_.AllOptions.end(), + MocConst().Options.begin(), + MocConst().Options.end()); } } return true; } +template <class JOBTYPE> +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<JOBTYPE>(src.second); + } + } +} + +void cmQtAutoMocUic::InitJobs() +{ + // Add moc_predefs.h job + if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) { + WorkerPool().EmplaceJob<JobMocPredefsT>(); + } + // Add header parse jobs + CreateParseJobs<JobParseHeaderT>(BaseEval().Headers); + // Add source parse jobs + CreateParseJobs<JobParseSourceT>(BaseEval().Sources); + // Add evaluate job + WorkerPool().EmplaceJob<JobEvaluateT>(); +} + bool cmQtAutoMocUic::Process() { SettingsFileRead(); + ParseCacheRead(); if (!CreateDirectories()) { return false; } + InitJobs(); if (!WorkerPool_.Process(this)) { return false; } if (JobError_) { return false; } - return SettingsFileWrite(); + if (!ParseCacheWrite()) { + return false; + } + if (!SettingsFileWrite()) { + return false; + } + return true; } void cmQtAutoMocUic::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); + 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 (FileSys().FileRead(content, SettingsFile_)) { - if (Moc().Enabled) { + if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) { + if (MocConst().Enabled) { if (SettingsStringMoc_ != SettingsFind(content, "moc")) { - Moc_.SettingsChanged = true; + MocConst_.SettingsChanged = true; } } - if (Uic().Enabled) { + if (UicConst().Enabled) { if (SettingsStringUic_ != SettingsFind(content, "uic")) { - Uic_.SettingsChanged = true; + 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 (Moc().SettingsChanged || Uic().SettingsChanged) { - FileSys().FileRemove(SettingsFile_); + if (MocConst().SettingsChanged || UicConst().SettingsChanged) { + cmSystemTools::RemoveFile(SettingsFile_); } } else { // Settings file read failed - if (Moc().Enabled) { - Moc_.SettingsChanged = true; + if (MocConst().Enabled) { + MocConst_.SettingsChanged = true; } - if (Uic().Enabled) { - Uic_.SettingsChanged = true; + if (UicConst().Enabled) { + UicConst_.SettingsChanged = true; } } } @@ -1561,7 +2083,7 @@ void cmQtAutoMocUic::SettingsFileRead() bool cmQtAutoMocUic::SettingsFileWrite() { // Only write if any setting changed - if (Moc().SettingsChanged || Uic().SettingsChanged) { + if (MocConst().SettingsChanged || UicConst().SettingsChanged) { if (Log().Verbose()) { Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_)); } @@ -1582,11 +2104,54 @@ bool cmQtAutoMocUic::SettingsFileWrite() } // Write settings file std::string error; - if (!FileSys().FileWrite(SettingsFile_, content, &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 - FileSys().FileRemove(SettingsFile_); + 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; } } @@ -1596,16 +2161,14 @@ bool cmQtAutoMocUic::SettingsFileWrite() bool cmQtAutoMocUic::CreateDirectories() { // Create AUTOGEN include directory - if (!FileSys().MakeDirectory(Base().AutogenIncludeDir)) { - Log().ErrorFile(GenT::GEN, Base().AutogenIncludeDir, + if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) { + Log().ErrorFile(GenT::GEN, BaseConst().AutogenIncludeDir, "Could not create directory."); return false; } return true; } -// Private method that requires cmQtAutoMocUic::JobsMutex_ to be -// locked void cmQtAutoMocUic::Abort(bool error) { if (error) { @@ -1614,132 +2177,20 @@ void cmQtAutoMocUic::Abort(bool error) WorkerPool_.Abort(); } -bool cmQtAutoMocUic::ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle) -{ - JobMocT const& mocJob(static_cast<JobMocT&>(*jobHandle)); - // Do additional tests if this is an included moc job - if (!mocJob.IncludeString.empty()) { - std::lock_guard<std::mutex> guard(MocMetaMutex_); - // Register included moc file - MocIncludedFiles_.emplace(mocJob.SourceFile); - - // Check if the same moc file would be generated from a different - // source file. - auto const range = MocIncludes_.equal_range(mocJob.IncludeString); - for (auto it = range.first; it != range.second; ++it) { - if (it->second[0] == mocJob.SourceFile) { - // The output file already gets generated - return true; - } - { - // The output file already gets generated - from a different source - // file! - std::string error = "The two source files\n "; - error += Quoted(mocJob.IncluderFile); - error += " and\n "; - error += Quoted(it->second[1]); - 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(it->second[0]); - error += ".\nConsider to\n" - "- not include the \"moc_<NAME>.cpp\" file\n" - "- add a directory prefix to a \"<NAME>.moc\" include " - "(e.g \"sub/<NAME>.moc\")\n" - "- rename the source file(s)\n"; - Log().Error(GenT::MOC, error); - AbortError(); - return false; - } - } - - // We're still here so register this job - MocIncludes_.emplace_hint(range.first, mocJob.IncludeString, - std::array<std::string, 2>{ - { mocJob.SourceFile, mocJob.IncluderFile } }); - } - return WorkerPool_.PushJob(std::move(jobHandle)); -} - -bool cmQtAutoMocUic::ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle) -{ - const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle)); - { - std::lock_guard<std::mutex> guard(UicMetaMutex_); - // Check if the same uic file would be generated from a different - // source file. - auto const range = UicIncludes_.equal_range(uicJob.IncludeString); - for (auto it = range.first; it != range.second; ++it) { - if (it->second[0] == uicJob.SourceFile) { - // The output file already gets generated - return true; - } - { - // The output file already gets generated - from a different .ui - // file! - std::string error = "The two source files\n "; - error += Quoted(uicJob.IncluderFile); - error += " and\n "; - error += Quoted(it->second[1]); - 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(it->second[0]); - error += - ".\nConsider to\n" - "- add a directory prefix to a \"ui_<NAME>.h\" include " - "(e.g \"sub/ui_<NAME>.h\")\n" - "- rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" " - "include(s)\n"; - Log().Error(GenT::UIC, error); - AbortError(); - return false; - } - } - - // We're still here so register this job - UicIncludes_.emplace_hint(range.first, uicJob.IncludeString, - std::array<std::string, 2>{ - { uicJob.SourceFile, uicJob.IncluderFile } }); - } - return WorkerPool_.PushJob(std::move(jobHandle)); -} - -bool cmQtAutoMocUic::ParallelMocIncluded(std::string const& sourceFile) +std::string cmQtAutoMocUic::AbsoluteBuildPath( + std::string const& relativePath) const { - std::lock_guard<std::mutex> guard(MocMetaMutex_); - return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); + std::string res(BaseConst().AutogenBuildDir); + res += '/'; + res += relativePath; + return res; } -std::string cmQtAutoMocUic::ParallelMocAutoRegister( - std::string const& baseName) +std::string cmQtAutoMocUic::AbsoluteIncludePath( + std::string const& relativePath) const { - std::string res; - { - std::lock_guard<std::mutex> mocLock(MocMetaMutex_); - 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; - } - } - } - } + std::string res(BaseConst().AutogenIncludeDir); + res += '/'; + res += relativePath; return res; } |