From a008578deebfa71b38786281450e3d9cf84f5847 Mon Sep 17 00:00:00 2001 From: Sebastian Holtermann Date: Wed, 3 Jan 2018 16:59:40 +0100 Subject: Autogen: Process files concurrently in AUTOMOC and AUTOUIC This introduces concurrent thread processing in the `_autogen` target wich processes AUTOMOC and AUTOUIC. Source file parsing is distributed among the threads by using a job queue from which the threads pull new parse jobs. Each thread might start an independent ``moc`` or ``uic`` process. Altogether this roughly speeds up the AUTOMOC and AUTOUIC build process by the number of physical CPUs on the host system. The exact number of threads to start in the `_autogen` target is controlled by the new AUTOGEN_PARALLEL target property which is initialized by the new CMAKE_AUTOGEN_PARALLEL variable. If AUTOGEN_PARALLEL is empty or unset (which is the default) the thread count is set to the number of physical CPUs on the host system. The AUTOMOC/AUTOUIC generator and the AUTORCC generator are refactored to use a libuv loop internally. Closes #17422. --- Modules/AutoRccInfo.cmake.in | 4 - Modules/AutogenInfo.cmake.in | 1 + Source/cmQtAutoGen.cxx | 314 ++- Source/cmQtAutoGen.h | 55 +- Source/cmQtAutoGenerator.cxx | 669 +++++-- Source/cmQtAutoGenerator.h | 308 ++- Source/cmQtAutoGeneratorInitializer.cxx | 177 +- Source/cmQtAutoGeneratorInitializer.h | 11 +- Source/cmQtAutoGeneratorMocUic.cxx | 3153 +++++++++++++++++-------------- Source/cmQtAutoGeneratorMocUic.h | 510 +++-- Source/cmQtAutoGeneratorRcc.cxx | 719 ++++--- Source/cmQtAutoGeneratorRcc.h | 97 +- Source/cmTarget.cxx | 1 + 13 files changed, 3682 insertions(+), 2337 deletions(-) diff --git a/Modules/AutoRccInfo.cmake.in b/Modules/AutoRccInfo.cmake.in index 5457a6f..cbab4a7 100644 --- a/Modules/AutoRccInfo.cmake.in +++ b/Modules/AutoRccInfo.cmake.in @@ -1,10 +1,6 @@ # Meta set(ARCC_MULTI_CONFIG @_multi_config@) # Directories and files -set(ARCC_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/") -set(ARCC_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/") -set(ARCC_CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@/") -set(ARCC_CMAKE_CURRENT_BINARY_DIR "@CMAKE_CURRENT_BINARY_DIR@/") set(ARCC_BUILD_DIR @_build_dir@) # Qt environment set(ARCC_RCC_EXECUTABLE @_qt_rcc_executable@) diff --git a/Modules/AutogenInfo.cmake.in b/Modules/AutogenInfo.cmake.in index 9a4a06d..7320c0a 100644 --- a/Modules/AutogenInfo.cmake.in +++ b/Modules/AutogenInfo.cmake.in @@ -1,5 +1,6 @@ # Meta set(AM_MULTI_CONFIG @_multi_config@) +set(AM_PARALLEL @_parallel@) # Directories and files set(AM_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/") set(AM_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/") diff --git a/Source/cmQtAutoGen.cxx b/Source/cmQtAutoGen.cxx index 255a532..18ecbe7 100644 --- a/Source/cmQtAutoGen.cxx +++ b/Source/cmQtAutoGen.cxx @@ -2,16 +2,13 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGen.h" #include "cmAlgorithms.h" -#include "cmProcessOutput.h" #include "cmSystemTools.h" -#include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include #include #include -#include // - Static variables @@ -21,8 +18,8 @@ std::string const genNameUic = "AutoUic"; std::string const genNameRcc = "AutoRcc"; std::string const mcNameSingle = "SINGLE"; -std::string const mcNameWrap = "WRAP"; -std::string const mcNameFull = "FULL"; +std::string const mcNameWrapper = "WRAPPER"; +std::string const mcNameMulti = "MULTI"; // - Static functions @@ -80,203 +77,53 @@ void MergeOptions(std::vector& baseOpts, baseOpts.insert(baseOpts.end(), extraOpts.begin(), extraOpts.end()); } -/// @brief Reads the resource files list from from a .qrc file - Qt4 version -/// @return True if the .qrc file was successfully parsed -static bool RccListInputsQt4(std::string const& fileName, - std::vector& files, - std::string* errorMessage) -{ - bool allGood = true; - // Read qrc file content into string - std::string qrcContents; - { - cmsys::ifstream ifs(fileName.c_str()); - if (ifs) { - std::ostringstream osst; - osst << ifs.rdbuf(); - qrcContents = osst.str(); - } else { - if (errorMessage != nullptr) { - std::string& err = *errorMessage; - err = "rcc file not readable:\n "; - err += cmQtAutoGen::Quoted(fileName); - err += "\n"; - } - allGood = false; - } - } - if (allGood) { - // qrc file directory - std::string qrcDir(cmSystemTools::GetFilenamePath(fileName)); - if (!qrcDir.empty()) { - qrcDir += '/'; - } - - cmsys::RegularExpression fileMatchRegex("(]*>)"); - - size_t offset = 0; - while (fileMatchRegex.find(qrcContents.c_str() + offset)) { - std::string qrcEntry = fileMatchRegex.match(1); - offset += qrcEntry.size(); - { - fileReplaceRegex.find(qrcEntry); - std::string tag = fileReplaceRegex.match(1); - qrcEntry = qrcEntry.substr(tag.size()); - } - if (!cmSystemTools::FileIsFullPath(qrcEntry.c_str())) { - qrcEntry = qrcDir + qrcEntry; - } - files.push_back(qrcEntry); - } - } - return allGood; -} - -/// @brief Reads the resource files list from from a .qrc file - Qt5 version -/// @return True if the .qrc file was successfully parsed -static bool RccListInputsQt5(std::string const& rccCommand, - std::vector const& rccListOptions, - std::string const& fileName, - std::vector& files, - std::string* errorMessage) -{ - if (rccCommand.empty()) { - cmSystemTools::Error("rcc executable not available"); - return false; - } - - std::string const fileDir = cmSystemTools::GetFilenamePath(fileName); - std::string const fileNameName = cmSystemTools::GetFilenameName(fileName); - - // Run rcc list command - bool result = false; - int retVal = 0; - std::string rccStdOut; - std::string rccStdErr; - { - std::vector command; - command.push_back(rccCommand); - command.insert(command.end(), rccListOptions.begin(), - rccListOptions.end()); - command.push_back(fileNameName); - result = cmSystemTools::RunSingleCommand( - command, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(), - cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto); - } - if (!result || retVal) { - if (errorMessage != nullptr) { - std::string& err = *errorMessage; - err = "rcc list process failed for:\n "; - err += cmQtAutoGen::Quoted(fileName); - err += "\n"; - err += rccStdOut; - err += "\n"; - err += rccStdErr; - err += "\n"; - } - return false; - } - - // Lambda to strip CR characters - auto StripCR = [](std::string& line) { - std::string::size_type cr = line.find('\r'); - if (cr != std::string::npos) { - line = line.substr(0, cr); - } - }; - - // Parse rcc std output - { - std::istringstream ostr(rccStdOut); - std::string oline; - while (std::getline(ostr, oline)) { - StripCR(oline); - if (!oline.empty()) { - files.push_back(oline); - } - } - } - // Parse rcc error output - { - std::istringstream estr(rccStdErr); - std::string eline; - while (std::getline(estr, eline)) { - StripCR(eline); - if (cmHasLiteralPrefix(eline, "RCC: Error in")) { - static std::string searchString = "Cannot find file '"; - - std::string::size_type pos = eline.find(searchString); - if (pos == std::string::npos) { - if (errorMessage != nullptr) { - std::string& err = *errorMessage; - err = "rcc lists unparsable output:\n"; - err += cmQtAutoGen::Quoted(eline); - err += "\n"; - } - return false; - } - pos += searchString.length(); - std::string::size_type sz = eline.size() - pos - 1; - files.push_back(eline.substr(pos, sz)); - } - } - } - - // Convert relative paths to absolute paths - for (std::string& resFile : files) { - resFile = cmSystemTools::CollapseCombinedPath(fileDir, resFile); - } - - return true; -} - // - Class definitions -std::string const cmQtAutoGen::listSep = "<<>>"; +std::string const cmQtAutoGen::ListSep = "<<>>"; +unsigned int const cmQtAutoGen::ParallelMax = 64; -std::string const& cmQtAutoGen::GeneratorName(Generator type) +std::string const& cmQtAutoGen::GeneratorName(GeneratorT type) { switch (type) { - case Generator::GEN: + case GeneratorT::GEN: return genNameGen; - case Generator::MOC: + case GeneratorT::MOC: return genNameMoc; - case Generator::UIC: + case GeneratorT::UIC: return genNameUic; - case Generator::RCC: + case GeneratorT::RCC: return genNameRcc; } return genNameGen; } -std::string cmQtAutoGen::GeneratorNameUpper(Generator genType) +std::string cmQtAutoGen::GeneratorNameUpper(GeneratorT genType) { return cmSystemTools::UpperCase(cmQtAutoGen::GeneratorName(genType)); } -std::string const& cmQtAutoGen::MultiConfigName(MultiConfig config) +std::string const& cmQtAutoGen::MultiConfigName(MultiConfigT config) { switch (config) { - case MultiConfig::SINGLE: + case MultiConfigT::SINGLE: return mcNameSingle; - case MultiConfig::WRAP: - return mcNameWrap; - case MultiConfig::FULL: - return mcNameFull; + case MultiConfigT::WRAPPER: + return mcNameWrapper; + case MultiConfigT::MULTI: + return mcNameMulti; } - return mcNameWrap; + return mcNameWrapper; } -cmQtAutoGen::MultiConfig cmQtAutoGen::MultiConfigType(std::string const& name) +cmQtAutoGen::MultiConfigT cmQtAutoGen::MultiConfigType(std::string const& name) { if (name == mcNameSingle) { - return MultiConfig::SINGLE; + return MultiConfigT::SINGLE; } - if (name == mcNameFull) { - return MultiConfig::FULL; + if (name == mcNameMulti) { + return MultiConfigT::MULTI; } - return MultiConfig::WRAP; + return MultiConfigT::WRAPPER; } std::string cmQtAutoGen::Quoted(std::string const& text) @@ -294,6 +141,33 @@ std::string cmQtAutoGen::Quoted(std::string const& text) return res; } +std::string cmQtAutoGen::QuotedCommand(std::vector const& command) +{ + std::string res; + for (std::string const& item : command) { + if (!res.empty()) { + res.push_back(' '); + } + std::string const cesc = cmQtAutoGen::Quoted(item); + if (item.empty() || (cesc.size() > (item.size() + 2)) || + (cesc.find(' ') != std::string::npos)) { + res += cesc; + } else { + res += item; + } + } + return res; +} + +std::string cmQtAutoGen::SubDirPrefix(std::string const& filename) +{ + std::string res(cmSystemTools::GetFilenamePath(filename)); + if (!res.empty()) { + res += '/'; + } + return res; +} + std::string cmQtAutoGen::AppendFilenameSuffix(std::string const& filename, std::string const& suffix) { @@ -333,27 +207,79 @@ void cmQtAutoGen::RccMergeOptions(std::vector& baseOpts, MergeOptions(baseOpts, newOpts, valueOpts, isQt5); } -bool cmQtAutoGen::RccListInputs(std::string const& rccCommand, - std::vector const& rccListOptions, - std::string const& fileName, - std::vector& files, - std::string* errorMessage) +void cmQtAutoGen::RccListParseContent(std::string const& content, + std::vector& files) { - bool allGood = false; - if (cmSystemTools::FileExists(fileName.c_str())) { - if (rccListOptions.empty()) { - allGood = RccListInputsQt4(fileName, files, errorMessage); - } else { - allGood = RccListInputsQt5(rccCommand, rccListOptions, fileName, files, - errorMessage); + cmsys::RegularExpression fileMatchRegex("(]*>)"); + + const char* contentChars = content.c_str(); + while (fileMatchRegex.find(contentChars)) { + std::string const qrcEntry = fileMatchRegex.match(1); + contentChars += qrcEntry.size(); + { + fileReplaceRegex.find(qrcEntry); + std::string const tag = fileReplaceRegex.match(1); + files.push_back(qrcEntry.substr(tag.size())); } - } else { - if (errorMessage != nullptr) { - std::string& err = *errorMessage; - err = "rcc resource file does not exist:\n "; - err += cmQtAutoGen::Quoted(fileName); - err += "\n"; + } +} + +bool cmQtAutoGen::RccListParseOutput(std::string const& rccStdOut, + std::string const& rccStdErr, + std::vector& files, + std::string& error) +{ + // Lambda to strip CR characters + auto StripCR = [](std::string& line) { + std::string::size_type cr = line.find('\r'); + if (cr != std::string::npos) { + line = line.substr(0, cr); } + }; + + // Parse rcc std output + { + std::istringstream ostr(rccStdOut); + std::string oline; + while (std::getline(ostr, oline)) { + StripCR(oline); + if (!oline.empty()) { + files.push_back(oline); + } + } + } + // Parse rcc error output + { + std::istringstream estr(rccStdErr); + std::string eline; + while (std::getline(estr, eline)) { + StripCR(eline); + if (cmHasLiteralPrefix(eline, "RCC: Error in")) { + static std::string const searchString = "Cannot find file '"; + + std::string::size_type pos = eline.find(searchString); + if (pos == std::string::npos) { + error = "rcc lists unparsable output:\n"; + error += cmQtAutoGen::Quoted(eline); + error += "\n"; + return false; + } + pos += searchString.length(); + std::string::size_type sz = eline.size() - pos - 1; + files.push_back(eline.substr(pos, sz)); + } + } + } + + return true; +} + +void cmQtAutoGen::RccListConvertFullPath(std::string const& qrcFileDir, + std::vector& files) +{ + for (std::string& entry : files) { + std::string tmp = cmSystemTools::CollapseCombinedPath(qrcFileDir, entry); + entry = std::move(tmp); } - return allGood; } diff --git a/Source/cmQtAutoGen.h b/Source/cmQtAutoGen.h index e769e93..30ce0f6 100644 --- a/Source/cmQtAutoGen.h +++ b/Source/cmQtAutoGen.h @@ -9,14 +9,18 @@ #include /** \class cmQtAutoGen - * \brief Class used as namespace for QtAutogen related types and functions + * \brief Common base class for QtAutoGen classes */ class cmQtAutoGen { public: - static std::string const listSep; + /// @brief Nested lists separator + static std::string const ListSep; + /// @brief Maximum number of parallel threads/processes in a generator + static unsigned int const ParallelMax; - enum Generator + /// @brief AutoGen generator type + enum class GeneratorT { GEN, // General MOC, @@ -24,27 +28,33 @@ public: RCC }; - enum MultiConfig + /// @brief Multiconfiguration type + enum class MultiConfigT { - SINGLE, // Single configuration - WRAP, // Multi configuration using wrapper files - FULL // Full multi configuration using per config sources + SINGLE, // Single configuration + WRAPPER, // Multi configuration using wrapper files + MULTI // Multi configuration using per config sources }; public: /// @brief Returns the generator name - static std::string const& GeneratorName(Generator genType); + static std::string const& GeneratorName(GeneratorT genType); /// @brief Returns the generator name in upper case - static std::string GeneratorNameUpper(Generator genType); + static std::string GeneratorNameUpper(GeneratorT genType); /// @brief Returns the multi configuration name string - static std::string const& MultiConfigName(MultiConfig config); + static std::string const& MultiConfigName(MultiConfigT config); /// @brief Returns the multi configuration type - static MultiConfig MultiConfigType(std::string const& name); + static MultiConfigT MultiConfigType(std::string const& name); /// @brief Returns a the string escaped and enclosed in quotes static std::string Quoted(std::string const& text); + static std::string QuotedCommand(std::vector const& command); + + /// @brief Returns the parent directory of the file with a "/" suffix + static std::string SubDirPrefix(std::string const& filename); + /// @brief Appends the suffix to the filename before the last dot static std::string AppendFilenameSuffix(std::string const& filename, std::string const& suffix); @@ -59,14 +69,21 @@ public: std::vector const& newOpts, bool isQt5); - /// @brief Reads the resource files list from from a .qrc file - /// @arg fileName Must be the absolute path of the .qrc file - /// @return True if the rcc file was successfully read - static bool RccListInputs(std::string const& rccCommand, - std::vector const& rccListOptions, - std::string const& fileName, - std::vector& files, - std::string* errorMessage = nullptr); + /// @brief Parses the content of a qrc file + /// + /// Use when rcc does not support the "--list" option + static void RccListParseContent(std::string const& content, + std::vector& files); + + /// @brief Parses the output of the "rcc --list ..." command + static bool RccListParseOutput(std::string const& rccStdOut, + std::string const& rccStdErr, + std::vector& files, + std::string& error); + + /// @brief Converts relative qrc entry paths to full paths + static void RccListConvertFullPath(std::string const& qrcFileDir, + std::vector& files); }; #endif diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx index ee0ddbc..5b2b6d0 100644 --- a/Source/cmQtAutoGenerator.cxx +++ b/Source/cmQtAutoGenerator.cxx @@ -4,7 +4,6 @@ #include "cmQtAutoGenerator.h" #include "cmsys/FStream.hxx" -#include "cmsys/Terminal.h" #include "cmAlgorithms.h" #include "cmGlobalGenerator.h" @@ -14,108 +13,55 @@ #include "cmSystemTools.h" #include "cmake.h" -// -- Static functions - -static std::string HeadLine(std::string const& title) -{ - std::string head = title; - head += '\n'; - head.append(head.size() - 1, '-'); - head += '\n'; - return head; -} - -static std::string QuotedCommand(std::vector const& command) -{ - std::string res; - for (std::string const& item : command) { - if (!res.empty()) { - res.push_back(' '); - } - std::string const cesc = cmQtAutoGen::Quoted(item); - if (item.empty() || (cesc.size() > (item.size() + 2)) || - (cesc.find(' ') != std::string::npos)) { - res += cesc; - } else { - res += item; - } - } - return res; -} +#include // -- Class methods -cmQtAutoGenerator::cmQtAutoGenerator() - : Verbose(cmSystemTools::HasEnv("VERBOSE")) - , ColorOutput(true) +void cmQtAutoGenerator::Logger::SetVerbose(bool value) { - { - std::string colorEnv; - cmSystemTools::GetEnv("COLOR", colorEnv); - if (!colorEnv.empty()) { - this->ColorOutput = cmSystemTools::IsOn(colorEnv.c_str()); - } - } + Verbose_ = value; } -bool cmQtAutoGenerator::Run(std::string const& infoFile, - std::string const& config) +void cmQtAutoGenerator::Logger::SetColorOutput(bool value) { - // Info settings - this->InfoFile = infoFile; - cmSystemTools::ConvertToUnixSlashes(this->InfoFile); - this->InfoDir = cmSystemTools::GetFilenamePath(infoFile); - this->InfoConfig = config; - - cmake cm(cmake::RoleScript); - cm.SetHomeOutputDirectory(this->InfoDir); - cm.SetHomeDirectory(this->InfoDir); - cm.GetCurrentSnapshot().SetDefaultDefinitions(); - cmGlobalGenerator gg(&cm); - - cmStateSnapshot snapshot = cm.GetCurrentSnapshot(); - snapshot.GetDirectory().SetCurrentBinary(this->InfoDir); - snapshot.GetDirectory().SetCurrentSource(this->InfoDir); - - auto makefile = cm::make_unique(&gg, snapshot); - // The OLD/WARN behavior for policy CMP0053 caused a speed regression. - // https://gitlab.kitware.com/cmake/cmake/issues/17570 - makefile->SetPolicyVersion("3.9"); - gg.SetCurrentMakefile(makefile.get()); - - return this->Process(makefile.get()); + ColorOutput_ = value; } -void cmQtAutoGenerator::LogBold(std::string const& message) const +std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title) { - cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundBlue | - cmsysTerminal_Color_ForegroundBold, - message.c_str(), true, this->ColorOutput); + std::string head = title; + head += '\n'; + head.append(head.size() - 1, '-'); + head += '\n'; + return head; } -void cmQtAutoGenerator::LogInfo(cmQtAutoGen::Generator genType, - std::string const& message) const +void cmQtAutoGenerator::Logger::Info(GeneratorT genType, + std::string const& message) { - std::string msg = cmQtAutoGen::GeneratorName(genType); + std::string msg = GeneratorName(genType); msg += ": "; msg += message; if (msg.back() != '\n') { msg.push_back('\n'); } - cmSystemTools::Stdout(msg.c_str(), msg.size()); + { + std::lock_guard lock(Mutex_); + cmSystemTools::Stdout(msg.c_str(), msg.size()); + } } -void cmQtAutoGenerator::LogWarning(cmQtAutoGen::Generator genType, - std::string const& message) const +void cmQtAutoGenerator::Logger::Warning(GeneratorT genType, + std::string const& message) { - std::string msg = cmQtAutoGen::GeneratorName(genType); - msg += " warning:"; + std::string msg; if (message.find('\n') == std::string::npos) { // Single line message - msg.push_back(' '); + msg += GeneratorName(genType); + msg += " warning: "; } else { // Multi line message - msg.push_back('\n'); + msg += HeadLine(GeneratorName(genType) + " warning"); } // Message msg += message; @@ -123,55 +69,60 @@ void cmQtAutoGenerator::LogWarning(cmQtAutoGen::Generator genType, msg.push_back('\n'); } msg.push_back('\n'); - cmSystemTools::Stdout(msg.c_str(), msg.size()); + { + std::lock_guard lock(Mutex_); + cmSystemTools::Stdout(msg.c_str(), msg.size()); + } } -void cmQtAutoGenerator::LogFileWarning(cmQtAutoGen::Generator genType, - std::string const& filename, - std::string const& message) const +void cmQtAutoGenerator::Logger::WarningFile(GeneratorT genType, + std::string const& filename, + std::string const& message) { std::string msg = " "; - msg += cmQtAutoGen::Quoted(filename); + msg += Quoted(filename); msg.push_back('\n'); // Message msg += message; - this->LogWarning(genType, msg); + Warning(genType, msg); } -void cmQtAutoGenerator::LogError(cmQtAutoGen::Generator genType, - std::string const& message) const +void cmQtAutoGenerator::Logger::Error(GeneratorT genType, + std::string const& message) { std::string msg; - msg.push_back('\n'); - msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " error"); + msg += HeadLine(GeneratorName(genType) + " error"); // Message msg += message; if (msg.back() != '\n') { msg.push_back('\n'); } msg.push_back('\n'); - cmSystemTools::Stderr(msg.c_str(), msg.size()); + { + std::lock_guard lock(Mutex_); + cmSystemTools::Stderr(msg.c_str(), msg.size()); + } } -void cmQtAutoGenerator::LogFileError(cmQtAutoGen::Generator genType, - std::string const& filename, - std::string const& message) const +void cmQtAutoGenerator::Logger::ErrorFile(GeneratorT genType, + std::string const& filename, + std::string const& message) { std::string emsg = " "; - emsg += cmQtAutoGen::Quoted(filename); + emsg += Quoted(filename); emsg += '\n'; // Message emsg += message; - this->LogError(genType, emsg); + Error(genType, emsg); } -void cmQtAutoGenerator::LogCommandError( - cmQtAutoGen::Generator genType, std::string const& message, - std::vector const& command, std::string const& output) const +void cmQtAutoGenerator::Logger::ErrorCommand( + GeneratorT genType, std::string const& message, + std::vector const& command, std::string const& output) { std::string msg; msg.push_back('\n'); - msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " subprocess error"); + msg += HeadLine(GeneratorName(genType) + " subprocess error"); msg += message; if (msg.back() != '\n') { msg.push_back('\n'); @@ -189,135 +140,495 @@ void cmQtAutoGenerator::LogCommandError( msg.push_back('\n'); } msg.push_back('\n'); - cmSystemTools::Stderr(msg.c_str(), msg.size()); + { + std::lock_guard lock(Mutex_); + cmSystemTools::Stderr(msg.c_str(), msg.size()); + } } -/** - * @brief Generates the parent directory of the given file on demand - * @return True on success - */ -bool cmQtAutoGenerator::MakeParentDirectory(cmQtAutoGen::Generator genType, - std::string const& filename) const +std::string cmQtAutoGenerator::FileSystem::RealPath( + std::string const& filename) { - bool success = true; - std::string const dirName = cmSystemTools::GetFilenamePath(filename); - if (!dirName.empty()) { - if (!cmSystemTools::MakeDirectory(dirName)) { - this->LogFileError(genType, filename, - "Could not create parent directory"); - success = false; - } - } - return success; + std::lock_guard lock(Mutex_); + return cmSystemTools::GetRealPath(filename); +} + +bool cmQtAutoGenerator::FileSystem::FileExists(std::string const& filename) +{ + std::lock_guard lock(Mutex_); + return cmSystemTools::FileExists(filename); } -/** - * @brief Tests if buildFile is older than sourceFile - * @return True if buildFile is older than sourceFile. - * False may indicate an error. - */ -bool cmQtAutoGenerator::FileIsOlderThan(std::string const& buildFile, - std::string const& sourceFile, - std::string* error) +bool cmQtAutoGenerator::FileSystem::FileIsOlderThan( + std::string const& buildFile, std::string const& sourceFile, + std::string* error) { + bool res(false); int result = 0; - if (cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result)) { - return (result < 0); + { + std::lock_guard lock(Mutex_); + res = cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result); } - if (error != nullptr) { - error->append( - "File modification time comparison failed for the files\n "); - error->append(cmQtAutoGen::Quoted(buildFile)); - error->append("\nand\n "); - error->append(cmQtAutoGen::Quoted(sourceFile)); + if (res) { + res = (result < 0); + } else { + if (error != nullptr) { + error->append( + "File modification time comparison failed for the files\n "); + error->append(Quoted(buildFile)); + error->append("\nand\n "); + error->append(Quoted(sourceFile)); + } } - return false; + return res; } -bool cmQtAutoGenerator::FileRead(std::string& content, - std::string const& filename, - std::string* error) +bool cmQtAutoGenerator::FileSystem::FileRead(std::string& content, + std::string const& filename, + std::string* error) { bool success = false; - if (cmSystemTools::FileExists(filename)) { - std::size_t const length = cmSystemTools::FileLength(filename); - cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary)); - if (ifs) { - content.resize(length); - ifs.read(&content.front(), content.size()); + { + std::lock_guard lock(Mutex_); + if (cmSystemTools::FileExists(filename)) { + std::size_t const length = cmSystemTools::FileLength(filename); + cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary)); if (ifs) { - success = true; - } else { - content.clear(); - if (error != nullptr) { - error->append("Reading from the file failed."); + content.resize(length); + ifs.read(&content.front(), content.size()); + if (ifs) { + success = true; + } else { + content.clear(); + if (error != nullptr) { + error->append("Reading from the file failed."); + } } + } else if (error != nullptr) { + error->append("Opening the file for reading failed."); } } else if (error != nullptr) { - error->append("Opening the file for reading failed."); + error->append("The file does not exist."); } - } else if (error != nullptr) { - error->append("The file does not exist."); } return success; } -bool cmQtAutoGenerator::FileWrite(cmQtAutoGen::Generator genType, - std::string const& filename, - std::string const& content) +bool cmQtAutoGenerator::FileSystem::FileRead(GeneratorT genType, + std::string& content, + std::string const& filename) { std::string error; + if (!FileRead(content, filename, &error)) { + Log()->ErrorFile(genType, filename, error); + return false; + } + return true; +} + +bool cmQtAutoGenerator::FileSystem::FileWrite(std::string const& filename, + std::string const& content, + std::string* error) +{ + bool success = false; // Make sure the parent directory exists - if (this->MakeParentDirectory(genType, filename)) { + if (MakeParentDirectory(filename)) { + std::lock_guard lock(Mutex_); cmsys::ofstream outfile; outfile.open(filename.c_str(), (std::ios::out | std::ios::binary | std::ios::trunc)); if (outfile) { outfile << content; // Check for write errors - if (!outfile.good()) { - error = "File writing failed"; + if (outfile.good()) { + success = true; + } else { + if (error != nullptr) { + error->assign("File writing failed"); + } } } else { - error = "Opening file for writing failed"; + if (error != nullptr) { + error->assign("Opening file for writing failed"); + } + } + } else { + if (error != nullptr) { + error->assign("Could not create parent directory"); } } - if (!error.empty()) { - this->LogFileError(genType, filename, error); + return success; +} + +bool cmQtAutoGenerator::FileSystem::FileWrite(GeneratorT genType, + std::string const& filename, + std::string const& content) +{ + std::string error; + if (!FileWrite(filename, content, &error)) { + Log()->ErrorFile(genType, filename, error); return false; } return true; } -bool cmQtAutoGenerator::FileDiffers(std::string const& filename, - std::string const& content) +bool cmQtAutoGenerator::FileSystem::FileDiffers(std::string const& filename, + std::string const& content) { bool differs = true; { std::string oldContents; - if (this->FileRead(oldContents, filename)) { + if (FileRead(oldContents, filename)) { differs = (oldContents != content); } } return differs; } -/** - * @brief Runs a command and returns true on success - * @return True on success - */ -bool cmQtAutoGenerator::RunCommand(std::vector const& command, - std::string& output) const -{ - // Log command - if (this->Verbose) { - std::string qcmd = QuotedCommand(command); - qcmd.push_back('\n'); - cmSystemTools::Stdout(qcmd.c_str(), qcmd.size()); - } - // Execute command - int retVal = 0; - bool res = cmSystemTools::RunSingleCommand( - command, &output, &output, &retVal, nullptr, cmSystemTools::OUTPUT_NONE); - return (res && (retVal == 0)); +bool cmQtAutoGenerator::FileSystem::FileRemove(std::string const& filename) +{ + std::lock_guard lock(Mutex_); + return cmSystemTools::RemoveFile(filename); +} + +bool cmQtAutoGenerator::FileSystem::Touch(std::string const& filename) +{ + std::lock_guard lock(Mutex_); + return cmSystemTools::Touch(filename, false); +} + +bool cmQtAutoGenerator::FileSystem::MakeDirectory(std::string const& dirname) +{ + std::lock_guard lock(Mutex_); + return cmSystemTools::MakeDirectory(dirname); +} + +bool cmQtAutoGenerator::FileSystem::MakeDirectory(GeneratorT genType, + std::string const& dirname) +{ + if (!MakeDirectory(dirname)) { + Log()->ErrorFile(genType, dirname, "Could not create directory"); + return false; + } + return true; +} + +bool cmQtAutoGenerator::FileSystem::MakeParentDirectory( + std::string const& filename) +{ + bool success = true; + std::string const dirName = cmSystemTools::GetFilenamePath(filename); + if (!dirName.empty()) { + success = MakeDirectory(dirName); + } + return success; +} + +bool cmQtAutoGenerator::FileSystem::MakeParentDirectory( + GeneratorT genType, std::string const& filename) +{ + if (!MakeParentDirectory(filename)) { + Log()->ErrorFile(genType, filename, "Could not create parent directory"); + return false; + } + return true; +} + +int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop, + ReadOnlyProcessT* process) +{ + Process_ = process; + Target_ = nullptr; + return UVPipe_.init(*uv_loop, 0, this); +} + +int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target) +{ + Target_ = target; + return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData); +} + +void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset() +{ + Process_ = nullptr; + Target_ = nullptr; + UVPipe_.reset(); + Buffer_.clear(); + Buffer_.shrink_to_fit(); +} + +void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle, + size_t suggestedSize, + uv_buf_t* buf) +{ + auto& pipe = *reinterpret_cast(handle->data); + pipe.Buffer_.resize(suggestedSize); + buf->base = &pipe.Buffer_.front(); + buf->len = pipe.Buffer_.size(); +} + +void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream, + ssize_t nread, + const uv_buf_t* buf) +{ + auto& pipe = *reinterpret_cast(stream->data); + if (nread > 0) { + // Append data to merged output + if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) { + pipe.Target_->append(buf->base, nread); + } + } else if (nread < 0) { + // EOF or error + auto* proc = pipe.Process_; + // Check it this an unusual error + if (nread != UV_EOF) { + if (!proc->Result()->error()) { + proc->Result()->ErrorMessage = + "libuv reading from pipe failed with error code "; + proc->Result()->ErrorMessage += std::to_string(nread); + } + } + // Clear libuv pipe handle and try to finish + pipe.reset(); + proc->UVTryFinish(); + } +} + +void cmQtAutoGenerator::ProcessResultT::reset() +{ + ExitStatus = 0; + TermSignal = 0; + if (!StdOut.empty()) { + StdOut.clear(); + StdOut.shrink_to_fit(); + } + if (!StdErr.empty()) { + StdErr.clear(); + StdErr.shrink_to_fit(); + } + if (!ErrorMessage.empty()) { + ErrorMessage.clear(); + ErrorMessage.shrink_to_fit(); + } +} + +void cmQtAutoGenerator::ReadOnlyProcessT::setup( + ProcessResultT* result, bool mergedOutput, + std::vector const& command, std::string const& workingDirectory) +{ + Setup_.WorkingDirectory = workingDirectory; + Setup_.Command = command; + Setup_.Result = result; + Setup_.MergedOutput = mergedOutput; +} + +bool cmQtAutoGenerator::ReadOnlyProcessT::start( + uv_loop_t* uv_loop, std::function&& finishedCallback) +{ + if (IsStarted() || (Result() == nullptr)) { + return false; + } + + // Reset result before the start + Result()->reset(); + + // Fill command string pointers + if (!Setup().Command.empty()) { + CommandPtr_.reserve(Setup().Command.size() + 1); + for (std::string const& arg : Setup().Command) { + CommandPtr_.push_back(arg.c_str()); + } + CommandPtr_.push_back(nullptr); + } else { + Result()->ErrorMessage = "Empty command"; + } + + if (!Result()->error()) { + if (UVPipeOut_.init(uv_loop, this) != 0) { + Result()->ErrorMessage = "libuv stdout pipe initialization failed"; + } + } + if (!Result()->error()) { + if (UVPipeErr_.init(uv_loop, this) != 0) { + Result()->ErrorMessage = "libuv stderr pipe initialization failed"; + } + } + if (!Result()->error()) { + // -- Setup process stdio options + // stdin + UVOptionsStdIO_[0].flags = UV_IGNORE; + UVOptionsStdIO_[0].data.stream = nullptr; + // stdout + UVOptionsStdIO_[1].flags = + static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream(); + // stderr + UVOptionsStdIO_[2].flags = + static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream(); + + // -- Setup process options + std::fill_n(reinterpret_cast(&UVOptions_), sizeof(UVOptions_), 0); + UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit; + UVOptions_.file = CommandPtr_[0]; + UVOptions_.args = const_cast(&CommandPtr_.front()); + UVOptions_.cwd = Setup_.WorkingDirectory.c_str(); + UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE; + UVOptions_.stdio_count = static_cast(UVOptionsStdIO_.size()); + UVOptions_.stdio = &UVOptionsStdIO_.front(); + + // -- Spawn process + if (UVProcess_.spawn(*uv_loop, UVOptions_, this) != 0) { + Result()->ErrorMessage = "libuv process spawn failed"; + } + } + // -- Start reading from stdio streams + if (!Result()->error()) { + if (UVPipeOut_.startRead(&Result()->StdOut) != 0) { + Result()->ErrorMessage = "libuv start reading from stdout pipe failed"; + } + } + if (!Result()->error()) { + if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut + : &Result()->StdErr) != 0) { + Result()->ErrorMessage = "libuv start reading from stderr pipe failed"; + } + } + + if (!Result()->error()) { + IsStarted_ = true; + FinishedCallback_ = std::move(finishedCallback); + } else { + // Clear libuv handles and finish + UVProcess_.reset(); + UVPipeOut_.reset(); + UVPipeErr_.reset(); + CommandPtr_.clear(); + } + + return IsStarted(); +} + +void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle, + int64_t exitStatus, + int termSignal) +{ + auto& proc = *reinterpret_cast(handle->data); + if (proc.IsStarted() && !proc.IsFinished()) { + // Set error message on demand + proc.Result()->ExitStatus = exitStatus; + proc.Result()->TermSignal = termSignal; + if (!proc.Result()->error()) { + if (termSignal != 0) { + proc.Result()->ErrorMessage = "Process was terminated by signal "; + proc.Result()->ErrorMessage += + std::to_string(proc.Result()->TermSignal); + } else if (exitStatus != 0) { + proc.Result()->ErrorMessage = "Process failed with return value "; + proc.Result()->ErrorMessage += + std::to_string(proc.Result()->ExitStatus); + } + } + + // Reset process handle and try to finish + proc.UVProcess_.reset(); + proc.UVTryFinish(); + } +} + +void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish() +{ + // There still might be data in the pipes after the process has finished. + // Therefore check if the process is finished AND all pipes are closed before + // signaling the worker thread to continue. + if (UVProcess_.get() == nullptr) { + if (UVPipeOut_.uv_pipe() == nullptr) { + if (UVPipeErr_.uv_pipe() == nullptr) { + IsFinished_ = true; + FinishedCallback_(); + } + } + } +} + +cmQtAutoGenerator::cmQtAutoGenerator() + : FileSys_(&Logger_) +{ + // Initialize logger + Logger_.SetVerbose(cmSystemTools::HasEnv("VERBOSE")); + { + std::string colorEnv; + cmSystemTools::GetEnv("COLOR", colorEnv); + if (!colorEnv.empty()) { + Logger_.SetColorOutput(cmSystemTools::IsOn(colorEnv.c_str())); + } else { + Logger_.SetColorOutput(true); + } + } + + // Initialize libuv loop + uv_disable_stdio_inheritance(); +#ifdef CMAKE_UV_SIGNAL_HACK + UVHackRAII_ = cm::make_unique(); +#endif + UVLoop_ = cm::make_unique(); + uv_loop_init(UVLoop()); +} + +cmQtAutoGenerator::~cmQtAutoGenerator() +{ + // Close libuv loop + uv_loop_close(UVLoop()); +} + +bool cmQtAutoGenerator::Run(std::string const& infoFile, + std::string const& config) +{ + // Info settings + InfoFile_ = infoFile; + cmSystemTools::ConvertToUnixSlashes(InfoFile_); + InfoDir_ = cmSystemTools::GetFilenamePath(infoFile); + InfoConfig_ = config; + + bool success = false; + { + cmake cm(cmake::RoleScript); + cm.SetHomeOutputDirectory(InfoDir()); + cm.SetHomeDirectory(InfoDir()); + cm.GetCurrentSnapshot().SetDefaultDefinitions(); + cmGlobalGenerator gg(&cm); + + cmStateSnapshot snapshot = cm.GetCurrentSnapshot(); + snapshot.GetDirectory().SetCurrentBinary(InfoDir()); + snapshot.GetDirectory().SetCurrentSource(InfoDir()); + + auto makefile = cm::make_unique(&gg, snapshot); + // The OLD/WARN behavior for policy CMP0053 caused a speed regression. + // https://gitlab.kitware.com/cmake/cmake/issues/17570 + makefile->SetPolicyVersion("3.9"); + gg.SetCurrentMakefile(makefile.get()); + success = this->Init(makefile.get()); + } + if (success) { + success = this->Process(); + } + return success; +} + +std::string cmQtAutoGenerator::SettingsFind(std::string const& content, + const char* key) +{ + std::string prefix(key); + prefix += ':'; + std::string::size_type pos = content.find(prefix); + if (pos != std::string::npos) { + pos += prefix.size(); + if (pos < content.size()) { + std::string::size_type posE = content.find('\n', pos); + if ((posE != std::string::npos) && (posE != pos)) { + return content.substr(pos, posE - pos); + } + } + } + return std::string(); } diff --git a/Source/cmQtAutoGenerator.h b/Source/cmQtAutoGenerator.h index 285340d..6b35234 100644 --- a/Source/cmQtAutoGenerator.h +++ b/Source/cmQtAutoGenerator.h @@ -6,71 +6,283 @@ #include "cmConfigure.h" // IWYU pragma: keep #include "cmQtAutoGen.h" +#include "cmUVHandlePtr.h" +#include "cm_uv.h" +#include +#include +#include +#include +#include #include #include class cmMakefile; -class cmQtAutoGenerator +/// @brief Base class for QtAutoGen gernerators +class cmQtAutoGenerator : public cmQtAutoGen { CM_DISABLE_COPY(cmQtAutoGenerator) public: + // -- Types + + /// @brief Thread safe logging + class Logger + { + public: + // -- Verbosity + bool Verbose() const { return this->Verbose_; } + void SetVerbose(bool value); + bool ColorOutput() const { return this->ColorOutput_; } + void SetColorOutput(bool value); + // -- Log info + void Info(GeneratorT genType, std::string const& message); + // -- Log warning + void Warning(GeneratorT genType, std::string const& message); + void WarningFile(GeneratorT genType, std::string const& filename, + std::string const& message); + // -- Log error + void Error(GeneratorT genType, std::string const& message); + void ErrorFile(GeneratorT genType, std::string const& filename, + std::string const& message); + void ErrorCommand(GeneratorT genType, std::string const& message, + std::vector const& command, + std::string const& output); + + private: + static std::string HeadLine(std::string const& title); + + private: + std::mutex Mutex_; + bool volatile Verbose_ = false; + bool volatile ColorOutput_ = false; + }; + + /// @brief Thread safe file system interface + class FileSystem + { + public: + FileSystem(Logger* log) + : Log_(log) + { + } + + Logger* Log() const { return Log_; } + std::string RealPath(std::string const& filename); + bool FileExists(std::string const& filename); + bool FileIsOlderThan(std::string const& buildFile, + std::string const& sourceFile, + std::string* error = nullptr); + + bool FileRead(std::string& content, std::string const& filename, + std::string* error = nullptr); + /// @brief Error logging version + bool FileRead(GeneratorT genType, std::string& content, + std::string const& filename); + + bool FileWrite(std::string const& filename, std::string const& content, + std::string* error = nullptr); + /// @brief Error logging version + bool FileWrite(GeneratorT genType, std::string const& filename, + std::string const& content); + + bool FileDiffers(std::string const& filename, std::string const& content); + + bool FileRemove(std::string const& filename); + bool Touch(std::string const& filename); + + bool MakeDirectory(std::string const& dirname); + /// @brief Error logging version + bool MakeDirectory(GeneratorT genType, std::string const& dirname); + + bool MakeParentDirectory(std::string const& filename); + /// @brief Error logging version + bool MakeParentDirectory(GeneratorT genType, std::string const& filename); + + private: + std::mutex Mutex_; + Logger* Log_; + }; + + /// @brief Return value and output of an external process + struct ProcessResultT + { + void reset(); + bool error() const + { + return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty(); + } + + std::int64_t ExitStatus = 0; + int TermSignal = 0; + std::string StdOut; + std::string StdErr; + std::string ErrorMessage; + }; + + /// @brief External process management class + struct ReadOnlyProcessT + { + // -- Types + + /// @brief libuv pipe buffer class + class PipeT + { + public: + int init(uv_loop_t* uv_loop, ReadOnlyProcessT* process); + int startRead(std::string* target); + void reset(); + + // -- Libuv casts + uv_pipe_t* uv_pipe() { return UVPipe_.get(); } + uv_stream_t* uv_stream() + { + return reinterpret_cast(uv_pipe()); + } + uv_handle_t* uv_handle() + { + return reinterpret_cast(uv_pipe()); + } + + // -- Libuv callbacks + static void UVAlloc(uv_handle_t* handle, size_t suggestedSize, + uv_buf_t* buf); + static void UVData(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf); + + private: + ReadOnlyProcessT* Process_ = nullptr; + std::string* Target_ = nullptr; + std::vector Buffer_; + cm::uv_pipe_ptr UVPipe_; + }; + + /// @brief Process settings + struct SetupT + { + std::string WorkingDirectory; + std::vector Command; + ProcessResultT* Result = nullptr; + bool MergedOutput = false; + }; + + // -- Constructor + ReadOnlyProcessT() = default; + + // -- Const accessors + const SetupT& Setup() const { return Setup_; } + ProcessResultT* Result() const { return Setup_.Result; } + bool IsStarted() const { return IsStarted_; } + bool IsFinished() const { return IsFinished_; } + + // -- Runtime + void setup(ProcessResultT* result, bool mergedOutput, + std::vector const& command, + std::string const& workingDirectory = std::string()); + bool start(uv_loop_t* uv_loop, std::function&& finishedCallback); + + private: + // -- Friends + friend class PipeT; + // -- Libuv callbacks + static void UVExit(uv_process_t* handle, int64_t exitStatus, + int termSignal); + void UVTryFinish(); + + // -- Setup + SetupT Setup_; + // -- Runtime + bool IsStarted_ = false; + bool IsFinished_ = false; + std::function FinishedCallback_; + std::vector CommandPtr_; + std::array UVOptionsStdIO_; + uv_process_options_t UVOptions_; + cm::uv_process_ptr UVProcess_; + PipeT UVPipeOut_; + PipeT UVPipeErr_; + }; + +#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) && \ + UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19 +#define CMAKE_UV_SIGNAL_HACK + /* + libuv does not use SA_RESTART on its signal handler, but C++ streams + depend on it for reliable i/o operations. This RAII helper convinces + libuv to install its handler, and then revises the handler to add the + SA_RESTART flag. We use a distinct uv loop that never runs to avoid + ever really getting a callback. libuv may fill the hack loop's signal + pipe and then stop writing, but that won't break any real loops. + */ + class cmUVSignalHackRAII + { + uv_loop_t HackLoop; + cm::uv_signal_ptr HackSignal; + static void HackCB(uv_signal_t*, int) {} + public: + cmUVSignalHackRAII() + { + uv_loop_init(&this->HackLoop); + this->HackSignal.init(this->HackLoop); + this->HackSignal.start(HackCB, SIGCHLD); + struct sigaction hack_sa; + sigaction(SIGCHLD, NULL, &hack_sa); + if (!(hack_sa.sa_flags & SA_RESTART)) { + hack_sa.sa_flags |= SA_RESTART; + sigaction(SIGCHLD, &hack_sa, NULL); + } + } + ~cmUVSignalHackRAII() + { + this->HackSignal.stop(); + uv_loop_close(&this->HackLoop); + } + }; +#endif + +public: + // -- Constructors cmQtAutoGenerator(); - virtual ~cmQtAutoGenerator() = default; + virtual ~cmQtAutoGenerator(); + + // -- Run bool Run(std::string const& infoFile, std::string const& config); - std::string const& GetInfoFile() const { return InfoFile; } - std::string const& GetInfoDir() const { return InfoDir; } - std::string const& GetInfoConfig() const { return InfoConfig; } - bool GetVerbose() const { return Verbose; } + // -- Accessors + // Logging + Logger& Log() { return Logger_; } + // File System + FileSystem& FileSys() { return FileSys_; } + // InfoFile + std::string const& InfoFile() const { return InfoFile_; } + std::string const& InfoDir() const { return InfoDir_; } + std::string const& InfoConfig() const { return InfoConfig_; } + // libuv loop + uv_loop_t* UVLoop() { return UVLoop_.get(); } + cm::uv_async_ptr& UVRequest() { return UVRequest_; } -protected: - // -- Central processing - virtual bool Process(cmMakefile* makefile) = 0; - - // -- Log info - void LogBold(std::string const& message) const; - void LogInfo(cmQtAutoGen::Generator genType, - std::string const& message) const; - // -- Log warning - void LogWarning(cmQtAutoGen::Generator genType, - std::string const& message) const; - void LogFileWarning(cmQtAutoGen::Generator genType, - std::string const& filename, - std::string const& message) const; - // -- Log error - void LogError(cmQtAutoGen::Generator genType, - std::string const& message) const; - void LogFileError(cmQtAutoGen::Generator genType, - std::string const& filename, - std::string const& message) const; - void LogCommandError(cmQtAutoGen::Generator genType, - std::string const& message, - std::vector const& command, - std::string const& output) const; // -- Utility - bool MakeParentDirectory(cmQtAutoGen::Generator genType, - std::string const& filename) const; - bool FileIsOlderThan(std::string const& buildFile, - std::string const& sourceFile, - std::string* error = nullptr); - bool FileRead(std::string& content, std::string const& filename, - std::string* error = nullptr); - bool FileWrite(cmQtAutoGen::Generator genType, std::string const& filename, - std::string const& content); - bool FileDiffers(std::string const& filename, std::string const& content); - bool RunCommand(std::vector const& command, - std::string& output) const; + static std::string SettingsFind(std::string const& content, const char* key); + +protected: + // -- Abstract processing interface + virtual bool Init(cmMakefile* makefile) = 0; + virtual bool Process() = 0; private: + // -- Logging + Logger Logger_; + FileSystem FileSys_; // -- Info settings - std::string InfoFile; - std::string InfoDir; - std::string InfoConfig; - // -- Settings - bool Verbose; - bool ColorOutput; + std::string InfoFile_; + std::string InfoDir_; + std::string InfoConfig_; +// -- libuv loop +#ifdef CMAKE_UV_SIGNAL_HACK + std::unique_ptr UVHackRAII_; +#endif + std::unique_ptr UVLoop_; + cm::uv_async_ptr UVRequest_; }; #endif diff --git a/Source/cmQtAutoGeneratorInitializer.cxx b/Source/cmQtAutoGeneratorInitializer.cxx index de0ba4f..29f3653 100644 --- a/Source/cmQtAutoGeneratorInitializer.cxx +++ b/Source/cmQtAutoGeneratorInitializer.cxx @@ -24,6 +24,7 @@ #include "cm_sys_stat.h" #include "cmake.h" #include "cmsys/FStream.hxx" +#include "cmsys/SystemInformation.hxx" #include #include @@ -52,6 +53,20 @@ inline static std::string GetSafeProperty(cmSourceFile const* sf, return std::string(SafeString(sf->GetProperty(key))); } +static std::size_t GetParallelCPUCount() +{ + static std::size_t count = 0; + // Detect only on the first call + if (count == 0) { + cmsys::SystemInformation info; + info.RunCPUCheck(); + count = info.GetNumberOfPhysicalCPU(); + count = std::max(count, 1); + count = std::min(count, cmQtAutoGen::ParallelMax); + } + return count; +} + static void AddDefinitionEscaped(cmMakefile* makefile, const char* key, std::string const& value) { @@ -85,12 +100,12 @@ static void AddDefinitionEscaped( seplist.push_back(std::move(blist)); } makefile->AddDefinition(key, cmOutputConverter::EscapeForCMake( - cmJoin(seplist, cmQtAutoGen::listSep)) + cmJoin(seplist, cmQtAutoGen::ListSep)) .c_str()); } static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName, - cmQtAutoGen::Generator genType) + cmQtAutoGen::GeneratorT genType) { cmSourceGroup* sourceGroup = nullptr; // Acquire source group @@ -101,10 +116,10 @@ static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName, std::array props; // Use generator specific group name switch (genType) { - case cmQtAutoGen::MOC: + case cmQtAutoGen::GeneratorT::MOC: props[0] = "AUTOMOC_SOURCE_GROUP"; break; - case cmQtAutoGen::RCC: + case cmQtAutoGen::GeneratorT::RCC: props[0] = "AUTORCC_SOURCE_GROUP"; break; default: @@ -219,7 +234,7 @@ cmQtAutoGeneratorInitializer::cmQtAutoGeneratorInitializer( , UicEnabled(uicEnabled) , RccEnabled(rccEnabled) , QtVersionMajor(qtVersionMajor) - , MultiConfig(cmQtAutoGen::WRAP) + , MultiConfig(MultiConfigT::WRAPPER) { this->QtVersionMinor = cmQtAutoGeneratorInitializer::GetQtMinorVersion( target, this->QtVersionMajor); @@ -240,19 +255,19 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() // Multi configuration { if (!globalGen->IsMultiConfig()) { - this->MultiConfig = cmQtAutoGen::SINGLE; + this->MultiConfig = MultiConfigT::SINGLE; } // FIXME: Xcode does not support per-config sources, yet. // (EXCLUDED_SOURCE_FILE_NAMES) // if (globalGen->GetName().find("Xcode") != std::string::npos) { - // return cmQtAutoGen::FULL; + // return MultiConfigT::MULTI; //} // FIXME: Visual Studio does not support per-config sources, yet. // (EXCLUDED_SOURCE_FILE_NAMES) // if (globalGen->GetName().find("Visual Studio") != std::string::npos) { - // return cmQtAutoGen::FULL; + // return MultiConfigT::MULTI; //} } @@ -294,7 +309,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() this->AutogenInfoFile += "/AutogenInfo.cmake"; this->AutogenSettingsFile = this->DirInfo; - this->AutogenSettingsFile += "/AutogenOldSettings.cmake"; + this->AutogenSettingsFile += "/AutogenOldSettings.txt"; } // Autogen target FOLDER property @@ -324,7 +339,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() { std::string base = this->DirInfo; base += "/AutogenOldSettings"; - if (this->MultiConfig == cmQtAutoGen::SINGLE) { + if (this->MultiConfig == MultiConfigT::SINGLE) { AddCleanFile(makefile, base.append(".cmake")); } else { for (std::string const& cfg : this->ConfigsList) { @@ -340,7 +355,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() // Add moc compilation to generated files list if (this->MocEnabled) { std::string const mocsComp = this->DirBuild + "/mocs_compilation.cpp"; - auto files = this->AddGeneratedSource(mocsComp, cmQtAutoGen::MOC); + auto files = this->AddGeneratedSource(mocsComp, GeneratorT::MOC); for (std::string& file : files) { autogenProvides.push_back(std::move(file)); } @@ -349,7 +364,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() // Add autogen includes directory to the origin target INCLUDE_DIRECTORIES if (this->MocEnabled || this->UicEnabled) { std::string includeDir = this->DirBuild + "/include"; - if (this->MultiConfig != cmQtAutoGen::SINGLE) { + if (this->MultiConfig != MultiConfigT::SINGLE) { includeDir += "_$"; } this->Target->AddIncludeDirectory(includeDir, true); @@ -560,10 +575,10 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() msg += "For compatibility, CMake is excluding the GENERATED source " "file(s):\n"; for (const std::string& absFile : generatedHeaders) { - msg.append(" ").append(cmQtAutoGen::Quoted(absFile)).append("\n"); + msg.append(" ").append(Quoted(absFile)).append("\n"); } for (const std::string& absFile : generatedSources) { - msg.append(" ").append(cmQtAutoGen::Quoted(absFile)).append("\n"); + msg.append(" ").append(Quoted(absFile)).append("\n"); } msg += "from processing by "; msg += tools; @@ -630,7 +645,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() qrc.InfoFile = base; qrc.InfoFile += "Info.cmake"; qrc.SettingsFile = base; - qrc.SettingsFile += "Settings.cmake"; + qrc.SettingsFile += "Settings.txt"; } } } @@ -650,16 +665,16 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() std::vector nameOpts; nameOpts.emplace_back("-name"); nameOpts.emplace_back(std::move(name)); - cmQtAutoGen::RccMergeOptions(opts, nameOpts, QtV5); + RccMergeOptions(opts, nameOpts, QtV5); } // Merge file option - cmQtAutoGen::RccMergeOptions(opts, qrc.Options, QtV5); + RccMergeOptions(opts, qrc.Options, QtV5); qrc.Options = std::move(opts); } for (Qrc& qrc : this->Qrcs) { // Register file at target std::vector const ccOutput = - this->AddGeneratedSource(qrc.RccFile, cmQtAutoGen::RCC); + this->AddGeneratedSource(qrc.RccFile, GeneratorT::RCC); cmCustomCommandLines commandLines; { @@ -686,8 +701,9 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() ccName += qrc.PathChecksum; } std::vector ccDepends; - // Add the .qrc file to the custom target dependencies + // Add the .qrc and info file to the custom target dependencies ccDepends.push_back(qrc.QrcFile); + ccDepends.push_back(qrc.InfoFile); cmTarget* autoRccTarget = makefile->AddUtilityCommand( ccName, cmMakefile::TargetOrigin::Generator, true, @@ -709,15 +725,14 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets() { std::vector ccByproducts; std::vector ccDepends; - // Add the .qrc file to the custom command dependencies + // Add the .qrc and info file to the custom command dependencies ccDepends.push_back(qrc.QrcFile); + ccDepends.push_back(qrc.InfoFile); // Add the resource files to the dependencies { std::string error; - if (cmQtAutoGen::RccListInputs(this->RccExecutable, - this->RccListOptions, qrc.QrcFile, - qrc.Resources, &error)) { + if (RccListInputs(qrc.QrcFile, qrc.Resources, error)) { for (std::string const& fileName : qrc.Resources) { // Add resource file to the custom command dependencies ccDepends.push_back(fileName); @@ -903,8 +918,16 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets() // Basic setup AddDefinitionEscaped(makefile, "_multi_config", - cmQtAutoGen::MultiConfigName(this->MultiConfig)); + MultiConfigName(this->MultiConfig)); AddDefinitionEscaped(makefile, "_build_dir", this->DirBuild); + { + std::string parallel = GetSafeProperty(this->Target, "AUTOGEN_PARALLEL"); + // Autodetect number of CPUs + if (parallel.empty() || (parallel == "AUTO")) { + parallel = std::to_string(GetParallelCPUCount()); + } + AddDefinitionEscaped(makefile, "_parallel", parallel); + } if (this->MocEnabled || this->UicEnabled) { AddDefinitionEscaped(makefile, "_qt_version_major", this->QtVersionMajor); @@ -929,7 +952,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets() // Create info directory on demand if (!cmSystemTools::MakeDirectory(this->DirInfo)) { std::string emsg = ("Could not create directory: "); - emsg += cmQtAutoGen::Quoted(this->DirInfo); + emsg += Quoted(this->DirInfo); cmSystemTools::Error(emsg.c_str()); } @@ -951,7 +974,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets() if (!ofs) { // File open error std::string error = "Internal CMake error when trying to open file: "; - error += cmQtAutoGen::Quoted(fileName); + error += Quoted(fileName); error += " for writing."; cmSystemTools::Error(error.c_str()); } @@ -984,11 +1007,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets() OfsWriteMap("AM_MOC_INCLUDES", this->ConfigMocIncludes); OfsWriteMap("AM_UIC_TARGET_OPTIONS", this->ConfigUicOptions); // Settings files (only require for multi configuration generators) - if (this->MultiConfig != cmQtAutoGen::SINGLE) { + if (this->MultiConfig != MultiConfigT::SINGLE) { std::map settingsFiles; for (std::string const& cfg : this->ConfigsList) { - settingsFiles[cfg] = cmQtAutoGen::AppendFilenameSuffix( - this->AutogenSettingsFile, "_" + cfg); + settingsFiles[cfg] = + AppendFilenameSuffix(this->AutogenSettingsFile, "_" + cfg); } OfsWriteMap("AM_SETTINGS_FILE", settingsFiles); } @@ -1033,11 +1056,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets() OfsWriteMap("ARCC_CONFIG_SUFFIX", configSuffixes); // Settings files (only require for multi configuration generators) - if (this->MultiConfig != cmQtAutoGen::SINGLE) { + if (this->MultiConfig != MultiConfigT::SINGLE) { std::map settingsFiles; for (std::string const& cfg : this->ConfigsList) { settingsFiles[cfg] = - cmQtAutoGen::AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg); + AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg); } OfsWriteMap("ARCC_SETTINGS_FILE", settingsFiles); } @@ -1267,16 +1290,15 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargetsUic() } std::vector cmQtAutoGeneratorInitializer::AddGeneratedSource( - std::string const& filename, cmQtAutoGen::Generator genType) + std::string const& filename, GeneratorT genType) { std::vector genFiles; // Register source file in makefile and source group - if (this->MultiConfig != cmQtAutoGen::FULL) { + if (this->MultiConfig != MultiConfigT::MULTI) { genFiles.push_back(filename); } else { for (std::string const& cfg : this->ConfigsList) { - genFiles.push_back( - cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg)); + genFiles.push_back(AppendFilenameSuffix(filename, "_" + cfg)); } } { @@ -1292,14 +1314,14 @@ std::vector cmQtAutoGeneratorInitializer::AddGeneratedSource( } // Add source file to target - if (this->MultiConfig != cmQtAutoGen::FULL) { + if (this->MultiConfig != MultiConfigT::MULTI) { this->Target->AddSource(filename); } else { for (std::string const& cfg : this->ConfigsList) { std::string src = "$<$Target->AddSource(src); } @@ -1356,3 +1378,84 @@ bool cmQtAutoGeneratorInitializer::QtVersionGreaterOrEqual( } return false; } + +/// @brief Reads the resource files list from from a .qrc file +/// @arg fileName Must be the absolute path of the .qrc file +/// @return True if the rcc file was successfully read +bool cmQtAutoGeneratorInitializer::RccListInputs( + std::string const& fileName, std::vector& files, + std::string& error) +{ + if (!cmSystemTools::FileExists(fileName.c_str())) { + error = "rcc resource file does not exist:\n "; + error += Quoted(fileName); + error += "\n"; + return false; + } + if (!RccListOptions.empty()) { + // Use rcc for file listing + if (RccExecutable.empty()) { + error = "rcc executable not available"; + return false; + } + + // Run rcc list command in the directory of the qrc file with the pathless + // qrc file name argument. This way rcc prints relative paths. + // This avoids issues on Windows when the qrc file is in a path that + // contains non-ASCII characters. + std::string const fileDir = cmSystemTools::GetFilenamePath(fileName); + std::string const fileNameName = cmSystemTools::GetFilenameName(fileName); + + bool result = false; + int retVal = 0; + std::string rccStdOut; + std::string rccStdErr; + { + std::vector cmd; + cmd.push_back(RccExecutable); + cmd.insert(cmd.end(), RccListOptions.begin(), RccListOptions.end()); + cmd.push_back(fileNameName); + result = cmSystemTools::RunSingleCommand( + cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(), + cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto); + } + if (!result || retVal) { + error = "rcc list process failed for:\n "; + error += Quoted(fileName); + error += "\n"; + error += rccStdOut; + error += "\n"; + error += rccStdErr; + error += "\n"; + return false; + } + if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) { + return false; + } + } else { + // We can't use rcc for the file listing. + // Read the qrc file content into string and parse it. + { + std::string qrcContents; + { + cmsys::ifstream ifs(fileName.c_str()); + if (ifs) { + std::ostringstream osst; + osst << ifs.rdbuf(); + qrcContents = osst.str(); + } else { + error = "rcc file not readable:\n "; + error += Quoted(fileName); + error += "\n"; + return false; + } + } + // Parse string content + RccListParseContent(qrcContents, files); + } + } + + // Convert relative paths to absolute paths + RccListConvertFullPath(cmSystemTools::GetFilenamePath(fileName), files); + return true; +} diff --git a/Source/cmQtAutoGeneratorInitializer.h b/Source/cmQtAutoGeneratorInitializer.h index e06e1c4..0567437 100644 --- a/Source/cmQtAutoGeneratorInitializer.h +++ b/Source/cmQtAutoGeneratorInitializer.h @@ -13,7 +13,8 @@ class cmGeneratorTarget; -class cmQtAutoGeneratorInitializer +/// @brief Initializes the QtAutoGen generators +class cmQtAutoGeneratorInitializer : public cmQtAutoGen { public: static std::string GetQtMajorVersion(cmGeneratorTarget const* target); @@ -55,11 +56,15 @@ private: void SetupCustomTargetsUic(); std::vector AddGeneratedSource(std::string const& filename, - cmQtAutoGen::Generator genType); + GeneratorT genType); bool QtVersionGreaterOrEqual(unsigned long requestMajor, unsigned long requestMinor) const; + bool RccListInputs(std::string const& fileName, + std::vector& files, + std::string& errorMessage); + private: cmGeneratorTarget* Target; bool MocEnabled; @@ -73,7 +78,7 @@ private: // Configurations std::string ConfigDefault; std::vector ConfigsList; - cmQtAutoGen::MultiConfig MultiConfig; + MultiConfigT MultiConfig; // Names std::string AutogenTargetName; std::string AutogenFolder; diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoGeneratorMocUic.cxx index bce148e..4b02e0b 100644 --- a/Source/cmQtAutoGeneratorMocUic.cxx +++ b/Source/cmQtAutoGeneratorMocUic.cxx @@ -5,16 +5,15 @@ #include #include +#include #include #include #include -#include #include #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmMakefile.h" -#include "cmOutputConverter.h" #include "cmSystemTools.h" #include "cmake.h" @@ -22,1712 +21,2004 @@ #include #endif -// -- Static variables - -static const char* SettingsKeyMoc = "AM_MOC_SETTINGS_HASH"; -static const char* SettingsKeyUic = "AM_UIC_SETTINGS_HASH"; +// -- Class methods -// -- Static functions +std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( + std::string const& relativePath) const +{ + return cmSystemTools::CollapseCombinedPath(AutogenBuildDir, relativePath); +} -static std::string SubDirPrefix(std::string const& fileName) +/** + * @brief Tries to find the header file to the given file base path by + * appending different header extensions + * @return True on success + */ +bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader( + std::string& header, std::string const& testBasePath) const { - std::string res(cmSystemTools::GetFilenamePath(fileName)); - if (!res.empty()) { - res += '/'; + for (std::string const& ext : HeaderExtensions) { + std::string testFilePath(testBasePath); + testFilePath.push_back('.'); + testFilePath += ext; + if (FileSys->FileExists(testFilePath)) { + header = testFilePath; + return true; + } } - return res; + return false; } -static bool ListContains(std::vector const& list, - std::string const& entry) +bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped( + std::string const& fileName) const { - return (std::find(list.begin(), list.end(), entry) != list.end()); + return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } -// -- Class methods +/** + * @brief Returns the first relevant Qt macro name found in the given C++ code + * @return The name of the Qt macro or an empty string + */ +std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro( + std::string const& content) const +{ + for (KeyExpT const& filter : MacroFilters) { + // Run a simple find string operation before the expensive + // regular expression check + if (content.find(filter.Key) != std::string::npos) { + cmsys::RegularExpressionMatch match; + if (filter.Exp.find(content.c_str(), match)) { + // Return macro name on demand + return filter.Key; + } + } + } + return std::string(); +} -cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() - : MultiConfig(cmQtAutoGen::WRAP) - , IncludeProjectDirsBefore(false) - , QtVersionMajor(4) - , MocSettingsChanged(false) - , MocPredefsChanged(false) - , MocRelaxedMode(false) - , UicSettingsChanged(false) +std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const { - // Precompile regular expressions - this->MocRegExpInclude.compile( - "[\n][ \t]*#[ \t]*include[ \t]+" - "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); - this->UicRegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+" - "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); + std::string res; + const auto itB = MacroFilters.cbegin(); + const auto itE = MacroFilters.cend(); + const auto itL = itE - 1; + auto itC = itB; + for (; itC != itE; ++itC) { + // Separator + if (itC != itB) { + if (itC != itL) { + res += ", "; + } else { + res += " or "; + } + } + // Key + res += itC->Key; + } + return res; } -bool cmQtAutoGeneratorMocUic::InitInfoFile(cmMakefile* makefile) +std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile( + std::string const& sourcePath, std::string const& includeString) const { - // -- Meta - this->HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); + // Search in vicinity of the source + { + std::string testPath = sourcePath; + testPath += includeString; + if (FileSys->FileExists(testPath)) { + return FileSys->RealPath(testPath); + } + } + // Search in include directories + for (std::string const& path : IncludePaths) { + std::string fullPath = path; + fullPath.push_back('/'); + fullPath += includeString; + if (FileSys->FileExists(fullPath)) { + return FileSys->RealPath(fullPath); + } + } + // Return empty string + return std::string(); +} - // Utility lambdas - auto InfoGet = [makefile](const char* key) { - return makefile->GetSafeDefinition(key); - }; - auto InfoGetBool = [makefile](const char* key) { - return makefile->IsOn(key); - }; - auto InfoGetList = [makefile](const char* key) -> std::vector { - std::vector list; - cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list); - return list; - }; - auto InfoGetLists = - [makefile](const char* key) -> std::vector> { - std::vector> lists; - { - std::string const value = makefile->GetSafeDefinition(key); - std::string::size_type pos = 0; - while (pos < value.size()) { - std::string::size_type next = value.find(cmQtAutoGen::listSep, pos); - std::string::size_type length = - (next != std::string::npos) ? next - pos : value.size() - pos; - // Remove enclosing braces - if (length >= 2) { - std::string::const_iterator itBeg = value.begin() + (pos + 1); - std::string::const_iterator itEnd = itBeg + (length - 2); +void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies( + std::string const& content, std::set& depends) const +{ + if (!DependFilters.empty() && !content.empty()) { + for (KeyExpT const& filter : DependFilters) { + // Run a simple find string check + if (content.find(filter.Key) != std::string::npos) { + // Run the expensive regular expression check loop + const char* contentChars = content.c_str(); + cmsys::RegularExpressionMatch match; + while (filter.Exp.find(contentChars, match)) { { - std::string subValue(itBeg, itEnd); - std::vector list; - cmSystemTools::ExpandListArgument(subValue, list); - lists.push_back(std::move(list)); + std::string dep = match.match(1); + if (!dep.empty()) { + depends.emplace(std::move(dep)); + } } + contentChars += match.end(); } - pos += length; - pos += cmQtAutoGen::listSep.size(); } } - return lists; - }; - auto InfoGetConfig = [makefile, this](const char* key) -> std::string { - const char* valueConf = nullptr; - { - std::string keyConf = key; - keyConf += '_'; - keyConf += this->GetInfoConfig(); - valueConf = makefile->GetDefinition(keyConf); + } +} + +bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped( + std::string const& fileName) const +{ + return (!Enabled || (SkipList.find(fileName) != SkipList.end())); +} + +void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk) +{ + if (AutoMoc && Header) { + // Don't parse header for moc if the file is included by a source already + if (wrk.Gen().ParallelMocIncluded(FileName)) { + AutoMoc = false; } - if (valueConf == nullptr) { - valueConf = makefile->GetSafeDefinition(key); + } + + if (AutoMoc || AutoUic) { + std::string error; + MetaT meta; + if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) { + if (!meta.Content.empty()) { + meta.FileDir = SubDirPrefix(FileName); + meta.FileBase = + cmSystemTools::GetFilenameWithoutLastExtension(FileName); + + bool success = true; + if (AutoMoc) { + if (Header) { + success = ParseMocHeader(wrk, meta); + } else { + success = ParseMocSource(wrk, meta); + } + } + if (AutoUic && success) { + ParseUic(wrk, meta); + } + } else { + wrk.LogFileWarning(GeneratorT::GEN, FileName, + "The source file is empty"); + } + } else { + wrk.LogFileError(GeneratorT::GEN, FileName, + "Could not read the file: " + error); } - return std::string(valueConf); - }; - auto InfoGetConfigList = - [&InfoGetConfig](const char* key) -> std::vector { - std::vector list; - cmSystemTools::ExpandListArgument(InfoGetConfig(key), list); - return list; + } +} + +bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, + MetaT const& meta) +{ + struct JobPre + { + bool self; // source file is self + bool underscore; // "moc_" style include + std::string SourceFile; + std::string IncludeString; }; - // -- Read info file - if (!makefile->ReadListFile(this->GetInfoFile().c_str())) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "File processing failed"); - return false; - } + struct MocInclude + { + std::string Inc; // full include string + std::string Dir; // include string directory + std::string Base; // include string file base + }; - // -- Meta - this->MultiConfig = cmQtAutoGen::MultiConfigType(InfoGet("AM_MULTI_CONFIG")); - this->ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX"); - if (this->ConfigSuffix.empty()) { - this->ConfigSuffix = "_"; - this->ConfigSuffix += this->GetInfoConfig(); - } + // Check if this source file contains a relevant macro + std::string const ownMacro = wrk.Moc().FindMacro(meta.Content); - this->SettingsFile = InfoGetConfig("AM_SETTINGS_FILE"); - if (this->SettingsFile.empty()) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "Settings file name missing"); - return false; + // Extract moc includes from file + std::deque mocIncsUsc; + std::deque mocIncsDot; + { + if (meta.Content.find("moc") != std::string::npos) { + const char* contentChars = meta.Content.c_str(); + cmsys::RegularExpressionMatch match; + while (wrk.Moc().RegExpInclude.find(contentChars, match)) { + std::string incString = match.match(1); + std::string incDir(SubDirPrefix(incString)); + std::string incBase = + cmSystemTools::GetFilenameWithoutLastExtension(incString); + if (cmHasLiteralPrefix(incBase, "moc_")) { + // moc_.cxx + // Remove the moc_ part from the base name + mocIncsUsc.emplace_back(MocInclude{ + std::move(incString), std::move(incDir), incBase.substr(4) }); + } else { + // .moc + mocIncsDot.emplace_back(MocInclude{ + std::move(incString), std::move(incDir), std::move(incBase) }); + } + // Forward content pointer + contentChars += match.end(); + } + } } - // - Files and directories - this->ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); - this->ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); - this->CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); - this->CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); - this->IncludeProjectDirsBefore = - InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); - this->AutogenBuildDir = InfoGet("AM_BUILD_DIR"); - if (this->AutogenBuildDir.empty()) { - this->LogFileError(cmQtAutoGen::GEN, this->GetInfoFile(), - "Autogen build directory missing"); - return false; + // Check if there is anything to do + if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) { + return true; } - // - Qt environment - if (!cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"), - &this->QtVersionMajor)) { - this->QtVersionMajor = 4; - } - this->MocExecutable = InfoGet("AM_QT_MOC_EXECUTABLE"); - this->UicExecutable = InfoGet("AM_QT_UIC_EXECUTABLE"); + bool ownDotMocIncluded = false; + bool ownMocUscIncluded = false; + std::deque jobs; - // - Moc - if (this->MocEnabled()) { - this->MocSkipList = InfoGetList("AM_MOC_SKIP"); - this->MocDefinitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); -#ifdef _WIN32 - { - std::string const win32("WIN32"); - if (!ListContains(this->MocDefinitions, win32)) { - this->MocDefinitions.push_back(win32); + // Process moc_.cxx includes + for (const MocInclude& mocInc : mocIncsUsc) { + std::string const header = + MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base); + if (!header.empty()) { + // Check if header is skipped + if (wrk.Moc().skipped(header)) { + continue; } - } -#endif - this->MocIncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); - this->MocOptions = InfoGetList("AM_MOC_OPTIONS"); - this->MocRelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); - { - std::vector const MocMacroNames = - InfoGetList("AM_MOC_MACRO_NAMES"); - for (std::string const& item : MocMacroNames) { - this->MocMacroFilters.emplace_back( - item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]")); + // 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; } - } - { - std::vector const mocDependFilters = - InfoGetList("AM_MOC_DEPEND_FILTERS"); - // Insert Q_PLUGIN_METADATA dependency filter - if (this->QtVersionMajor != 4) { - this->MocDependFilterPush("Q_PLUGIN_METADATA", - "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" - "[^\\)]*FILE[ \t]*\"([^\"]+)\""); + } else { + { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += ", but the header "; + emsg += Quoted(MocStringHeaders(wrk, mocInc.Base)); + emsg += " could not be found."; + wrk.LogFileError(GeneratorT::MOC, FileName, emsg); } - // Insert user defined dependency filters - if ((mocDependFilters.size() % 2) == 0) { - for (std::vector::const_iterator - dit = mocDependFilters.begin(), - ditEnd = mocDependFilters.end(); - dit != ditEnd; dit += 2) { - if (!this->MocDependFilterPush(*dit, *(dit + 1))) { - return false; + return false; + } + } + + // Process .moc includes + for (const MocInclude& mocInc : mocIncsDot) { + const bool ownMoc = (mocInc.Base == meta.FileBase); + if (wrk.Moc().RelaxedMode) { + // Relaxed mode + if (!ownMacro.empty() && ownMoc) { + // Add self + jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc }); + ownDotMocIncluded = true; + } else { + // In relaxed mode try to find a header instead but issue a warning. + // This is for KDE4 compatibility + std::string const header = + MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base); + if (!header.empty()) { + // Check if header is skipped + if (wrk.Moc().skipped(header)) { + continue; } + // Register moc job + jobs.emplace_back(JobPre{ ownMoc, false, header, mocInc.Inc }); + if (ownMacro.empty()) { + if (ownMoc) { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += ", but does not contain a "; + emsg += wrk.Moc().MacrosString(); + emsg += " macro.\nRunning moc on\n "; + emsg += Quoted(header); + emsg += "!\nBetter include "; + emsg += Quoted("moc_" + mocInc.Base + ".cpp"); + emsg += " for a compatibility with strict mode.\n" + "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; + wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg); + } else { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += " instead of "; + emsg += Quoted("moc_" + mocInc.Base + ".cpp"); + emsg += ".\nRunning moc on\n "; + emsg += Quoted(header); + emsg += "!\nBetter include "; + emsg += Quoted("moc_" + mocInc.Base + ".cpp"); + emsg += " for compatibility with strict mode.\n" + "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; + wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg); + } + } + } else { + { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += ", which seems to be the moc file from a different " + "source file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a " + "matching header "; + emsg += Quoted(MocStringHeaders(wrk, mocInc.Base)); + emsg += " could not be found."; + wrk.LogFileError(GeneratorT::MOC, FileName, emsg); + } + return false; + } + } + } else { + // Strict mode + if (ownMoc) { + // Include self + jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc }); + ownDotMocIncluded = true; + // Accept but issue a warning if moc isn't required + if (ownMacro.empty()) { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += ", but does not contain a "; + emsg += wrk.Moc().MacrosString(); + emsg += " macro."; + wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg); } } else { - this->LogFileError( - cmQtAutoGen::MOC, this->GetInfoFile(), - "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); + // Don't allow .moc include other than self in strict mode + { + std::string emsg = "The file includes the moc file "; + emsg += Quoted(mocInc.Inc); + emsg += ", which seems to be the moc file from a different " + "source file.\nThis is not supported. Include "; + emsg += Quoted(meta.FileBase + ".moc"); + emsg += " to run moc on this source file."; + wrk.LogFileError(GeneratorT::MOC, FileName, emsg); + } return false; } } - this->MocPredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); } - // - Uic - if (this->UicEnabled()) { - this->UicSkipList = InfoGetList("AM_UIC_SKIP"); - this->UicSearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); - this->UicTargetOptions = 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() << ")"; - this->LogFileError(cmQtAutoGen::UIC, this->GetInfoFile(), ost.str()); - return false; + if (!ownMacro.empty() && !ownDotMocIncluded) { + // In this case, check whether the scanned file itself contains a + // Q_OBJECT. + // If this is the case, the moc_foo.cpp should probably be generated from + // foo.cpp instead of foo.h, because otherwise it won't build. + // But warn, since this is not how it is supposed to be used. + // This is for KDE4 compatibility. + if (wrk.Moc().RelaxedMode && ownMocUscIncluded) { + JobPre uscJobPre; + // Remove underscore job request + { + auto itC = jobs.begin(); + auto itE = jobs.end(); + for (; itC != itE; ++itC) { + JobPre& job(*itC); + if (job.self && job.underscore) { + uscJobPre = std::move(job); + jobs.erase(itC); + break; + } + } } - auto fitEnd = sources.cend(); - auto fit = sources.begin(); - auto oit = options.begin(); - while (fit != fitEnd) { - this->UicOptions[*fit] = std::move(*oit); - ++fit; - ++oit; + // Issue a warning + { + std::string emsg = "The file contains a "; + emsg += ownMacro; + emsg += " macro, but does not include "; + emsg += Quoted(meta.FileBase + ".moc"); + emsg += ". Instead it includes "; + emsg += Quoted(uscJobPre.IncludeString); + emsg += ".\nRunning moc on\n "; + emsg += Quoted(FileName); + emsg += "!\nBetter include "; + emsg += Quoted(meta.FileBase + ".moc"); + emsg += " for compatibility with strict mode.\n" + "(CMAKE_AUTOMOC_RELAXED_MODE warning)"; + wrk.LogFileWarning(GeneratorT::MOC, FileName, emsg); } + // Add own source job + jobs.emplace_back( + JobPre{ true, false, FileName, uscJobPre.IncludeString }); + } else { + // Otherwise always error out since it will not compile. + { + std::string emsg = "The file contains a "; + emsg += ownMacro; + emsg += " macro, but does not include "; + emsg += Quoted(meta.FileBase + ".moc"); + emsg += "!\nConsider to\n - add #include \""; + emsg += meta.FileBase; + emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; + wrk.LogFileError(GeneratorT::MOC, FileName, emsg); + } + return false; } } - // Initialize source file jobs + // Convert pre jobs to actual jobs + for (JobPre& jobPre : jobs) { + JobHandleT jobHandle(new JobMocT(std::move(jobPre.SourceFile), FileName, + std::move(jobPre.IncludeString))); + if (jobPre.self) { + // Read depdendencies from this source + static_cast(*jobHandle).FindDependencies(wrk, meta.Content); + } + if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) { + return false; + } + } + return true; +} + +bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk, + MetaT const& meta) +{ + bool success = true; + std::string const macroName = wrk.Moc().FindMacro(meta.Content); + if (!macroName.empty()) { + JobHandleT jobHandle( + new JobMocT(std::string(FileName), std::string(), std::string())); + // Read depdendencies from this source + static_cast(*jobHandle).FindDependencies(wrk, meta.Content); + success = wrk.Gen().ParallelJobPushMoc(jobHandle); + } + return success; +} + +std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders( + WorkerT& wrk, std::string const& fileBase) const +{ + std::string res = fileBase; + res += ".{"; + res += cmJoin(wrk.Base().HeaderExtensions, ","); + res += "}"; + return res; +} + +std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader( + WorkerT& wrk, std::string const& includerDir, std::string const& includeBase) +{ + std::string header; + // Search in vicinity of the source + if (!wrk.Base().FindHeader(header, includerDir + includeBase)) { + // Search in include directories + for (std::string const& path : wrk.Moc().IncludePaths) { + std::string fullPath = path; + fullPath.push_back('/'); + fullPath += includeBase; + if (wrk.Base().FindHeader(header, fullPath)) { + break; + } + } + } + // Sanitize + if (!header.empty()) { + header = wrk.FileSys().RealPath(header); + } + return header; +} + +bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk, + MetaT const& meta) +{ + bool success = true; + if (meta.Content.find("ui_") != std::string::npos) { + const char* contentChars = meta.Content.c_str(); + cmsys::RegularExpressionMatch match; + while (wrk.Uic().RegExpInclude.find(contentChars, match)) { + if (!ParseUicInclude(wrk, meta, match.match(1))) { + success = false; + break; + } + contentChars += match.end(); + } + } + return success; +} + +bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude( + WorkerT& wrk, MetaT const& meta, std::string&& includeString) +{ + bool success = false; + std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString); + if (!uiInputFile.empty()) { + if (!wrk.Uic().skipped(uiInputFile)) { + JobHandleT jobHandle(new JobUicT(std::move(uiInputFile), FileName, + std::move(includeString))); + success = wrk.Gen().ParallelJobPushUic(jobHandle); + } else { + // A skipped file is successful + success = true; + } + } + return success; +} + +std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile( + WorkerT& wrk, MetaT const& meta, std::string const& includeString) +{ + std::string res; + std::string searchFile = + cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3); + searchFile += ".ui"; + // Collect search paths list + std::deque testFiles; { - // Utility lambdas - auto AddJob = [this](std::map& jobs, - std::string&& sourceFile) { - const bool moc = !this->MocSkip(sourceFile); - const bool uic = !this->UicSkip(sourceFile); - if (moc || uic) { - SourceJob& job = jobs[std::move(sourceFile)]; - job.Moc = moc; - job.Uic = uic; - } - }; + std::string const searchPath = SubDirPrefix(includeString); - // Add header jobs - for (std::string& hdr : InfoGetList("AM_HEADERS")) { - AddJob(this->HeaderJobs, std::move(hdr)); + std::string searchFileFull; + if (!searchPath.empty()) { + searchFileFull = searchPath; + searchFileFull += searchFile; } - // Add source jobs + // Vicinity of the source { - std::vector sources = InfoGetList("AM_SOURCES"); - // Add header(s) for the source file - for (std::string const& src : sources) { - const bool srcMoc = !this->MocSkip(src); - const bool srcUic = !this->UicSkip(src); - if (!srcMoc && !srcUic) { - continue; - } - // Search for the default header file and a private header - std::array headerBases; - headerBases[0] = SubDirPrefix(src); - headerBases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src); - headerBases[1] = headerBases[0]; - headerBases[1] += "_p"; - for (std::string const& headerBase : headerBases) { - std::string header; - if (this->FindHeader(header, headerBase)) { - const bool moc = srcMoc && !this->MocSkip(header); - const bool uic = srcUic && !this->UicSkip(header); - if (moc || uic) { - SourceJob& job = this->HeaderJobs[std::move(header)]; - job.Moc = moc; - job.Uic = uic; - } - } - } + std::string const sourcePath = meta.FileDir; + testFiles.push_back(sourcePath + searchFile); + if (!searchPath.empty()) { + testFiles.push_back(sourcePath + searchFileFull); } - // Add Source jobs - for (std::string& src : sources) { - AddJob(this->SourceJobs, std::move(src)); + } + // AUTOUIC search paths + if (!wrk.Uic().SearchPaths.empty()) { + for (std::string const& sPath : wrk.Uic().SearchPaths) { + testFiles.push_back((sPath + "/").append(searchFile)); + } + if (!searchPath.empty()) { + for (std::string const& sPath : wrk.Uic().SearchPaths) { + testFiles.push_back((sPath + "/").append(searchFileFull)); + } } } } - // Init derived information - // ------------------------ - - // Init file path checksum generator - this->FilePathChecksum.setupParentDirs( - this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir, - this->ProjectBinaryDir); - - // include directory - this->AutogenIncludeDir = "include"; - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - this->AutogenIncludeDir += this->ConfigSuffix; + // Search for the .ui file! + for (std::string const& testFile : testFiles) { + if (wrk.FileSys().FileExists(testFile)) { + res = wrk.FileSys().RealPath(testFile); + break; + } } - this->AutogenIncludeDir += "/"; - // Moc variables - if (this->MocEnabled()) { - // Mocs compilation file - this->MocCompFileRel = "mocs_compilation"; - if (this->MultiConfig == cmQtAutoGen::FULL) { - this->MocCompFileRel += this->ConfigSuffix; + // Log error + if (res.empty()) { + std::string emsg = "Could not find "; + emsg += Quoted(searchFile); + emsg += " in\n"; + for (std::string const& testFile : testFiles) { + emsg += " "; + emsg += Quoted(testFile); + emsg += "\n"; } - this->MocCompFileRel += ".cpp"; - this->MocCompFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->MocCompFileRel); + wrk.LogFileError(GeneratorT::UIC, FileName, emsg); + } - // Moc predefs file - if (!this->MocPredefsCmd.empty()) { - this->MocPredefsFileRel = "moc_predefs"; - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - this->MocPredefsFileRel += this->ConfigSuffix; + return res; +} + +void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk) +{ + // (Re)generate moc_predefs.h on demand + bool generate(false); + bool fileExists(wrk.FileSys().FileExists(wrk.Moc().PredefsFileAbs)); + if (!fileExists) { + if (wrk.Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(wrk.Moc().PredefsFileRel); + reason += " because it doesn't exist"; + wrk.LogInfo(GeneratorT::MOC, reason); + } + generate = true; + } else if (wrk.Moc().SettingsChanged) { + if (wrk.Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(wrk.Moc().PredefsFileRel); + reason += " because the settings changed."; + wrk.LogInfo(GeneratorT::MOC, reason); + } + generate = true; + } + if (generate) { + ProcessResultT result; + { + // Compose command + std::vector cmd = wrk.Moc().PredefsCmd; + // Add includes + cmd.insert(cmd.end(), wrk.Moc().Includes.begin(), + wrk.Moc().Includes.end()); + // Add definitions + for (std::string const& def : wrk.Moc().Definitions) { + cmd.push_back("-D" + def); + } + // Execute command + if (!wrk.RunProcess(GeneratorT::MOC, result, cmd)) { + std::string emsg = "The content generation command for "; + emsg += Quoted(wrk.Moc().PredefsFileRel); + emsg += " failed.\n"; + emsg += result.ErrorMessage; + wrk.LogCommandError(GeneratorT::MOC, emsg, cmd, result.StdOut); } - this->MocPredefsFileRel += ".h"; - this->MocPredefsFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->MocPredefsFileRel); } - // Sort include directories on demand - if (this->IncludeProjectDirsBefore) { - // Move strings to temporary list - std::list includes; - includes.insert(includes.end(), this->MocIncludePaths.begin(), - this->MocIncludePaths.end()); - this->MocIncludePaths.clear(); - this->MocIncludePaths.reserve(includes.size()); - // Append project directories only - { - std::array const movePaths = { - { &this->ProjectBinaryDir, &this->ProjectSourceDir } - }; - for (std::string const* ppath : movePaths) { - std::list::iterator it = includes.begin(); - while (it != includes.end()) { - std::string const& path = *it; - if (cmSystemTools::StringStartsWith(path, ppath->c_str())) { - this->MocIncludePaths.push_back(path); - it = includes.erase(it); - } else { - ++it; - } - } + // (Re)write predefs file only on demand + if (!result.error()) { + if (!fileExists || + wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) { + if (wrk.FileSys().FileWrite(GeneratorT::MOC, wrk.Moc().PredefsFileAbs, + result.StdOut)) { + // Success + } else { + std::string emsg = "Writing "; + emsg += Quoted(wrk.Moc().PredefsFileRel); + emsg += " failed."; + wrk.LogFileError(GeneratorT::MOC, wrk.Moc().PredefsFileAbs, emsg); } + } else { + // Touch to update the time stamp + if (wrk.Log().Verbose()) { + std::string msg = "Touching "; + msg += Quoted(wrk.Moc().PredefsFileRel); + msg += "."; + wrk.LogInfo(GeneratorT::MOC, msg); + } + wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs); } - // Append remaining directories - this->MocIncludePaths.insert(this->MocIncludePaths.end(), - includes.begin(), includes.end()); } - // Compose moc includes list + } +} + +void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies( + WorkerT& wrk, std::string const& content) +{ + wrk.Moc().FindDependencies(content, Depends); + DependsValid = true; +} + +void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk) +{ + // Compute build file name + if (!IncludeString.empty()) { + BuildFile = wrk.Base().AutogenIncludeDirAbs; + BuildFile += IncludeString; + } else { + std::string buildRel = wrk.Base().FilePathChecksum.getPart(SourceFile); + buildRel += '/'; + buildRel += "moc_"; + buildRel += cmSystemTools::GetFilenameWithoutLastExtension(SourceFile); + if (wrk.Base().MultiConfig != MultiConfigT::SINGLE) { + buildRel += wrk.Base().ConfigSuffix; + } + buildRel += ".cpp"; + wrk.Gen().ParallelMocAutoRegister(buildRel); + BuildFile = wrk.Base().AbsoluteBuildPath(buildRel); + } + + if (UpdateRequired(wrk)) { + GenerateMoc(wrk); + } +} + +bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) +{ + bool const verbose = wrk.Gen().Log().Verbose(); + + // Test if the build file exists + if (!wrk.FileSys().FileExists(BuildFile)) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " from its source file "; + reason += Quoted(SourceFile); + reason += " because it doesn't exist"; + wrk.LogInfo(GeneratorT::MOC, reason); + } + return true; + } + + // Test if any setting changed + if (wrk.Moc().SettingsChanged) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " from "; + reason += Quoted(SourceFile); + reason += " because the MOC settings changed"; + wrk.LogInfo(GeneratorT::MOC, reason); + } + return true; + } + + // Test if the moc_predefs file is newer + if (!wrk.Moc().PredefsFileAbs.empty()) { + bool isOlder = false; { - std::set frameworkPaths; - for (std::string const& path : this->MocIncludePaths) { - this->MocIncludes.push_back("-I" + path); - // Extract framework path - if (cmHasLiteralSuffix(path, ".framework/Headers")) { - // Go up twice to get to the framework root - std::vector pathComponents; - cmSystemTools::SplitPath(path, pathComponents); - std::string frameworkPath = cmSystemTools::JoinPath( - pathComponents.begin(), pathComponents.end() - 2); - frameworkPaths.insert(frameworkPath); - } - } - // Append framework includes - for (std::string const& path : frameworkPaths) { - this->MocIncludes.push_back("-F"); - this->MocIncludes.push_back(path); + std::string error; + isOlder = wrk.FileSys().FileIsOlderThan( + BuildFile, wrk.Moc().PredefsFileAbs, &error); + if (!isOlder && !error.empty()) { + wrk.LogError(GeneratorT::MOC, error); + return false; } } - // Setup single list with all options - { - // Add includes - this->MocAllOptions.insert(this->MocAllOptions.end(), - this->MocIncludes.begin(), - this->MocIncludes.end()); - // Add definitions - for (std::string const& def : this->MocDefinitions) { - this->MocAllOptions.push_back("-D" + def); + if (isOlder) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " because it's older than: "; + reason += Quoted(wrk.Moc().PredefsFileAbs); + wrk.LogInfo(GeneratorT::MOC, reason); } - // Add options - this->MocAllOptions.insert(this->MocAllOptions.end(), - this->MocOptions.begin(), - this->MocOptions.end()); + return true; } } - return true; -} - -void cmQtAutoGeneratorMocUic::SettingsFileRead(cmMakefile* makefile) -{ - // Compose current settings strings + // Test if the source file is newer { - cmCryptoHash crypt(cmCryptoHash::AlgoSHA256); - std::string const sep(" ~~~ "); - if (this->MocEnabled()) { - std::string str; - str += this->MocExecutable; - str += sep; - str += cmJoin(this->MocAllOptions, ";"); - str += sep; - str += this->IncludeProjectDirsBefore ? "TRUE" : "FALSE"; - str += sep; - str += cmJoin(this->MocPredefsCmd, ";"); - str += sep; - this->SettingsStringMoc = crypt.HashString(str); + bool isOlder = false; + { + std::string error; + isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); + if (!isOlder && !error.empty()) { + wrk.LogError(GeneratorT::MOC, error); + return false; + } } - if (this->UicEnabled()) { - std::string str; - str += this->UicExecutable; - str += sep; - str += cmJoin(this->UicTargetOptions, ";"); - for (const auto& item : this->UicOptions) { - str += sep; - str += item.first; - str += sep; - str += cmJoin(item.second, ";"); + if (isOlder) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " because it's older than its source file "; + reason += Quoted(SourceFile); + wrk.LogInfo(GeneratorT::MOC, reason); } - str += sep; - this->SettingsStringUic = crypt.HashString(str); + return true; } } - // Read old settings - if (makefile->ReadListFile(this->SettingsFile.c_str())) { - { - auto SMatch = [makefile](const char* key, std::string const& value) { - return (value == makefile->GetSafeDefinition(key)); - }; - if (!SMatch(SettingsKeyMoc, this->SettingsStringMoc)) { - this->MocSettingsChanged = true; - } - if (!SMatch(SettingsKeyUic, this->SettingsStringUic)) { - this->UicSettingsChanged = true; + // Test if a dependency file is newer + { + // Read dependencies on demand + if (!DependsValid) { + std::string content; + { + std::string error; + if (!wrk.FileSys().FileRead(content, SourceFile, &error)) { + std::string emsg = "Could not read file\n "; + emsg += Quoted(SourceFile); + emsg += "\nrequired by moc include "; + emsg += Quoted(IncludeString); + emsg += " in\n "; + emsg += Quoted(IncluderFile); + emsg += ".\n"; + emsg += error; + wrk.LogError(GeneratorT::MOC, emsg); + return false; + } } + FindDependencies(wrk, content); } - // In case any setting changed remove the old settings file. - // This triggers a full rebuild on the next run if the current - // build is aborted before writing the current settings in the end. - if (this->SettingsChanged()) { - cmSystemTools::RemoveFile(this->SettingsFile); + // Check dependency timestamps + std::string error; + std::string sourceDir = SubDirPrefix(SourceFile); + for (std::string const& depFileRel : Depends) { + std::string depFileAbs = + wrk.Moc().FindIncludedFile(sourceDir, depFileRel); + if (!depFileAbs.empty()) { + if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " from "; + reason += Quoted(SourceFile); + reason += " because it is older than it's dependency file "; + reason += Quoted(depFileAbs); + wrk.LogInfo(GeneratorT::MOC, reason); + } + return true; + } + if (!error.empty()) { + wrk.LogError(GeneratorT::MOC, error); + return false; + } + } else { + std::string message = "Could not find dependency file "; + message += Quoted(depFileRel); + wrk.LogFileWarning(GeneratorT::MOC, SourceFile, message); + } } - } else { - // If the file could not be read re-generate everythiung. - this->MocSettingsChanged = true; - this->UicSettingsChanged = true; } + + return false; } -bool cmQtAutoGeneratorMocUic::SettingsFileWrite() +void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk) { - bool success = true; - // Only write if any setting changed - if (this->SettingsChanged()) { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::GEN, "Writing settings file " + - cmQtAutoGen::Quoted(this->SettingsFile)); - } - // Compose settings file content - std::string settings; - { - auto SettingAppend = [&settings](const char* key, - std::string const& value) { - settings += "set("; - settings += key; - settings += " "; - settings += cmOutputConverter::EscapeForCMake(value); - settings += ")\n"; - }; - SettingAppend(SettingsKeyMoc, this->SettingsStringMoc); - SettingAppend(SettingsKeyUic, this->SettingsStringUic); - } - // Write settings file - if (!this->FileWrite(cmQtAutoGen::GEN, this->SettingsFile, settings)) { - this->LogFileError(cmQtAutoGen::GEN, this->SettingsFile, - "Settings file writing failed"); - // Remove old settings file to trigger a full rebuild on the next run - cmSystemTools::RemoveFile(this->SettingsFile); - success = false; + // Make sure the parent directory exists + if (wrk.FileSys().MakeParentDirectory(GeneratorT::MOC, BuildFile)) { + // Compose moc command + std::vector cmd; + cmd.push_back(wrk.Moc().Executable); + // Add options + cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(), + wrk.Moc().AllOptions.end()); + // Add predefs include + if (!wrk.Moc().PredefsFileAbs.empty()) { + cmd.push_back("--include"); + cmd.push_back(wrk.Moc().PredefsFileAbs); + } + cmd.push_back("-o"); + cmd.push_back(BuildFile); + cmd.push_back(SourceFile); + + // Execute moc command + ProcessResultT result; + if (wrk.RunProcess(GeneratorT::MOC, result, cmd)) { + // Moc command success + if (IncludeString.empty()) { + // Notify the generator that a not included file changed + wrk.Gen().ParallelMocAutoUpdated(); + } + } else { + // Moc command failed + { + std::string emsg = "The moc process failed to compile\n "; + emsg += Quoted(SourceFile); + emsg += "\ninto\n "; + emsg += Quoted(BuildFile); + emsg += ".\n"; + emsg += result.ErrorMessage; + wrk.LogCommandError(GeneratorT::MOC, emsg, cmd, result.StdOut); + } + wrk.FileSys().FileRemove(BuildFile); } } - return success; } -bool cmQtAutoGeneratorMocUic::Process(cmMakefile* makefile) +void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk) { - // the program goes through all .cpp files to see which moc files are - // included. It is not really interesting how the moc file is named, but - // what file the moc is created from. Once a moc is included the same moc - // may not be included in the mocs_compilation.cpp file anymore. - // OTOH if there's a header containing Q_OBJECT where no corresponding - // moc file is included anywhere a moc_.cpp file is created and - // included in the mocs_compilation.cpp file. - - if (!this->InitInfoFile(makefile)) { - return false; - } - // Read latest settings - this->SettingsFileRead(makefile); + // Compute build file name + BuildFile = wrk.Base().AutogenIncludeDirAbs; + BuildFile += IncludeString; - // Create AUTOGEN include directory - { - std::string const incDirAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, this->AutogenIncludeDir); - if (!cmSystemTools::MakeDirectory(incDirAbs)) { - this->LogFileError(cmQtAutoGen::GEN, incDirAbs, - "Could not create directory"); - return false; - } + if (UpdateRequired(wrk)) { + GenerateUic(wrk); } +} - // Parse source files - for (const auto& item : this->SourceJobs) { - if (!this->ParseSourceFile(item.first, item.second)) { - return false; - } - } - // Parse header files - for (const auto& item : this->HeaderJobs) { - if (!this->ParseHeaderFile(item.first, item.second)) { - return false; +bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) +{ + bool const verbose = wrk.Gen().Log().Verbose(); + + // Test if the build file exists + if (!wrk.FileSys().FileExists(BuildFile)) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " from its source file "; + reason += Quoted(SourceFile); + reason += " because it doesn't exist"; + wrk.LogInfo(GeneratorT::UIC, reason); } - } - // Read missing dependency information - if (!this->ParsePostprocess()) { - return false; + return true; } - // Generate files - if (!this->MocGenerateAll()) { - return false; - } - if (!this->UicGenerateAll()) { - return false; + // Test if the uic settings changed + if (wrk.Uic().SettingsChanged) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " from "; + reason += Quoted(SourceFile); + reason += " because the UIC settings changed"; + wrk.LogInfo(GeneratorT::UIC, reason); + } + return true; } - if (!this->SettingsFileWrite()) { - return false; + // Test if the source file is newer + { + bool isOlder = false; + { + std::string error; + isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); + if (!isOlder && !error.empty()) { + wrk.LogError(GeneratorT::UIC, error); + return false; + } + } + if (isOlder) { + if (verbose) { + std::string reason = "Generating "; + reason += Quoted(BuildFile); + reason += " because it's older than its source file "; + reason += Quoted(SourceFile); + wrk.LogInfo(GeneratorT::UIC, reason); + } + return true; + } } - return true; + return false; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParseSourceFile(std::string const& absFilename, - const SourceJob& job) +void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk) { - std::string contentText; - std::string error; - bool success = this->FileRead(contentText, absFilename, &error); - if (success) { - if (!contentText.empty()) { - if (job.Moc) { - success = this->MocParseSourceContent(absFilename, contentText); - } - if (success && job.Uic) { - success = this->UicParseContent(absFilename, contentText); + // Make sure the parent directory exists + if (wrk.FileSys().MakeParentDirectory(GeneratorT::UIC, BuildFile)) { + // Compose uic command + std::vector cmd; + cmd.push_back(wrk.Uic().Executable); + { + std::vector allOpts = wrk.Uic().TargetOptions; + auto optionIt = wrk.Uic().Options.find(SourceFile); + if (optionIt != wrk.Uic().Options.end()) { + UicMergeOptions(allOpts, optionIt->second, + (wrk.Base().QtVersionMajor == 5)); } + cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); + } + cmd.push_back("-o"); + cmd.push_back(BuildFile); + cmd.push_back(SourceFile); + + ProcessResultT result; + if (wrk.RunProcess(GeneratorT::UIC, result, cmd)) { + // Success } else { - this->LogFileWarning(cmQtAutoGen::GEN, absFilename, - "The source file is empty"); + // Command failed + { + std::string emsg = "The uic process failed to compile\n "; + emsg += Quoted(SourceFile); + emsg += "\ninto\n "; + emsg += Quoted(BuildFile); + emsg += "\nincluded by\n "; + emsg += Quoted(IncluderFile); + emsg += ".\n"; + emsg += result.ErrorMessage; + wrk.LogCommandError(GeneratorT::UIC, emsg, cmd, result.StdOut); + } + wrk.FileSys().FileRemove(BuildFile); } - } else { - this->LogFileError(cmQtAutoGen::GEN, absFilename, - "Could not read the source file: " + error); } - return success; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParseHeaderFile(std::string const& absFilename, - const SourceJob& job) +void cmQtAutoGeneratorMocUic::JobDeleterT::operator()(JobT* job) { - std::string contentText; - std::string error; - bool success = this->FileRead(contentText, absFilename, &error); - if (success) { - if (!contentText.empty()) { - if (job.Moc) { - this->MocParseHeaderContent(absFilename, contentText); - } - if (job.Uic) { - success = this->UicParseContent(absFilename, contentText); - } - } else { - this->LogFileWarning(cmQtAutoGen::GEN, absFilename, - "The header file is empty"); - } - } else { - this->LogFileError(cmQtAutoGen::GEN, absFilename, - "Could not read the header file: " + error); + delete job; +} + +cmQtAutoGeneratorMocUic::WorkerT::WorkerT(cmQtAutoGeneratorMocUic* gen, + uv_loop_t* uvLoop) + : Gen_(gen) +{ + // Initialize uv asynchronous callback for process starting + ProcessRequest_.init(*uvLoop, &WorkerT::UVProcessStart, this); + // Start thread + Thread_ = std::thread(&WorkerT::Loop, this); +} + +cmQtAutoGeneratorMocUic::WorkerT::~WorkerT() +{ + // Join thread + if (Thread_.joinable()) { + Thread_.join(); } - return success; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::ParsePostprocess() +void cmQtAutoGeneratorMocUic::WorkerT::LogInfo( + GeneratorT genType, std::string const& message) const { - bool success = true; - // Read missing dependencies - for (auto& item : this->MocJobsIncluded) { - if (!item->DependsValid) { - std::string content; - std::string error; - if (this->FileRead(content, item->SourceFile, &error)) { - this->MocFindDepends(item->SourceFile, content, item->Depends); - item->DependsValid = true; - } else { - std::string emsg = "Could not read file\n "; - emsg += item->SourceFile; - emsg += "\nrequired by moc include \""; - emsg += item->IncludeString; - emsg += "\".\n"; - emsg += error; - this->LogFileError(cmQtAutoGen::MOC, item->Includer, emsg); - success = false; - break; - } + return Log().Info(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogWarning( + GeneratorT genType, std::string const& message) const +{ + return Log().Warning(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning( + GeneratorT genType, std::string const& filename, + std::string const& message) const +{ + return Log().WarningFile(genType, filename, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogError( + GeneratorT genType, std::string const& message) const +{ + Gen().ParallelRegisterJobError(); + Log().Error(genType, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogFileError( + GeneratorT genType, std::string const& filename, + std::string const& message) const +{ + Gen().ParallelRegisterJobError(); + Log().ErrorFile(genType, filename, message); +} + +void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError( + GeneratorT genType, std::string const& message, + std::vector const& command, std::string const& output) const +{ + Gen().ParallelRegisterJobError(); + Log().ErrorCommand(genType, message, command, output); +} + +bool cmQtAutoGeneratorMocUic::WorkerT::RunProcess( + GeneratorT genType, ProcessResultT& result, + std::vector const& command) +{ + if (command.empty()) { + return false; + } + + // Create process instance + { + std::lock_guard lock(ProcessMutex_); + Process_ = cm::make_unique(); + Process_->setup(&result, true, command, Gen().Base().AutogenBuildDir); + } + + // Send asynchronous process start request to libuv loop + ProcessRequest_.send(); + + // Log command + if (this->Log().Verbose()) { + std::string msg = "Running command:\n"; + msg += QuotedCommand(command); + msg += '\n'; + this->LogInfo(genType, msg); + } + + // Wait until the process has been finished and destroyed + { + std::unique_lock ulock(ProcessMutex_); + while (Process_) { + ProcessCondition_.wait(ulock); } } - return success; + return !result.error(); } -/** - * @brief Tests if the file should be ignored for moc scanning - * @return True if the file should be ignored - */ -bool cmQtAutoGeneratorMocUic::MocSkip(std::string const& absFilename) const +void cmQtAutoGeneratorMocUic::WorkerT::Loop() { - if (this->MocEnabled()) { - // Test if the file name is on the skip list - if (!ListContains(this->MocSkipList, absFilename)) { - return false; + while (true) { + Gen().WorkerSwapJob(JobHandle_); + if (JobHandle_) { + JobHandle_->Process(*this); + } else { + break; } } - return true; } -/** - * @brief Tests if the C++ content requires moc processing - * @return True if moc is required - */ -bool cmQtAutoGeneratorMocUic::MocRequired(std::string const& contentText, - std::string* macroName) +void cmQtAutoGeneratorMocUic::WorkerT::UVProcessStart(uv_async_t* handle) { - for (KeyRegExp& filter : this->MocMacroFilters) { - // Run a simple find string operation before the expensive - // regular expression check - if (contentText.find(filter.Key) != std::string::npos) { - if (filter.RegExp.find(contentText)) { - // Return macro name on demand - if (macroName != nullptr) { - *macroName = filter.Key; - } - return true; - } + auto& wrk = *reinterpret_cast(handle->data); + { + std::lock_guard lock(wrk.ProcessMutex_); + if (wrk.Process_ && !wrk.Process_->IsStarted()) { + wrk.Process_->start(handle->loop, + std::bind(&WorkerT::UVProcessFinished, &wrk)); } } - return false; } -std::string cmQtAutoGeneratorMocUic::MocStringMacros() const +void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished() { - std::string res; - const auto itB = this->MocMacroFilters.cbegin(); - const auto itE = this->MocMacroFilters.cend(); - const auto itL = itE - 1; - auto itC = itB; - for (; itC != itE; ++itC) { - // Separator - if (itC != itB) { - if (itC != itL) { - res += ", "; - } else { - res += " or "; - } + { + std::lock_guard lock(ProcessMutex_); + if (Process_ && Process_->IsFinished()) { + Process_.reset(); } - // Key - res += itC->Key; } - return res; + // Notify idling thread + ProcessCondition_.notify_one(); } -std::string cmQtAutoGeneratorMocUic::MocStringHeaders( - std::string const& fileBase) const +cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() + : Base_(&FileSys()) + , Moc_(&FileSys()) + , Stage_(StageT::SETTINGS_READ) + , JobsRemain_(0) + , JobError_(false) + , JobThreadsAbort_(false) + , MocAutoFileUpdated_(false) { - std::string res = fileBase; - res += ".{"; - res += cmJoin(this->HeaderExtensions, ","); - res += "}"; - return res; + // Precompile regular expressions + Moc_.RegExpInclude.compile( + "[\n][ \t]*#[ \t]*include[ \t]+" + "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); + Uic_.RegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+" + "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); + + // Initialize libuv asynchronous iteration request + UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this); } -std::string cmQtAutoGeneratorMocUic::MocFindIncludedHeader( - std::string const& sourcePath, std::string const& includeBase) const +cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() { - std::string header; - // Search in vicinity of the source - if (!this->FindHeader(header, sourcePath + includeBase)) { - // Search in include directories - for (std::string const& path : this->MocIncludePaths) { - std::string fullPath = path; - fullPath.push_back('/'); - fullPath += includeBase; - if (this->FindHeader(header, fullPath)) { - break; - } - } - } - // Sanitize - if (!header.empty()) { - header = cmSystemTools::GetRealPath(header); - } - return header; } -bool cmQtAutoGeneratorMocUic::MocFindIncludedFile( - std::string& absFile, std::string const& sourcePath, - std::string const& includeString) const +bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) { - bool success = false; - // Search in vicinity of the source - { - std::string testPath = sourcePath; - testPath += includeString; - if (cmSystemTools::FileExists(testPath.c_str())) { - absFile = cmSystemTools::GetRealPath(testPath); - success = true; - } - } - // Search in include directories - if (!success) { - for (std::string const& path : this->MocIncludePaths) { - std::string fullPath = path; - fullPath.push_back('/'); - fullPath += includeString; - if (cmSystemTools::FileExists(fullPath.c_str())) { - absFile = cmSystemTools::GetRealPath(fullPath); - success = true; - break; + // -- Meta + Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); + + // Utility lambdas + auto InfoGet = [makefile](const char* key) { + return makefile->GetSafeDefinition(key); + }; + auto InfoGetBool = [makefile](const char* key) { + return makefile->IsOn(key); + }; + auto InfoGetList = [makefile](const char* key) -> std::vector { + std::vector list; + cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list); + return list; + }; + auto InfoGetLists = + [makefile](const char* key) -> std::vector> { + std::vector> lists; + { + std::string const value = makefile->GetSafeDefinition(key); + std::string::size_type pos = 0; + while (pos < value.size()) { + std::string::size_type next = value.find(ListSep, pos); + std::string::size_type length = + (next != std::string::npos) ? next - pos : value.size() - pos; + // Remove enclosing braces + if (length >= 2) { + std::string::const_iterator itBeg = value.begin() + (pos + 1); + std::string::const_iterator itEnd = itBeg + (length - 2); + { + std::string subValue(itBeg, itEnd); + std::vector list; + cmSystemTools::ExpandListArgument(subValue, list); + lists.push_back(std::move(list)); + } + } + pos += length; + pos += ListSep.size(); } } - } - return success; -} - -bool cmQtAutoGeneratorMocUic::MocDependFilterPush(std::string const& key, - std::string const& regExp) -{ - std::string error; - if (!key.empty()) { - if (!regExp.empty()) { - KeyRegExp filter; - filter.Key = key; - if (filter.RegExp.compile(regExp)) { - this->MocDependFilters.push_back(std::move(filter)); - } else { - error = "Regular expression compiling failed"; - } - } else { - error = "Regular expression is empty"; + return lists; + }; + auto InfoGetConfig = [makefile, this](const char* key) -> std::string { + const char* valueConf = nullptr; + { + std::string keyConf = key; + keyConf += '_'; + keyConf += InfoConfig(); + valueConf = makefile->GetDefinition(keyConf); } - } else { - error = "Key is empty"; - } - if (!error.empty()) { - std::string emsg = "AUTOMOC_DEPEND_FILTERS: "; - emsg += error; - emsg += "\n"; - emsg += " Key: "; - emsg += cmQtAutoGen::Quoted(key); - emsg += "\n"; - emsg += " RegExp: "; - emsg += cmQtAutoGen::Quoted(regExp); - emsg += "\n"; - this->LogError(cmQtAutoGen::MOC, emsg); + if (valueConf == nullptr) { + valueConf = makefile->GetSafeDefinition(key); + } + return std::string(valueConf); + }; + auto InfoGetConfigList = + [&InfoGetConfig](const char* key) -> std::vector { + std::vector list; + cmSystemTools::ExpandListArgument(InfoGetConfig(key), list); + return list; + }; + + // -- Read info file + if (!makefile->ReadListFile(InfoFile().c_str())) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), "File processing failed"); return false; } - return true; -} -void cmQtAutoGeneratorMocUic::MocFindDepends(std::string const& absFilename, - std::string const& contentText, - std::set& depends) -{ - if (this->MocDependFilters.empty() && contentText.empty()) { - return; - } - - std::vector matches; - for (KeyRegExp& filter : this->MocDependFilters) { - // Run a simple find string check - if (contentText.find(filter.Key) != std::string::npos) { - // Run the expensive regular expression check loop - const char* contentChars = contentText.c_str(); - while (filter.RegExp.find(contentChars)) { - std::string match = filter.RegExp.match(1); - if (!match.empty()) { - matches.emplace_back(std::move(match)); - } - contentChars += filter.RegExp.end(); - } - } - } + // -- Meta + Base_.MultiConfig = MultiConfigType(InfoGet("AM_MULTI_CONFIG")); - if (!matches.empty()) { - std::string const sourcePath = SubDirPrefix(absFilename); - for (std::string const& match : matches) { - // Find the dependency file - std::string incFile; - if (this->MocFindIncludedFile(incFile, sourcePath, match)) { - depends.insert(incFile); - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Found dependency:\n " + - cmQtAutoGen::Quoted(absFilename) + "\n " + - cmQtAutoGen::Quoted(incFile)); - } - } else { - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, - "Could not find dependency file " + - cmQtAutoGen::Quoted(match)); - } - } + Base_.ConfigSuffix = InfoGetConfig("AM_CONFIG_SUFFIX"); + if (Base_.ConfigSuffix.empty()) { + Base_.ConfigSuffix = "_"; + Base_.ConfigSuffix += InfoConfig(); } -} - -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::MocParseSourceContent( - std::string const& absFilename, std::string const& contentText) -{ - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename); - } - - auto AddJob = [this, &absFilename](std::string const& sourceFile, - std::string const& includeString, - std::string const* content) { - auto job = cm::make_unique(); - job->SourceFile = sourceFile; - job->BuildFileRel = this->AutogenIncludeDir; - job->BuildFileRel += includeString; - job->Includer = absFilename; - job->IncludeString = includeString; - job->DependsValid = (content != nullptr); - if (job->DependsValid) { - this->MocFindDepends(sourceFile, *content, job->Depends); - } - this->MocJobsIncluded.push_back(std::move(job)); - }; - struct MocInc - { - std::string Inc; // full include string - std::string Dir; // include string directory - std::string Base; // include string file base - }; + SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE"); + if (SettingsFile_.empty()) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), "Settings file name missing"); + return false; + } - // Extract moc includes from file - std::vector mocIncsUsc; - std::vector mocIncsDot; { - const char* contentChars = contentText.c_str(); - if (strstr(contentChars, "moc") != nullptr) { - while (this->MocRegExpInclude.find(contentChars)) { - std::string incString = this->MocRegExpInclude.match(1); - std::string incDir(SubDirPrefix(incString)); - std::string incBase = - cmSystemTools::GetFilenameWithoutLastExtension(incString); - if (cmHasLiteralPrefix(incBase, "moc_")) { - // moc_.cxx - // Remove the moc_ part from the base name - mocIncsUsc.push_back(MocInc{ std::move(incString), std::move(incDir), - incBase.substr(4) }); - } else { - // .moc - mocIncsDot.push_back(MocInc{ std::move(incString), std::move(incDir), - std::move(incBase) }); - } - // Forward content pointer - contentChars += this->MocRegExpInclude.end(); - } + unsigned long num = Base_.NumThreads; + if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL"), &num)) { + num = std::max(num, 1); + num = std::min(num, ParallelMax); + Base_.NumThreads = static_cast(num); } } - std::string selfMacroName; - const bool selfRequiresMoc = this->MocRequired(contentText, &selfMacroName); - - // Check if there is anything to do - if (!selfRequiresMoc && mocIncsUsc.empty() && mocIncsDot.empty()) { - return true; + // - Files and directories + Base_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR"); + Base_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR"); + Base_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR"); + Base_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR"); + Base_.IncludeProjectDirsBefore = + InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); + Base_.AutogenBuildDir = InfoGet("AM_BUILD_DIR"); + if (Base_.AutogenBuildDir.empty()) { + Log().ErrorFile(GeneratorT::GEN, InfoFile(), + "Autogen build directory missing"); + return false; } - // Scan file variables - std::string const scanFileDir = SubDirPrefix(absFilename); - std::string const scanFileBase = - cmSystemTools::GetFilenameWithoutLastExtension(absFilename); - // Relaxed mode variables - bool ownDotMocIncluded = false; - std::string ownMocUscInclude; - std::string ownMocUscHeader; - - // Process moc_.cxx includes - for (const MocInc& mocInc : mocIncsUsc) { - std::string const header = - this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (this->MocSkip(header)) { - continue; - } - // Register moc job - AddJob(header, mocInc.Inc, nullptr); - // Store meta information for relaxed mode - if (this->MocRelaxedMode && (mocInc.Base == scanFileBase)) { - ownMocUscInclude = mocInc.Inc; - ownMocUscHeader = header; - } - } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but could not find the header "; - emsg += cmQtAutoGen::Quoted(this->MocStringHeaders(mocInc.Base)); - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; + // - Qt environment + { + unsigned long qtv = Base_.QtVersionMajor; + if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR"), &qtv)) { + Base_.QtVersionMajor = static_cast(qtv); } } - // Process .moc includes - for (const MocInc& mocInc : mocIncsDot) { - const bool ownMoc = (mocInc.Base == scanFileBase); - if (this->MocRelaxedMode) { - // Relaxed mode - if (selfRequiresMoc && ownMoc) { - // Add self - AddJob(absFilename, mocInc.Inc, &contentText); - ownDotMocIncluded = true; - } else { - // In relaxed mode try to find a header instead but issue a warning. - // This is for KDE4 compatibility - std::string const header = - this->MocFindIncludedHeader(scanFileDir, mocInc.Dir + mocInc.Base); - if (!header.empty()) { - // Check if header is skipped - if (this->MocSkip(header)) { - continue; - } - // Register moc job - AddJob(header, mocInc.Inc, nullptr); - if (!selfRequiresMoc) { - if (ownMoc) { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but does not contain a "; - emsg += this->MocStringMacros(); - emsg += " macro.\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(header); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += " for a compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); + // - Moc + Moc_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE"); + Moc_.Enabled = !Moc().Executable.empty(); + if (Moc().Enabled) { + { + auto lst = InfoGetList("AM_MOC_SKIP"); + Moc_.SkipList.insert(lst.begin(), lst.end()); + } + Moc_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS"); +#ifdef _WIN32 + { + std::string win32("WIN32"); + auto itB = Moc().Definitions.cbegin(); + auto itE = Moc().Definitions.cend(); + if (std::find(itB, itE, win32) == itE) { + Moc_.Definitions.emplace_back(std::move(win32)); + } + } +#endif + Moc_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES"); + Moc_.Options = InfoGetList("AM_MOC_OPTIONS"); + Moc_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE"); + for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) { + Moc_.MacroFilters.emplace_back( + item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]")); + } + { + auto pushFilter = [this](std::string const& key, std::string const& exp, + std::string& error) { + if (!key.empty()) { + if (!exp.empty()) { + Moc_.DependFilters.push_back(KeyExpT()); + KeyExpT& filter(Moc_.DependFilters.back()); + if (filter.Exp.compile(exp)) { + filter.Key = key; } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += " instead of "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += ".\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(header); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted("moc_" + mocInc.Base + ".cpp"); - emsg += " for compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); + error = "Regular expression compiling failed"; } + } else { + error = "Regular expression is empty"; } } else { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", which seems to be the moc file from a different " - "source file. CMake also could not find a matching " - "header."; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; + error = "Key is empty"; } - } - } else { - // Strict mode - if (ownMoc) { - // Include self - AddJob(absFilename, mocInc.Inc, &contentText); - ownDotMocIncluded = true; - // Accept but issue a warning if moc isn't required - if (!selfRequiresMoc) { - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", but does not contain a "; - emsg += this->MocStringMacros(); - emsg += " macro."; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); + if (!error.empty()) { + error = ("AUTOMOC_DEPEND_FILTERS: " + error); + error += "\n"; + error += " Key: "; + error += Quoted(key); + error += "\n"; + error += " Exp: "; + error += Quoted(exp); + error += "\n"; } - } else { - // Don't allow .moc include other than self in strict mode - std::string emsg = "The file includes the moc file "; - emsg += cmQtAutoGen::Quoted(mocInc.Inc); - emsg += ", which seems to be the moc file from a different " - "source file.\nThis is not supported. Include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += " to run moc on this source file."; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; - } - } - } + }; - if (selfRequiresMoc && !ownDotMocIncluded) { - // In this case, check whether the scanned file itself contains a Q_OBJECT. - // If this is the case, the moc_foo.cpp should probably be generated from - // foo.cpp instead of foo.h, because otherwise it won't build. - // But warn, since this is not how it is supposed to be used. - if (this->MocRelaxedMode && !ownMocUscInclude.empty()) { - // This is for KDE4 compatibility: - std::string emsg = "The file contains a "; - emsg += selfMacroName; - emsg += " macro, but does not include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += ". Instead it includes "; - emsg += cmQtAutoGen::Quoted(ownMocUscInclude); - emsg += ".\nRunning moc on\n "; - emsg += cmQtAutoGen::Quoted(absFilename); - emsg += "!\nBetter include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += " for compatibility with strict mode.\n" - "(CMAKE_AUTOMOC_RELAXED_MODE warning)"; - this->LogFileWarning(cmQtAutoGen::MOC, absFilename, emsg); - - // Remove own header job + std::string error; + // Insert default filter for Q_PLUGIN_METADATA + if (Base().QtVersionMajor != 4) { + pushFilter("Q_PLUGIN_METADATA", "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" + "[^\\)]*FILE[ \t]*\"([^\"]+)\"", + error); + } + // Insert user defined dependency filters { - auto itC = this->MocJobsIncluded.begin(); - auto itE = this->MocJobsIncluded.end(); - for (; itC != itE; ++itC) { - if ((*itC)->SourceFile == ownMocUscHeader) { - if ((*itC)->IncludeString == ownMocUscInclude) { - this->MocJobsIncluded.erase(itC); + std::vector flts = InfoGetList("AM_MOC_DEPEND_FILTERS"); + if ((flts.size() % 2) == 0) { + for (std::vector::iterator itC = flts.begin(), + itE = flts.end(); + itC != itE; itC += 2) { + pushFilter(*itC, *(itC + 1), error); + if (!error.empty()) { break; } } + } else { + Log().ErrorFile( + GeneratorT::MOC, InfoFile(), + "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2"); + return false; } } - // Add own source job - AddJob(absFilename, ownMocUscInclude, &contentText); - } else { - // Otherwise always error out since it will not compile: - std::string emsg = "The file contains a "; - emsg += selfMacroName; - emsg += " macro, but does not include "; - emsg += cmQtAutoGen::Quoted(scanFileBase + ".moc"); - emsg += "!\nConsider to\n - add #include \""; - emsg += scanFileBase; - emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; - this->LogFileError(cmQtAutoGen::MOC, absFilename, emsg); - return false; + if (!error.empty()) { + Log().ErrorFile(GeneratorT::MOC, InfoFile(), error); + return false; + } + } + Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); + // Install moc predefs job + if (!Moc().PredefsCmd.empty()) { + JobQueues_.MocPredefs.emplace_back(new JobMocPredefsT()); } } - return true; -} -void cmQtAutoGeneratorMocUic::MocParseHeaderContent( - std::string const& absFilename, std::string const& contentText) -{ - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, "Checking: " + absFilename); - } - - auto const fit = - std::find_if(this->MocJobsIncluded.cbegin(), this->MocJobsIncluded.cend(), - [&absFilename](std::unique_ptr const& job) { - return job->SourceFile == absFilename; - }); - if (fit == this->MocJobsIncluded.cend()) { - if (this->MocRequired(contentText)) { - auto job = cm::make_unique(); - job->SourceFile = absFilename; - { - std::string& bld = job->BuildFileRel; - bld = this->FilePathChecksum.getPart(absFilename); - bld += '/'; - bld += "moc_"; - bld += cmSystemTools::GetFilenameWithoutLastExtension(absFilename); - if (this->MultiConfig != cmQtAutoGen::SINGLE) { - bld += this->ConfigSuffix; - } - bld += ".cpp"; + // - Uic + Uic_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE"); + Uic_.Enabled = !Uic().Executable.empty(); + if (Uic().Enabled) { + { + auto lst = InfoGetList("AM_UIC_SKIP"); + Uic_.SkipList.insert(lst.begin(), lst.end()); + } + Uic_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS"); + Uic_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS"); + { + auto sources = InfoGetList("AM_UIC_OPTIONS_FILES"); + auto options = InfoGetLists("AM_UIC_OPTIONS_OPTIONS"); + // Compare list sizes + if (sources.size() != options.size()) { + std::ostringstream ost; + ost << "files/options lists sizes missmatch (" << sources.size() << "/" + << options.size() << ")"; + Log().ErrorFile(GeneratorT::UIC, InfoFile(), ost.str()); + return false; + } + auto fitEnd = sources.cend(); + auto fit = sources.begin(); + auto oit = options.begin(); + while (fit != fitEnd) { + Uic_.Options[*fit] = std::move(*oit); + ++fit; + ++oit; } - this->MocFindDepends(absFilename, contentText, job->Depends); - this->MocJobsAuto.push_back(std::move(job)); } } -} - -bool cmQtAutoGeneratorMocUic::MocGenerateAll() -{ - if (!this->MocEnabled()) { - return true; - } - // Look for name collisions in included moc files + // Initialize source file jobs { - bool collision = false; - std::map> collisions; - for (auto const& job : this->MocJobsIncluded) { - auto& list = collisions[job->IncludeString]; - if (!list.empty()) { - collision = true; - } - list.push_back(job.get()); - } - if (collision) { - std::string emsg = - "Included moc files with the same name will be " - "generated from different sources.\n" - "Consider to\n" - " - not include the \"moc_.cpp\" file\n" - " - add a directory prefix to a \".moc\" include " - "(e.g \"sub/.moc\")\n" - " - rename the source file(s)\n" - "Include conflicts\n" - "-----------------\n"; - const auto& colls = collisions; - for (auto const& coll : colls) { - if (coll.second.size() > 1) { - emsg += cmQtAutoGen::Quoted(coll.first); - emsg += " included in\n"; - for (const MocJobIncluded* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->Includer); - emsg += "\n"; - } - emsg += "would be generated from\n"; - for (const MocJobIncluded* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->SourceFile); - emsg += "\n"; + std::hash stringHash; + std::set uniqueHeaders; + + // Add header jobs + for (std::string& hdr : InfoGetList("AM_HEADERS")) { + const bool moc = !Moc().skipped(hdr); + const bool uic = !Uic().skipped(hdr); + if ((moc || uic) && uniqueHeaders.emplace(stringHash(hdr)).second) { + JobQueues_.Headers.emplace_back( + new JobParseT(std::move(hdr), moc, uic, true)); + } + } + // Add source jobs + { + std::vector sources = InfoGetList("AM_SOURCES"); + // Add header(s) for the source file + for (std::string& src : sources) { + const bool srcMoc = !Moc().skipped(src); + const bool srcUic = !Uic().skipped(src); + if (!srcMoc && !srcUic) { + continue; + } + // Search for the default header file and a private header + { + std::array bases; + bases[0] = SubDirPrefix(src); + bases[0] += cmSystemTools::GetFilenameWithoutLastExtension(src); + bases[1] = bases[0]; + bases[1] += "_p"; + for (std::string const& headerBase : bases) { + std::string header; + if (Base().FindHeader(header, headerBase)) { + const bool moc = srcMoc && !Moc().skipped(header); + const bool uic = srcUic && !Uic().skipped(header); + if ((moc || uic) && + uniqueHeaders.emplace(stringHash(header)).second) { + JobQueues_.Headers.emplace_back( + new JobParseT(std::move(header), moc, uic, true)); + } + } } } + // Add source job + JobQueues_.Sources.emplace_back( + new JobParseT(std::move(src), srcMoc, srcUic)); } - this->LogError(cmQtAutoGen::MOC, emsg); - return false; } } - // (Re)generate moc_predefs.h on demand - if (!this->MocPredefsCmd.empty()) { - if (this->MocSettingsChanged || - !cmSystemTools::FileExists(this->MocPredefsFileAbs)) { - if (this->GetVerbose()) { - this->LogBold("Generating MOC predefs " + this->MocPredefsFileRel); + // Init derived information + // ------------------------ + + // Init file path checksum generator + Base_.FilePathChecksum.setupParentDirs( + Base().CurrentSourceDir, Base().CurrentBinaryDir, Base().ProjectSourceDir, + Base().ProjectBinaryDir); + + // include directory + Base_.AutogenIncludeDirRel = "include"; + if (Base().MultiConfig != MultiConfigT::SINGLE) { + Base_.AutogenIncludeDirRel += Base().ConfigSuffix; + } + Base_.AutogenIncludeDirRel += "/"; + Base_.AutogenIncludeDirAbs = + Base_.AbsoluteBuildPath(Base().AutogenIncludeDirRel); + + // Moc variables + if (Moc().Enabled) { + // Mocs compilation file + Moc_.CompFileRel = "mocs_compilation"; + if (Base_.MultiConfig == MultiConfigT::MULTI) { + Moc_.CompFileRel += Base().ConfigSuffix; + } + Moc_.CompFileRel += ".cpp"; + Moc_.CompFileAbs = Base_.AbsoluteBuildPath(Moc().CompFileRel); + + // Moc predefs file + if (!Moc_.PredefsCmd.empty()) { + Moc_.PredefsFileRel = "moc_predefs"; + if (Base_.MultiConfig != MultiConfigT::SINGLE) { + Moc_.PredefsFileRel += Base().ConfigSuffix; } + Moc_.PredefsFileRel += ".h"; + Moc_.PredefsFileAbs = Base_.AbsoluteBuildPath(Moc().PredefsFileRel); + } - std::string output; + // Sort include directories on demand + if (Base().IncludeProjectDirsBefore) { + // Move strings to temporary list + std::list includes; + includes.insert(includes.end(), Moc().IncludePaths.begin(), + Moc().IncludePaths.end()); + Moc_.IncludePaths.clear(); + Moc_.IncludePaths.reserve(includes.size()); + // Append project directories only { - // Compose command - std::vector cmd = this->MocPredefsCmd; - // Add includes - cmd.insert(cmd.end(), this->MocIncludes.begin(), - this->MocIncludes.end()); - // Add definitions - for (std::string const& def : this->MocDefinitions) { - cmd.push_back("-D" + def); - } - // Execute command - if (!this->RunCommand(cmd, output)) { - this->LogCommandError(cmQtAutoGen::MOC, - "moc_predefs generation failed", cmd, output); - return false; + std::array const movePaths = { + { &Base().ProjectBinaryDir, &Base().ProjectSourceDir } + }; + for (std::string const* ppath : movePaths) { + std::list::iterator it = includes.begin(); + while (it != includes.end()) { + std::string const& path = *it; + if (cmSystemTools::StringStartsWith(path, ppath->c_str())) { + Moc_.IncludePaths.push_back(path); + it = includes.erase(it); + } else { + ++it; + } + } } } - - // (Re)write predefs file only on demand - if (this->FileDiffers(this->MocPredefsFileAbs, output)) { - if (this->FileWrite(cmQtAutoGen::MOC, this->MocPredefsFileAbs, - output)) { - this->MocPredefsChanged = true; - } else { - this->LogFileError(cmQtAutoGen::MOC, this->MocPredefsFileAbs, - "moc_predefs file writing failed"); - return false; - } - } else { - // Touch to update the time stamp - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, - "Touching moc_predefs " + this->MocPredefsFileRel); + // Append remaining directories + Moc_.IncludePaths.insert(Moc_.IncludePaths.end(), includes.begin(), + includes.end()); + } + // Compose moc includes list + { + std::set frameworkPaths; + for (std::string const& path : Moc().IncludePaths) { + Moc_.Includes.push_back("-I" + path); + // Extract framework path + if (cmHasLiteralSuffix(path, ".framework/Headers")) { + // Go up twice to get to the framework root + std::vector pathComponents; + cmSystemTools::SplitPath(path, pathComponents); + std::string frameworkPath = cmSystemTools::JoinPath( + pathComponents.begin(), pathComponents.end() - 2); + frameworkPaths.insert(frameworkPath); } - cmSystemTools::Touch(this->MocPredefsFileAbs, false); + } + // Append framework includes + for (std::string const& path : frameworkPaths) { + Moc_.Includes.push_back("-F"); + Moc_.Includes.push_back(path); } } - - // Add moc_predefs.h to moc file dependencies - for (auto const& item : this->MocJobsIncluded) { - item->Depends.insert(this->MocPredefsFileAbs); - } - for (auto const& item : this->MocJobsAuto) { - item->Depends.insert(this->MocPredefsFileAbs); + // Setup single list with all options + { + // Add includes + Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Includes.begin(), + Moc().Includes.end()); + // Add definitions + for (std::string const& def : Moc().Definitions) { + Moc_.AllOptions.push_back("-D" + def); + } + // Add options + Moc_.AllOptions.insert(Moc_.AllOptions.end(), Moc().Options.begin(), + Moc().Options.end()); } } - // Generate moc files that are included by source files. - for (auto const& item : this->MocJobsIncluded) { - if (!this->MocGenerateFile(*item)) { - return false; - } - } - // Generate moc files that are _not_ included by source files. - bool autoNameGenerated = false; - for (auto const& item : this->MocJobsAuto) { - if (!this->MocGenerateFile(*item, &autoNameGenerated)) { + return true; +} + +bool cmQtAutoGeneratorMocUic::Process() +{ + // Run libuv event loop + UVRequest().send(); + if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) { + if (JobError_) { return false; } + } else { + return false; } + return true; +} - // Compose mocs compilation file content - { - std::string mocs = - "// This file is autogenerated. Changes will be overwritten.\n"; - if (this->MocJobsAuto.empty()) { - // Placeholder content - mocs += - "// No files found that require moc or the moc files are included\n"; - mocs += "enum some_compilers { need_more_than_nothing };\n"; - } else { - // Valid content - for (const auto& item : this->MocJobsAuto) { - mocs += "#include \""; - mocs += item->BuildFileRel; - mocs += "\"\n"; - } - } +void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle) +{ + reinterpret_cast(handle->data)->PollStage(); +} - if (this->FileDiffers(this->MocCompFileAbs, mocs)) { - // Actually write mocs compilation file - if (this->GetVerbose()) { - this->LogBold("Generating MOC compilation " + this->MocCompFileRel); +void cmQtAutoGeneratorMocUic::PollStage() +{ + switch (Stage_) { + case StageT::SETTINGS_READ: + SettingsFileRead(); + SetStage(StageT::CREATE_DIRECTORIES); + break; + case StageT::CREATE_DIRECTORIES: + CreateDirectories(); + SetStage(StageT::PARSE_SOURCES); + break; + case StageT::PARSE_SOURCES: + if (ThreadsStartJobs(JobQueues_.Sources)) { + SetStage(StageT::PARSE_HEADERS); } - if (!this->FileWrite(cmQtAutoGen::MOC, this->MocCompFileAbs, mocs)) { - this->LogFileError(cmQtAutoGen::MOC, this->MocCompFileAbs, - "mocs compilation file writing failed"); - return false; + break; + case StageT::PARSE_HEADERS: + if (ThreadsStartJobs(JobQueues_.Headers)) { + SetStage(StageT::MOC_PREDEFS); } - } else if (autoNameGenerated) { - // Only touch mocs compilation file - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::MOC, - "Touching mocs compilation " + this->MocCompFileRel); + break; + case StageT::MOC_PREDEFS: + if (ThreadsStartJobs(JobQueues_.MocPredefs)) { + SetStage(StageT::MOC_PROCESS); } - cmSystemTools::Touch(this->MocCompFileAbs, false); - } + break; + case StageT::MOC_PROCESS: + if (ThreadsStartJobs(JobQueues_.Moc)) { + SetStage(StageT::MOCS_COMPILATION); + } + break; + case StageT::MOCS_COMPILATION: + if (ThreadsJobsDone()) { + MocGenerateCompilation(); + SetStage(StageT::UIC_PROCESS); + } + break; + case StageT::UIC_PROCESS: + if (ThreadsStartJobs(JobQueues_.Uic)) { + SetStage(StageT::SETTINGS_WRITE); + } + break; + case StageT::SETTINGS_WRITE: + SettingsFileWrite(); + SetStage(StageT::FINISH); + break; + case StageT::FINISH: + if (ThreadsJobsDone()) { + // Clear all libuv handles + ThreadsStop(); + UVRequest().reset(); + // Set highest END stage manually + Stage_ = StageT::END; + } + break; + case StageT::END: + break; } - - return true; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::MocGenerateFile(const MocJobAuto& mocJob, - bool* generated) +void cmQtAutoGeneratorMocUic::SetStage(StageT stage) { - bool success = true; - - std::string const mocFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, mocJob.BuildFileRel); - - bool generate = false; - std::string generateReason; - if (!generate && !cmSystemTools::FileExists(mocFileAbs.c_str())) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from its source file "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because it doesn't exist"; - } - generate = true; + if (JobError_) { + stage = StageT::FINISH; } - if (!generate && this->MocSettingsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because the MOC settings changed"; - } - generate = true; + // Only allow to increase the stage + if (Stage_ < stage) { + Stage_ = stage; + UVRequest().send(); } - if (!generate && this->MocPredefsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because moc_predefs.h changed"; +} + +void cmQtAutoGeneratorMocUic::SettingsFileRead() +{ + // Compose current settings strings + { + cmCryptoHash crypt(cmCryptoHash::AlgoSHA256); + std::string const sep(" ~~~ "); + if (Moc_.Enabled) { + std::string str; + str += Moc().Executable; + str += sep; + str += cmJoin(Moc().AllOptions, ";"); + str += sep; + str += Base().IncludeProjectDirsBefore ? "TRUE" : "FALSE"; + str += sep; + str += cmJoin(Moc().PredefsCmd, ";"); + str += sep; + SettingsStringMoc_ = crypt.HashString(str); } - generate = true; - } - if (!generate) { - std::string error; - if (FileIsOlderThan(mocFileAbs, mocJob.SourceFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " because it's older than its source file "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - } - generate = true; - } else { - if (!error.empty()) { - this->LogError(cmQtAutoGen::MOC, error); - success = false; + if (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); } } - if (success && !generate) { - // Test if a dependency file is newer - std::string error; - for (std::string const& depFile : mocJob.Depends) { - if (FileIsOlderThan(mocFileAbs, depFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(mocFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(mocJob.SourceFile); - generateReason += " because it is older than "; - generateReason += cmQtAutoGen::Quoted(depFile); + + // Read old settings and compare + { + std::string content; + if (FileSys().FileRead(content, SettingsFile_)) { + if (Moc().Enabled) { + if (SettingsStringMoc_ != SettingsFind(content, "moc")) { + Moc_.SettingsChanged = true; } - generate = true; - break; } - if (!error.empty()) { - this->LogError(cmQtAutoGen::MOC, error); - success = false; - break; + if (Uic().Enabled) { + if (SettingsStringUic_ != SettingsFind(content, "uic")) { + Uic_.SettingsChanged = true; + } + } + // In case any setting changed remove the old settings file. + // This triggers a full rebuild on the next run if the current + // build is aborted before writing the current settings in the end. + if (Moc().SettingsChanged || Uic().SettingsChanged) { + FileSys().FileRemove(SettingsFile_); + } + } else { + // Settings file read failed + if (Moc().Enabled) { + Moc_.SettingsChanged = true; + } + if (Uic().Enabled) { + Uic_.SettingsChanged = true; } } } +} - if (generate) { - // Log - if (this->GetVerbose()) { - this->LogBold("Generating MOC source " + mocJob.BuildFileRel); - this->LogInfo(cmQtAutoGen::MOC, generateReason); +void cmQtAutoGeneratorMocUic::SettingsFileWrite() +{ + std::lock_guard jobsLock(JobsMutex_); + // Only write if any setting changed + if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) { + if (Log().Verbose()) { + Log().Info(GeneratorT::GEN, + "Writing settings file " + Quoted(SettingsFile_)); } - - // Make sure the parent directory exists - if (this->MakeParentDirectory(cmQtAutoGen::MOC, mocFileAbs)) { - // Compose moc command - std::vector cmd; - cmd.push_back(this->MocExecutable); - // Add options - cmd.insert(cmd.end(), this->MocAllOptions.begin(), - this->MocAllOptions.end()); - // Add predefs include - if (!this->MocPredefsFileAbs.empty()) { - cmd.push_back("--include"); - cmd.push_back(this->MocPredefsFileAbs); - } - cmd.push_back("-o"); - cmd.push_back(mocFileAbs); - cmd.push_back(mocJob.SourceFile); - - // Execute moc command - std::string output; - if (this->RunCommand(cmd, output)) { - // Success - if (generated != nullptr) { - *generated = true; - } - } else { - // Moc command failed - { - std::string emsg = "moc failed for\n "; - emsg += cmQtAutoGen::Quoted(mocJob.SourceFile); - this->LogCommandError(cmQtAutoGen::MOC, emsg, cmd, output); + // Compose settings file content + std::string content; + { + auto SettingAppend = [&content](const char* key, + std::string const& value) { + if (!value.empty()) { + content += key; + content += ':'; + content += value; + content += '\n'; } - cmSystemTools::RemoveFile(mocFileAbs); - success = false; - } - } else { - // Parent directory creation failed - success = false; + }; + SettingAppend("moc", SettingsStringMoc_); + SettingAppend("uic", SettingsStringUic_); + } + // Write settings file + if (!FileSys().FileWrite(GeneratorT::GEN, SettingsFile_, content)) { + Log().ErrorFile(GeneratorT::GEN, SettingsFile_, + "Settings file writing failed"); + // Remove old settings file to trigger a full rebuild on the next run + FileSys().FileRemove(SettingsFile_); + RegisterJobError(); } } - return success; } -/** - * @brief Tests if the file name is in the skip list - */ -bool cmQtAutoGeneratorMocUic::UicSkip(std::string const& absFilename) const +void cmQtAutoGeneratorMocUic::CreateDirectories() { - if (this->UicEnabled()) { - // Test if the file name is on the skip list - if (!ListContains(this->UicSkipList, absFilename)) { - return false; - } + // Create AUTOGEN include directory + if (!FileSys().MakeDirectory(GeneratorT::GEN, Base().AutogenIncludeDirAbs)) { + RegisterJobError(); } - return true; } -bool cmQtAutoGeneratorMocUic::UicParseContent(std::string const& absFilename, - std::string const& contentText) +bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue) { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::UIC, "Checking: " + absFilename); - } + bool done = false; + std::size_t queueSize = queue.size(); - std::vector includes; - // Extracte includes + // Change the active queue { - const char* contentChars = contentText.c_str(); - if (strstr(contentChars, "ui_") != nullptr) { - while (this->UicRegExpInclude.find(contentChars)) { - includes.push_back(this->UicRegExpInclude.match(1)); - contentChars += this->UicRegExpInclude.end(); + std::lock_guard jobsLock(JobsMutex_); + // Check if there are still unfinished jobs from the previous queue + if (JobsRemain_ == 0) { + if (!JobThreadsAbort_) { + JobQueue_.swap(queue); + JobsRemain_ = queueSize; + } else { + // Abort requested + queue.clear(); + queueSize = 0; } + done = true; } } - for (std::string const& includeString : includes) { - std::string uiInputFile; - if (!UicFindIncludedFile(uiInputFile, absFilename, includeString)) { - return false; - } - // Check if this file should be skipped - if (this->UicSkip(uiInputFile)) { - continue; - } - // Check if the job already exists - bool jobExists = false; - for (const auto& job : this->UicJobs) { - if ((job->SourceFile == uiInputFile) && - (job->IncludeString == includeString)) { - jobExists = true; - break; + if (done && (queueSize != 0)) { + // Start new threads on demand + if (Workers_.empty()) { + Workers_.resize(Base().NumThreads); + for (auto& item : Workers_) { + item = cm::make_unique(this, UVLoop()); + } + } else { + // Notify threads + if (queueSize == 1) { + JobsConditionRead_.notify_one(); + } else { + JobsConditionRead_.notify_all(); } - } - if (!jobExists) { - auto job = cm::make_unique(); - job->SourceFile = uiInputFile; - job->BuildFileRel = this->AutogenIncludeDir; - job->BuildFileRel += includeString; - job->Includer = absFilename; - job->IncludeString = includeString; - this->UicJobs.push_back(std::move(job)); } } - return true; + return done; } -bool cmQtAutoGeneratorMocUic::UicFindIncludedFile( - std::string& absFile, std::string const& sourceFile, - std::string const& includeString) +void cmQtAutoGeneratorMocUic::ThreadsStop() { - bool success = false; - std::string searchFile = - cmSystemTools::GetFilenameWithoutLastExtension(includeString).substr(3); - searchFile += ".ui"; - // Collect search paths list - std::vector testFiles; - { - std::string const searchPath = SubDirPrefix(includeString); - - std::string searchFileFull; - if (!searchPath.empty()) { - searchFileFull = searchPath; - searchFileFull += searchFile; - } - // Vicinity of the source + if (!Workers_.empty()) { + // Clear all jobs { - std::string const sourcePath = SubDirPrefix(sourceFile); - testFiles.push_back(sourcePath + searchFile); - if (!searchPath.empty()) { - testFiles.push_back(sourcePath + searchFileFull); - } - } - // AUTOUIC search paths - if (!this->UicSearchPaths.empty()) { - for (std::string const& sPath : this->UicSearchPaths) { - testFiles.push_back((sPath + "/").append(searchFile)); - } - if (!searchPath.empty()) { - for (std::string const& sPath : this->UicSearchPaths) { - testFiles.push_back((sPath + "/").append(searchFileFull)); - } - } - } + std::lock_guard jobsLock(JobsMutex_); + JobThreadsAbort_ = true; + JobsRemain_ -= JobQueue_.size(); + JobQueue_.clear(); + + JobQueues_.Sources.clear(); + JobQueues_.Headers.clear(); + JobQueues_.MocPredefs.clear(); + JobQueues_.Moc.clear(); + JobQueues_.Uic.clear(); + } + // Wake threads + JobsConditionRead_.notify_all(); + // Join and clear threads + Workers_.clear(); } +} - // Search for the .ui file! - for (std::string const& testFile : testFiles) { - if (cmSystemTools::FileExists(testFile.c_str())) { - absFile = cmSystemTools::GetRealPath(testFile); - success = true; - break; - } - } +bool cmQtAutoGeneratorMocUic::ThreadsJobsDone() +{ + std::lock_guard jobsLock(JobsMutex_); + return (JobsRemain_ == 0); +} - // Log error - if (!success) { - std::string emsg = "Could not find "; - emsg += cmQtAutoGen::Quoted(searchFile); - emsg += " in\n"; - for (std::string const& testFile : testFiles) { - emsg += " "; - emsg += cmQtAutoGen::Quoted(testFile); - emsg += "\n"; +void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle) +{ + bool const jobProcessed(jobHandle); + if (jobProcessed) { + jobHandle.reset(nullptr); + } + { + std::unique_lock jobsLock(JobsMutex_); + // Reduce the remaining job count and notify the libuv loop + // when all jobs are done + if (jobProcessed) { + --JobsRemain_; + if (JobsRemain_ == 0) { + UVRequest().send(); + } + } + // Wait for new jobs + while (!JobThreadsAbort_ && JobQueue_.empty()) { + JobsConditionRead_.wait(jobsLock); + } + // Try to pick up a new job handle + if (!JobThreadsAbort_ && !JobQueue_.empty()) { + jobHandle = std::move(JobQueue_.front()); + JobQueue_.pop_front(); } - this->LogFileError(cmQtAutoGen::UIC, sourceFile, emsg); } +} - return success; +void cmQtAutoGeneratorMocUic::ParallelRegisterJobError() +{ + std::lock_guard jobsLock(JobsMutex_); + RegisterJobError(); } -bool cmQtAutoGeneratorMocUic::UicGenerateAll() +// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be +// locked +void cmQtAutoGeneratorMocUic::RegisterJobError() { - if (!this->UicEnabled()) { - return true; + JobError_ = true; + if (!JobThreadsAbort_) { + JobThreadsAbort_ = true; + // Clear remaining jobs + if (JobsRemain_ != 0) { + JobsRemain_ -= JobQueue_.size(); + JobQueue_.clear(); + } } +} - // Look for name collisions in included uic files - { - bool collision = false; - std::map> collisions; - for (auto const& job : this->UicJobs) { - auto& list = collisions[job->IncludeString]; - if (!list.empty()) { - collision = true; - } - list.push_back(job.get()); - } - if (collision) { - std::string emsg = - "Included uic files with the same name will be " - "generated from different sources.\n" - "Consider to\n" - " - add a directory prefix to a \"ui_.h\" include " - "(e.g \"sub/ui_.h\")\n" - " - rename the .ui file(s) and adjust the \"ui_.h\" " - "include(s)\n" - "Include conflicts\n" - "-----------------\n"; - const auto& colls = collisions; - for (auto const& coll : colls) { - if (coll.second.size() > 1) { - emsg += cmQtAutoGen::Quoted(coll.first); - emsg += " included in\n"; - for (const UicJob* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->Includer); - emsg += "\n"; - } - emsg += "would be generated from\n"; - for (const UicJob* job : coll.second) { - emsg += " - "; - emsg += cmQtAutoGen::Quoted(job->SourceFile); - emsg += "\n"; +bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle) +{ + std::lock_guard jobsLock(JobsMutex_); + if (!JobThreadsAbort_) { + bool pushJobHandle = true; + // Do additional tests if this is an included moc job + const JobMocT& mocJob(static_cast(*jobHandle)); + if (!mocJob.IncludeString.empty()) { + // Register included moc file and look for collisions + MocIncludedFiles_.emplace(mocJob.SourceFile); + if (!MocIncludedStrings_.emplace(mocJob.IncludeString).second) { + // Another source file includes the same moc file! + for (const JobHandleT& otherHandle : JobQueues_.Moc) { + const JobMocT& otherJob(static_cast(*otherHandle)); + if (otherJob.IncludeString == mocJob.IncludeString) { + // Check if the same moc file would be generated from different + // source files which is an error. + if (otherJob.SourceFile != mocJob.SourceFile) { + // Include string collision + std::string error = "The two source files\n "; + error += Quoted(mocJob.IncluderFile); + error += " and\n "; + error += Quoted(otherJob.IncluderFile); + error += "\ncontain the the same moc include string "; + error += Quoted(mocJob.IncludeString); + error += "\nbut the moc file would be generated from different " + "source files\n "; + error += Quoted(mocJob.SourceFile); + error += " and\n "; + error += Quoted(otherJob.SourceFile); + error += ".\nConsider to\n" + "- not include the \"moc_.cpp\" file\n" + "- add a directory prefix to a \".moc\" include " + "(e.g \"sub/.moc\")\n" + "- rename the source file(s)\n"; + Log().Error(GeneratorT::MOC, error); + RegisterJobError(); + } + // Do not push this job in since the included moc file already + // gets generated by an other job. + pushJobHandle = false; + break; } } } - this->LogError(cmQtAutoGen::UIC, emsg); - return false; + } + // Push job on demand + if (pushJobHandle) { + JobQueues_.Moc.emplace_back(std::move(jobHandle)); } } + return !JobError_; +} - // Generate ui header files - for (const auto& item : this->UicJobs) { - if (!this->UicGenerateFile(*item)) { - return false; +bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle) +{ + std::lock_guard jobsLock(JobsMutex_); + if (!JobThreadsAbort_) { + bool pushJobHandle = true; + // Look for include collisions. + const JobUicT& uicJob(static_cast(*jobHandle)); + for (const JobHandleT& otherHandle : JobQueues_.Uic) { + const JobUicT& otherJob(static_cast(*otherHandle)); + if (otherJob.IncludeString == uicJob.IncludeString) { + // Check if the same uic file would be generated from different + // source files which would be an error. + if (otherJob.SourceFile != uicJob.SourceFile) { + // Include string collision + std::string error = "The two source files\n "; + error += Quoted(uicJob.IncluderFile); + error += " and\n "; + error += Quoted(otherJob.IncluderFile); + error += "\ncontain the the same uic include string "; + error += Quoted(uicJob.IncludeString); + error += "\nbut the uic file would be generated from different " + "source files\n "; + error += Quoted(uicJob.SourceFile); + error += " and\n "; + error += Quoted(otherJob.SourceFile); + error += + ".\nConsider to\n" + "- add a directory prefix to a \"ui_.h\" include " + "(e.g \"sub/ui_.h\")\n" + "- rename the .ui file(s) and adjust the \"ui_.h\" " + "include(s)\n"; + Log().Error(GeneratorT::UIC, error); + RegisterJobError(); + } + // Do not push this job in since the uic file already + // gets generated by an other job. + pushJobHandle = false; + break; + } + } + if (pushJobHandle) { + JobQueues_.Uic.emplace_back(std::move(jobHandle)); } } - - return true; + return !JobError_; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::UicGenerateFile(const UicJob& uicJob) +bool cmQtAutoGeneratorMocUic::ParallelMocIncluded( + std::string const& sourceFile) { - bool success = true; + std::lock_guard mocLock(JobsMutex_); + return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); +} - std::string const uicFileAbs = cmSystemTools::CollapseCombinedPath( - this->AutogenBuildDir, uicJob.BuildFileRel); +void cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( + std::string const& mocFile) +{ + std::lock_guard mocLock(JobsMutex_); + MocAutoFiles_.emplace(mocFile); +} - bool generate = false; - std::string generateReason; - if (!generate && !cmSystemTools::FileExists(uicFileAbs.c_str())) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " from its source file "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - generateReason += " because it doesn't exist"; - } - generate = true; - } - if (!generate && this->UicSettingsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - generateReason += " because the UIC settings changed"; - } - generate = true; - } - if (!generate) { - std::string error; - if (FileIsOlderThan(uicFileAbs, uicJob.SourceFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(uicFileAbs); - generateReason += " because it's older than its source file "; - generateReason += cmQtAutoGen::Quoted(uicJob.SourceFile); - } - generate = true; - } else { - if (!error.empty()) { - this->LogError(cmQtAutoGen::UIC, error); - success = false; - } - } - } - if (generate) { - // Log - if (this->GetVerbose()) { - this->LogBold("Generating UIC header " + uicJob.BuildFileRel); - this->LogInfo(cmQtAutoGen::UIC, generateReason); - } +void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated() +{ + std::lock_guard mocLock(JobsMutex_); + MocAutoFileUpdated_ = true; +} - // Make sure the parent directory exists - if (this->MakeParentDirectory(cmQtAutoGen::UIC, uicFileAbs)) { - // Compose uic command - std::vector cmd; - cmd.push_back(this->UicExecutable); - { - std::vector allOpts = this->UicTargetOptions; - auto optionIt = this->UicOptions.find(uicJob.SourceFile); - if (optionIt != this->UicOptions.end()) { - cmQtAutoGen::UicMergeOptions(allOpts, optionIt->second, - (this->QtVersionMajor == 5)); +void cmQtAutoGeneratorMocUic::MocGenerateCompilation() +{ + std::lock_guard mocLock(JobsMutex_); + if (!JobThreadsAbort_ && Moc().Enabled) { + // Compose mocs compilation file content + { + std::string content = + "// This file is autogenerated. Changes will be overwritten.\n"; + if (MocAutoFiles_.empty()) { + // Placeholder content + content += "// No files found that require moc or the moc files are " + "included\n"; + content += "enum some_compilers { need_more_than_nothing };\n"; + } else { + // Valid content + for (std::string const& mocfile : MocAutoFiles_) { + content += "#include \""; + content += mocfile; + content += "\"\n"; } - cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); } - cmd.push_back("-o"); - cmd.push_back(uicFileAbs); - cmd.push_back(uicJob.SourceFile); - std::string output; - if (this->RunCommand(cmd, output)) { - // Success - } else { - // Command failed - { - std::string emsg = "uic failed for\n "; - emsg += cmQtAutoGen::Quoted(uicJob.SourceFile); - emsg += "\nincluded by\n "; - emsg += cmQtAutoGen::Quoted(uicJob.Includer); - this->LogCommandError(cmQtAutoGen::UIC, emsg, cmd, output); + std::string const& compRel = Moc().CompFileRel; + std::string const& compAbs = Moc().CompFileAbs; + if (FileSys().FileDiffers(compAbs, content)) { + // Actually write mocs compilation file + if (Log().Verbose()) { + Log().Info(GeneratorT::MOC, "Generating MOC compilation " + compRel); } - cmSystemTools::RemoveFile(uicFileAbs); - success = false; + if (!FileSys().FileWrite(GeneratorT::MOC, compAbs, content)) { + Log().ErrorFile(GeneratorT::MOC, compAbs, + "mocs compilation file writing failed"); + RegisterJobError(); + return; + } + } else if (MocAutoFileUpdated_) { + // Only touch mocs compilation file + if (Log().Verbose()) { + Log().Info(GeneratorT::MOC, "Touching mocs compilation " + compRel); + } + FileSys().Touch(compAbs); } - } else { - // Parent directory creation failed - success = false; - } - } - return success; -} - -/** - * @brief Tries to find the header file to the given file base path by - * appending different header extensions - * @return True on success - */ -bool cmQtAutoGeneratorMocUic::FindHeader(std::string& header, - std::string const& testBasePath) const -{ - for (std::string const& ext : this->HeaderExtensions) { - std::string testFilePath(testBasePath); - testFilePath.push_back('.'); - testFilePath += ext; - if (cmSystemTools::FileExists(testFilePath.c_str())) { - header = testFilePath; - return true; } } - return false; } diff --git a/Source/cmQtAutoGeneratorMocUic.h b/Source/cmQtAutoGeneratorMocUic.h index d510939..215e25a 100644 --- a/Source/cmQtAutoGeneratorMocUic.h +++ b/Source/cmQtAutoGeneratorMocUic.h @@ -8,188 +8,434 @@ #include "cmFilePathChecksum.h" #include "cmQtAutoGen.h" #include "cmQtAutoGenerator.h" +#include "cmUVHandlePtr.h" +#include "cm_uv.h" #include "cmsys/RegularExpression.hxx" +#include +#include +#include +#include #include #include // IWYU pragma: keep +#include #include #include +#include #include class cmMakefile; +// @brief AUTOMOC and AUTOUIC generator class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator { CM_DISABLE_COPY(cmQtAutoGeneratorMocUic) public: cmQtAutoGeneratorMocUic(); + ~cmQtAutoGeneratorMocUic() override; -private: +public: // -- Types + class WorkerT; /// @brief Search key plus regular expression pair - struct KeyRegExp + /// + struct KeyExpT { - KeyRegExp() = default; + KeyExpT() = default; - KeyRegExp(const char* key, const char* regExp) + KeyExpT(const char* key, const char* exp) : Key(key) - , RegExp(regExp) + , Exp(exp) { } - KeyRegExp(std::string const& key, std::string const& regExp) + KeyExpT(std::string const& key, std::string const& exp) : Key(key) - , RegExp(regExp) + , Exp(exp) { } std::string Key; - cmsys::RegularExpression RegExp; + cmsys::RegularExpression Exp; }; - /// @brief Source file job - struct SourceJob + /// @brief Common settings + /// + class BaseSettingsT { - bool Moc = false; - bool Uic = false; + CM_DISABLE_COPY(BaseSettingsT) + public: + // -- Volatile methods + BaseSettingsT(FileSystem* fileSystem) + : MultiConfig(MultiConfigT::WRAPPER) + , IncludeProjectDirsBefore(false) + , QtVersionMajor(4) + , NumThreads(1) + , FileSys(fileSystem) + { + } + + // -- Const methods + std::string AbsoluteBuildPath(std::string const& relativePath) const; + bool FindHeader(std::string& header, + std::string const& testBasePath) const; + + // -- Attributes + // - Config + std::string ConfigSuffix; + MultiConfigT MultiConfig; + bool IncludeProjectDirsBefore; + unsigned int QtVersionMajor; + unsigned int NumThreads; + // - Directories + std::string ProjectSourceDir; + std::string ProjectBinaryDir; + std::string CurrentSourceDir; + std::string CurrentBinaryDir; + std::string AutogenBuildDir; + std::string AutogenIncludeDirRel; + std::string AutogenIncludeDirAbs; + // - Files + cmFilePathChecksum FilePathChecksum; + std::vector HeaderExtensions; + // - File system + FileSystem* FileSys; }; - /// @brief MOC job - struct MocJobAuto + /// @brief Moc settings + /// + class MocSettingsT { - std::string SourceFile; - std::string BuildFileRel; - std::set Depends; + CM_DISABLE_COPY(MocSettingsT) + public: + MocSettingsT(FileSystem* fileSys) + : FileSys(fileSys) + { + } + + // -- Const methods + bool skipped(std::string const& fileName) const; + std::string FindMacro(std::string const& content) const; + std::string MacrosString() const; + std::string FindIncludedFile(std::string const& sourcePath, + std::string const& includeString) const; + void FindDependencies(std::string const& content, + std::set& depends) const; + + // -- Attributes + bool Enabled = false; + bool SettingsChanged = false; + bool RelaxedMode = false; + std::string Executable; + std::string CompFileRel; + std::string CompFileAbs; + std::string PredefsFileRel; + std::string PredefsFileAbs; + std::set SkipList; + std::vector IncludePaths; + std::vector Includes; + std::vector Definitions; + std::vector Options; + std::vector AllOptions; + std::vector PredefsCmd; + std::vector DependFilters; + std::vector MacroFilters; + cmsys::RegularExpression RegExpInclude; + // - File system + FileSystem* FileSys; }; - /// @brief MOC job - struct MocJobIncluded : MocJobAuto + /// @brief Uic settings + /// + class UicSettingsT { - bool DependsValid = false; - std::string Includer; + CM_DISABLE_COPY(UicSettingsT) + public: + UicSettingsT() = default; + // -- Const methods + bool skipped(std::string const& fileName) const; + + // -- Attributes + bool Enabled = false; + bool SettingsChanged = false; + std::string Executable; + std::set SkipList; + std::vector TargetOptions; + std::map> Options; + std::vector SearchPaths; + cmsys::RegularExpression RegExpInclude; + }; + + /// @brief Abstract job class for threaded processing + /// + class JobT + { + CM_DISABLE_COPY(JobT) + public: + JobT() = default; + virtual ~JobT() = default; + // -- Abstract processing interface + virtual void Process(WorkerT& wrk) = 0; + }; + + /// @brief Deleter for classes derived from Job + /// + struct JobDeleterT + { + void operator()(JobT* job); + }; + + // Job management types + typedef std::unique_ptr JobHandleT; + typedef std::deque JobQueueT; + + /// @brief Parse source job + /// + class JobParseT : public JobT + { + public: + JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false) + : FileName(std::move(fileName)) + , AutoMoc(moc) + , AutoUic(uic) + , Header(header) + { + } + + private: + struct MetaT + { + std::string Content; + std::string FileDir; + std::string FileBase; + }; + + void Process(WorkerT& wrk) override; + bool ParseMocSource(WorkerT& wrk, MetaT const& meta); + bool ParseMocHeader(WorkerT& wrk, MetaT const& meta); + std::string MocStringHeaders(WorkerT& wrk, + std::string const& fileBase) const; + std::string MocFindIncludedHeader(WorkerT& wrk, + std::string const& includerDir, + std::string const& includeBase); + bool ParseUic(WorkerT& wrk, MetaT const& meta); + bool ParseUicInclude(WorkerT& wrk, MetaT const& meta, + std::string&& includeString); + std::string UicFindIncludedFile(WorkerT& wrk, MetaT const& meta, + std::string const& includeString); + + private: + std::string FileName; + bool AutoMoc = false; + bool AutoUic = false; + bool Header = false; + }; + + /// @brief Generate moc_predefs + /// + class JobMocPredefsT : public JobT + { + private: + void Process(WorkerT& wrk) override; + }; + + /// @brief Moc a file job + /// + class JobMocT : public JobT + { + public: + JobMocT(std::string&& sourceFile, std::string const& includerFile, + std::string&& includeString) + : SourceFile(std::move(sourceFile)) + , IncluderFile(includerFile) + , IncludeString(std::move(includeString)) + { + } + + void FindDependencies(WorkerT& wrk, std::string const& content); + + private: + void Process(WorkerT& wrk) override; + bool UpdateRequired(WorkerT& wrk); + void GenerateMoc(WorkerT& wrk); + + public: + std::string SourceFile; + std::string IncluderFile; std::string IncludeString; + std::string BuildFile; + bool DependsValid = false; + std::set Depends; }; - /// @brief UIC job - struct UicJob + /// @brief Uic a file job + /// + class JobUicT : public JobT { + public: + JobUicT(std::string&& sourceFile, std::string const& includerFile, + std::string&& includeString) + : SourceFile(std::move(sourceFile)) + , IncluderFile(includerFile) + , IncludeString(std::move(includeString)) + { + } + + private: + void Process(WorkerT& wrk) override; + bool UpdateRequired(WorkerT& wrk); + void GenerateUic(WorkerT& wrk); + + public: std::string SourceFile; - std::string BuildFileRel; - std::string Includer; + std::string IncluderFile; std::string IncludeString; + std::string BuildFile; }; - // -- Initialization - bool InitInfoFile(cmMakefile* makefile); + /// @brief Worker Thread + /// + class WorkerT + { + CM_DISABLE_COPY(WorkerT) + public: + WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop); + ~WorkerT(); + + // -- Const accessors + cmQtAutoGeneratorMocUic& Gen() const { return *Gen_; } + Logger& Log() const { return Gen_->Log(); } + FileSystem& FileSys() const { return Gen_->FileSys(); } + const BaseSettingsT& Base() const { return Gen_->Base(); } + const MocSettingsT& Moc() const { return Gen_->Moc(); } + const UicSettingsT& Uic() const { return Gen_->Uic(); } - // -- Settings file - void SettingsFileRead(cmMakefile* makefile); - bool SettingsFileWrite(); - bool SettingsChanged() const + // -- Log info + void LogInfo(GeneratorT genType, std::string const& message) const; + // -- Log warning + void LogWarning(GeneratorT genType, std::string const& message) const; + void LogFileWarning(GeneratorT genType, std::string const& filename, + std::string const& message) const; + // -- Log error + void LogError(GeneratorT genType, std::string const& message) const; + void LogFileError(GeneratorT genType, std::string const& filename, + std::string const& message) const; + void LogCommandError(GeneratorT genType, std::string const& message, + std::vector const& command, + std::string const& output) const; + + // -- External processes + /// @brief Verbose logging version + bool RunProcess(GeneratorT genType, ProcessResultT& result, + std::vector const& command); + + private: + /// @brief Thread main loop + void Loop(); + + // -- Libuv callbacks + static void UVProcessStart(uv_async_t* handle); + void UVProcessFinished(); + + private: + // -- Generator + cmQtAutoGeneratorMocUic* Gen_; + // -- Job handle + JobHandleT JobHandle_; + // -- Process management + std::mutex ProcessMutex_; + cm::uv_async_ptr ProcessRequest_; + std::condition_variable ProcessCondition_; + std::unique_ptr Process_; + // -- System thread + std::thread Thread_; + }; + + /// @brief Processing stage + enum class StageT { - return (this->MocSettingsChanged || this->UicSettingsChanged); - } - - // -- Central processing - bool Process(cmMakefile* makefile) override; - - // -- Source parsing - bool ParseSourceFile(std::string const& absFilename, const SourceJob& job); - bool ParseHeaderFile(std::string const& absFilename, const SourceJob& job); - bool ParsePostprocess(); - - // -- Moc - bool MocEnabled() const { return !this->MocExecutable.empty(); } - bool MocSkip(std::string const& absFilename) const; - bool MocRequired(std::string const& contentText, - std::string* macroName = nullptr); - // Moc strings - std::string MocStringMacros() const; - std::string MocStringHeaders(std::string const& fileBase) const; - std::string MocFindIncludedHeader(std::string const& sourcePath, - std::string const& includeBase) const; - bool MocFindIncludedFile(std::string& absFile, std::string const& sourceFile, - std::string const& includeString) const; - // Moc depends - bool MocDependFilterPush(std::string const& key, std::string const& regExp); - void MocFindDepends(std::string const& absFilename, - std::string const& contentText, - std::set& depends); - // Moc - bool MocParseSourceContent(std::string const& absFilename, - std::string const& contentText); - void MocParseHeaderContent(std::string const& absFilename, - std::string const& contentText); - - bool MocGenerateAll(); - bool MocGenerateFile(const MocJobAuto& mocJob, bool* generated = nullptr); - - // -- Uic - bool UicEnabled() const { return !this->UicExecutable.empty(); } - bool UicSkip(std::string const& absFilename) const; - bool UicParseContent(std::string const& fileName, - std::string const& contentText); - bool UicFindIncludedFile(std::string& absFile, std::string const& sourceFile, - std::string const& includeString); - bool UicGenerateAll(); - bool UicGenerateFile(const UicJob& uicJob); - - // -- Utility - bool FindHeader(std::string& header, std::string const& testBasePath) const; - - // -- Meta - std::string ConfigSuffix; - cmQtAutoGen::MultiConfig MultiConfig; + SETTINGS_READ, + CREATE_DIRECTORIES, + PARSE_SOURCES, + PARSE_HEADERS, + MOC_PREDEFS, + MOC_PROCESS, + MOCS_COMPILATION, + UIC_PROCESS, + SETTINGS_WRITE, + FINISH, + END + }; + + // -- Const settings interface + const BaseSettingsT& Base() const { return this->Base_; } + const MocSettingsT& Moc() const { return this->Moc_; } + const UicSettingsT& Uic() const { return this->Uic_; } + + // -- Worker thread interface + void WorkerSwapJob(JobHandleT& jobHandle); + // -- Parallel job processing interface + void ParallelRegisterJobError(); + bool ParallelJobPushMoc(JobHandleT& jobHandle); + bool ParallelJobPushUic(JobHandleT& jobHandle); + bool ParallelMocIncluded(std::string const& sourceFile); + void ParallelMocAutoRegister(std::string const& mocFile); + void ParallelMocAutoUpdated(); + +private: + // -- Abstract processing interface + bool Init(cmMakefile* makefile) override; + bool Process() override; + // -- Process stage + static void UVPollStage(uv_async_t* handle); + void PollStage(); + void SetStage(StageT stage); + // -- Settings file + void SettingsFileRead(); + void SettingsFileWrite(); + // -- Thread processing + bool ThreadsStartJobs(JobQueueT& queue); + bool ThreadsJobsDone(); + void ThreadsStop(); + void RegisterJobError(); + // -- Generation + void CreateDirectories(); + void MocGenerateCompilation(); + +private: // -- Settings - bool IncludeProjectDirsBefore; - std::string SettingsFile; - std::string SettingsStringMoc; - std::string SettingsStringUic; - // -- Directories - std::string ProjectSourceDir; - std::string ProjectBinaryDir; - std::string CurrentSourceDir; - std::string CurrentBinaryDir; - std::string AutogenBuildDir; - std::string AutogenIncludeDir; - // -- Qt environment - unsigned long QtVersionMajor; - std::string MocExecutable; - std::string UicExecutable; - // -- File lists - std::map HeaderJobs; - std::map SourceJobs; - std::vector HeaderExtensions; - cmFilePathChecksum FilePathChecksum; - // -- Moc - bool MocSettingsChanged; - bool MocPredefsChanged; - bool MocRelaxedMode; - std::string MocCompFileRel; - std::string MocCompFileAbs; - std::string MocPredefsFileRel; - std::string MocPredefsFileAbs; - std::vector MocSkipList; - std::vector MocIncludePaths; - std::vector MocIncludes; - std::vector MocDefinitions; - std::vector MocOptions; - std::vector MocAllOptions; - std::vector MocPredefsCmd; - std::vector MocDependFilters; - std::vector MocMacroFilters; - cmsys::RegularExpression MocRegExpInclude; - std::vector> MocJobsIncluded; - std::vector> MocJobsAuto; - // -- Uic - bool UicSettingsChanged; - std::vector UicSkipList; - std::vector UicTargetOptions; - std::map> UicOptions; - std::vector UicSearchPaths; - cmsys::RegularExpression UicRegExpInclude; - std::vector> UicJobs; + BaseSettingsT Base_; + MocSettingsT Moc_; + UicSettingsT Uic_; + // -- Progress + StageT Stage_; + // -- Job queues + std::mutex JobsMutex_; + struct + { + JobQueueT Sources; + JobQueueT Headers; + JobQueueT MocPredefs; + JobQueueT Moc; + JobQueueT Uic; + } JobQueues_; + JobQueueT JobQueue_; + std::size_t volatile JobsRemain_; + bool volatile JobError_; + bool volatile JobThreadsAbort_; + std::condition_variable JobsConditionRead_; + // -- Moc meta + std::set MocIncludedStrings_; + std::set MocIncludedFiles_; + std::set MocAutoFiles_; + bool volatile MocAutoFileUpdated_; + // -- Settings file + std::string SettingsFile_; + std::string SettingsStringMoc_; + std::string SettingsStringUic_; + // -- Threads and loops + std::vector> Workers_; }; #endif diff --git a/Source/cmQtAutoGeneratorRcc.cxx b/Source/cmQtAutoGeneratorRcc.cxx index 3c9f1a8..e8ff75a 100644 --- a/Source/cmQtAutoGeneratorRcc.cxx +++ b/Source/cmQtAutoGeneratorRcc.cxx @@ -6,22 +6,30 @@ #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmMakefile.h" -#include "cmOutputConverter.h" #include "cmSystemTools.h" +#include "cmUVHandlePtr.h" -// -- Static variables - -static const char* SettingsKeyRcc = "ARCC_SETTINGS_HASH"; +#include // -- Class methods cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc() - : MultiConfig(cmQtAutoGen::WRAP) - , SettingsChanged(false) + : SettingsChanged_(false) + , MultiConfig_(MultiConfigT::WRAPPER) + , Stage_(StageT::SETTINGS_READ) + , Error_(false) + , Generate_(false) + , BuildFileChanged_(false) { + // Initialize libuv asynchronous iteration request + UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this); } -bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile) +cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc() +{ +} + +bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile) { // Utility lambdas auto InfoGet = [makefile](const char* key) { @@ -37,7 +45,7 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile) { std::string keyConf = key; keyConf += '_'; - keyConf += this->GetInfoConfig(); + keyConf += InfoConfig(); valueConf = makefile->GetDefinition(keyConf); } if (valueConf == nullptr) { @@ -53,79 +61,180 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile) }; // -- Read info file - if (!makefile->ReadListFile(this->GetInfoFile().c_str())) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "File processing failed"); + if (!makefile->ReadListFile(InfoFile().c_str())) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed"); return false; } // -- Meta - this->MultiConfig = - cmQtAutoGen::MultiConfigType(InfoGet("ARCC_MULTI_CONFIG")); - this->ConfigSuffix = InfoGetConfig("ARCC_CONFIG_SUFFIX"); - if (this->ConfigSuffix.empty()) { - this->ConfigSuffix = "_"; - this->ConfigSuffix += this->GetInfoConfig(); + MultiConfig_ = MultiConfigType(InfoGet("ARCC_MULTI_CONFIG")); + ConfigSuffix_ = InfoGetConfig("ARCC_CONFIG_SUFFIX"); + if (ConfigSuffix_.empty()) { + ConfigSuffix_ = "_"; + ConfigSuffix_ += InfoConfig(); } - this->SettingsFile = InfoGetConfig("ARCC_SETTINGS_FILE"); + SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE"); // - Files and directories - this->ProjectSourceDir = InfoGet("ARCC_CMAKE_SOURCE_DIR"); - this->ProjectBinaryDir = InfoGet("ARCC_CMAKE_BINARY_DIR"); - this->CurrentSourceDir = InfoGet("ARCC_CMAKE_CURRENT_SOURCE_DIR"); - this->CurrentBinaryDir = InfoGet("ARCC_CMAKE_CURRENT_BINARY_DIR"); - this->AutogenBuildDir = InfoGet("ARCC_BUILD_DIR"); + AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR"); // - Qt environment - this->RccExecutable = InfoGet("ARCC_RCC_EXECUTABLE"); - this->RccListOptions = InfoGetList("ARCC_RCC_LIST_OPTIONS"); + RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE"); + RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS"); // - Job - this->QrcFile = InfoGet("ARCC_SOURCE"); - this->RccFile = InfoGet("ARCC_OUTPUT"); - this->Options = InfoGetConfigList("ARCC_OPTIONS"); - this->Inputs = InfoGetList("ARCC_INPUTS"); + QrcFile_ = InfoGet("ARCC_SOURCE"); + QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_); + QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_); + RccFile_ = InfoGet("ARCC_OUTPUT"); + Options_ = InfoGetConfigList("ARCC_OPTIONS"); + Inputs_ = InfoGetList("ARCC_INPUTS"); // - Validity checks - if (this->SettingsFile.empty()) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "Settings file name missing"); + if (SettingsFile_.empty()) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing"); return false; } - if (this->AutogenBuildDir.empty()) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "Autogen build directory missing"); + if (AutogenBuildDir_.empty()) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), + "Autogen build directory missing"); return false; } - if (this->RccExecutable.empty()) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "rcc executable missing"); + if (RccExecutable_.empty()) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing"); return false; } - if (this->QrcFile.empty()) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "rcc input file missing"); + if (QrcFile_.empty()) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing"); return false; } - if (this->RccFile.empty()) { - this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(), - "rcc output file missing"); + if (RccFile_.empty()) { + Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing"); return false; } // Init derived information // ------------------------ - // Init file path checksum generator - this->FilePathChecksum.setupParentDirs( - this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir, - this->ProjectBinaryDir); + // Compute rcc output file name + { + std::string suffix; + switch (MultiConfig_) { + case MultiConfigT::SINGLE: + break; + case MultiConfigT::WRAPPER: + suffix = "_CMAKE"; + suffix += ConfigSuffix_; + suffix += "_"; + break; + case MultiConfigT::MULTI: + suffix = ConfigSuffix_; + break; + } + RccFileBuild_ = AppendFilenameSuffix(RccFile_, suffix); + } + + return true; +} +bool cmQtAutoGeneratorRcc::Process() +{ + // Run libuv event loop + UVRequest().send(); + if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) { + if (Error_) { + return false; + } + } else { + return false; + } return true; } -void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile) +void cmQtAutoGeneratorRcc::UVPollStage(uv_async_t* handle) +{ + reinterpret_cast(handle->data)->PollStage(); +} + +void cmQtAutoGeneratorRcc::PollStage() +{ + switch (Stage_) { + // -- Initialize + case StageT::SETTINGS_READ: + SettingsFileRead(); + SetStage(StageT::TEST_QRC_RCC_FILES); + break; + + // -- Change detection + case StageT::TEST_QRC_RCC_FILES: + if (TestQrcRccFiles()) { + SetStage(StageT::GENERATE); + } else { + SetStage(StageT::TEST_RESOURCES_READ); + } + break; + case StageT::TEST_RESOURCES_READ: + if (TestResourcesRead()) { + SetStage(StageT::TEST_RESOURCES); + } + break; + case StageT::TEST_RESOURCES: + if (TestResources()) { + SetStage(StageT::GENERATE); + } else { + SetStage(StageT::TEST_INFO_FILE); + } + break; + case StageT::TEST_INFO_FILE: + TestInfoFile(); + SetStage(StageT::GENERATE_WRAPPER); + break; + + // -- Generation + case StageT::GENERATE: + GenerateParentDir(); + SetStage(StageT::GENERATE_RCC); + break; + case StageT::GENERATE_RCC: + if (GenerateRcc()) { + SetStage(StageT::GENERATE_WRAPPER); + } + break; + case StageT::GENERATE_WRAPPER: + GenerateWrapper(); + SetStage(StageT::SETTINGS_WRITE); + break; + + // -- Finalize + case StageT::SETTINGS_WRITE: + SettingsFileWrite(); + SetStage(StageT::FINISH); + break; + case StageT::FINISH: + // Clear all libuv handles + UVRequest().reset(); + // Set highest END stage manually + Stage_ = StageT::END; + break; + case StageT::END: + break; + } +} + +void cmQtAutoGeneratorRcc::SetStage(StageT stage) +{ + if (Error_) { + stage = StageT::FINISH; + } + // Only allow to increase the stage + if (Stage_ < stage) { + Stage_ = stage; + UVRequest().send(); + } +} + +void cmQtAutoGeneratorRcc::SettingsFileRead() { // Compose current settings strings { @@ -133,293 +242,375 @@ void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile) std::string const sep(" ~~~ "); { std::string str; - str += this->RccExecutable; + str += RccExecutable_; str += sep; - str += cmJoin(this->RccListOptions, ";"); + str += cmJoin(RccListOptions_, ";"); str += sep; - str += this->QrcFile; + str += QrcFile_; str += sep; - str += this->RccFile; + str += RccFile_; str += sep; - str += cmJoin(this->Options, ";"); + str += cmJoin(Options_, ";"); str += sep; - str += cmJoin(this->Inputs, ";"); + str += cmJoin(Inputs_, ";"); str += sep; - this->SettingsString = crypt.HashString(str); + SettingsString_ = crypt.HashString(str); } } // Read old settings - if (makefile->ReadListFile(this->SettingsFile.c_str())) { - { - auto SMatch = [makefile](const char* key, std::string const& value) { - return (value == makefile->GetSafeDefinition(key)); - }; - if (!SMatch(SettingsKeyRcc, this->SettingsString)) { - this->SettingsChanged = true; + { + std::string content; + if (FileSys().FileRead(content, SettingsFile_)) { + SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc")); + // 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 (SettingsChanged_) { + FileSys().FileRemove(SettingsFile_); } + } else { + SettingsChanged_ = true; } - // In case any setting changed remove the old settings file. - // This triggers a full rebuild on the next run if the current - // build is aborted before writing the current settings in the end. - if (this->SettingsChanged) { - cmSystemTools::RemoveFile(this->SettingsFile); - } - } else { - // If the file could not be read re-generate everythiung. - this->SettingsChanged = true; } } -bool cmQtAutoGeneratorRcc::SettingsFileWrite() +void cmQtAutoGeneratorRcc::SettingsFileWrite() { - bool success = true; // Only write if any setting changed - if (this->SettingsChanged) { - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::RCC, "Writing settings file " + - cmQtAutoGen::Quoted(this->SettingsFile)); - } - // Compose settings file content - std::string settings; - { - auto SettingAppend = [&settings](const char* key, - std::string const& value) { - settings += "set("; - settings += key; - settings += " "; - settings += cmOutputConverter::EscapeForCMake(value); - settings += ")\n"; - }; - SettingAppend(SettingsKeyRcc, this->SettingsString); + if (SettingsChanged_) { + if (Log().Verbose()) { + Log().Info(GeneratorT::RCC, + "Writing settings file " + Quoted(SettingsFile_)); } // Write settings file - if (!this->FileWrite(cmQtAutoGen::RCC, this->SettingsFile, settings)) { - this->LogFileError(cmQtAutoGen::RCC, this->SettingsFile, - "Settings file writing failed"); + std::string content = "rcc:"; + content += SettingsString_; + content += '\n'; + if (!FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, content)) { + Log().ErrorFile(GeneratorT::RCC, SettingsFile_, + "Settings file writing failed"); // Remove old settings file to trigger a full rebuild on the next run - cmSystemTools::RemoveFile(this->SettingsFile); - success = false; + FileSys().FileRemove(SettingsFile_); + Error_ = true; } } - return success; } -bool cmQtAutoGeneratorRcc::Process(cmMakefile* makefile) +bool cmQtAutoGeneratorRcc::TestQrcRccFiles() { - // Read info file - if (!this->InfoFileRead(makefile)) { - return false; + // Do basic checks if rcc generation is required + + // Test if the rcc output file exists + if (!FileSys().FileExists(RccFileBuild_)) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(RccFileBuild_); + reason += " from its source file "; + reason += Quoted(QrcFile_); + reason += " because it doesn't exist"; + Log().Info(GeneratorT::RCC, reason); + } + Generate_ = true; + return Generate_; } - // Read latest settings - this->SettingsFileRead(makefile); - // Generate rcc file - if (!this->RccGenerate()) { - return false; + + // Test if the settings changed + if (SettingsChanged_) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(RccFileBuild_); + reason += " from "; + reason += Quoted(QrcFile_); + reason += " because the RCC settings changed"; + Log().Info(GeneratorT::RCC, reason); + } + Generate_ = true; + return Generate_; } - // Write latest settings - if (!this->SettingsFileWrite()) { - return false; + + // Test if the rcc output file is older than the .qrc file + { + bool isOlder = false; + { + std::string error; + isOlder = FileSys().FileIsOlderThan(RccFileBuild_, QrcFile_, &error); + if (!error.empty()) { + Log().ErrorFile(GeneratorT::RCC, QrcFile_, error); + Error_ = true; + } + } + if (isOlder) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(RccFileBuild_); + reason += " because it is older than "; + reason += Quoted(QrcFile_); + Log().Info(GeneratorT::RCC, reason); + } + Generate_ = true; + } } - return true; + + return Generate_; } -/** - * @return True on success - */ -bool cmQtAutoGeneratorRcc::RccGenerate() +bool cmQtAutoGeneratorRcc::TestResourcesRead() { - bool success = true; - bool rccGenerated = false; - - std::string rccFileAbs; - { - std::string suffix; - switch (this->MultiConfig) { - case cmQtAutoGen::SINGLE: - break; - case cmQtAutoGen::WRAP: - suffix = "_CMAKE"; - suffix += this->ConfigSuffix; - suffix += "_"; - break; - case cmQtAutoGen::FULL: - suffix = this->ConfigSuffix; - break; - } - rccFileAbs = cmQtAutoGen::AppendFilenameSuffix(this->RccFile, suffix); + if (!Inputs_.empty()) { + // Inputs are known already + return true; } - std::string const rccFileRel = cmSystemTools::RelativePath( - this->AutogenBuildDir.c_str(), rccFileAbs.c_str()); - // Check if regeneration is required - bool generate = false; - std::string generateReason; - if (!cmSystemTools::FileExists(this->QrcFile)) { - { - std::string error = "Could not find the file\n "; - error += cmQtAutoGen::Quoted(this->QrcFile); - this->LogError(cmQtAutoGen::RCC, error); + if (!RccListOptions_.empty()) { + // Start a rcc list process and parse the output + if (Process_) { + // Process is running already + if (Process_->IsFinished()) { + // Process is finished + if (!ProcessResult_.error()) { + // Process success + std::string parseError; + if (!RccListParseOutput(ProcessResult_.StdOut, ProcessResult_.StdErr, + Inputs_, parseError)) { + Log().ErrorFile(GeneratorT::RCC, QrcFile_, parseError); + Error_ = true; + } + } else { + Log().ErrorFile(GeneratorT::RCC, QrcFile_, + ProcessResult_.ErrorMessage); + Error_ = true; + } + // Clean up + Process_.reset(); + ProcessResult_.reset(); + } else { + // Process is not finished, yet. + return false; + } + } else { + // Start a new process + // rcc prints relative entry paths when started in the directory of the + // qrc file with a pathless qrc file name argument. + // This is important because on Windows absolute paths returned by rcc + // might contain bad multibyte characters when the qrc file path + // contains non-ASCII pcharacters. + std::vector cmd; + cmd.push_back(RccExecutable_); + cmd.insert(cmd.end(), RccListOptions_.begin(), RccListOptions_.end()); + cmd.push_back(QrcFileName_); + // We're done here if the process fails to start + return !StartProcess(QrcFileDir_, cmd, false); } - success = false; - } - if (success && !generate && !cmSystemTools::FileExists(rccFileAbs.c_str())) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(rccFileAbs); - generateReason += " from its source file "; - generateReason += cmQtAutoGen::Quoted(this->QrcFile); - generateReason += " because it doesn't exist"; + } else { + // rcc does not support the --list command. + // Read the qrc file content and parse it. + std::string qrcContent; + if (FileSys().FileRead(GeneratorT::RCC, qrcContent, QrcFile_)) { + RccListParseContent(qrcContent, Inputs_); } - generate = true; } - if (success && !generate && this->SettingsChanged) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(rccFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(this->QrcFile); - generateReason += " because the RCC settings changed"; - } - generate = true; + + if (!Inputs_.empty()) { + // Convert relative paths to absolute paths + RccListConvertFullPath(QrcFileDir_, Inputs_); + } + + return true; +} + +bool cmQtAutoGeneratorRcc::TestResources() +{ + if (Inputs_.empty()) { + return true; } - if (success && !generate) { + { std::string error; - if (FileIsOlderThan(rccFileAbs, this->QrcFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(rccFileAbs); - generateReason += " because it is older than "; - generateReason += cmQtAutoGen::Quoted(this->QrcFile); + for (std::string const& resFile : Inputs_) { + // Check if the resource file exists + if (!FileSys().FileExists(resFile)) { + error = "Could not find the resource file\n "; + error += Quoted(resFile); + error += '\n'; + Log().ErrorFile(GeneratorT::RCC, QrcFile_, error); + Error_ = true; + break; } - generate = true; - } else { + // Check if the resource file is newer than the build file + if (FileSys().FileIsOlderThan(RccFileBuild_, resFile, &error)) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(RccFileBuild_); + reason += " from "; + reason += Quoted(QrcFile_); + reason += " because it is older than "; + reason += Quoted(resFile); + Log().Info(GeneratorT::RCC, reason); + } + Generate_ = true; + break; + } + // Print error and break on demand if (!error.empty()) { - this->LogError(cmQtAutoGen::RCC, error); - success = false; + Log().ErrorFile(GeneratorT::RCC, QrcFile_, error); + Error_ = true; + break; } } } - if (success && !generate) { - // Acquire input file list - std::vector readFiles; - std::vector const* files = nullptr; - if (!this->Inputs.empty()) { - files = &this->Inputs; - } else { - // Read input file list from qrc file + + return Generate_; +} + +void cmQtAutoGeneratorRcc::TestInfoFile() +{ + // Test if the rcc output file is older than the info file + { + bool isOlder = false; + { std::string error; - if (cmQtAutoGen::RccListInputs(this->RccExecutable, this->RccListOptions, - this->QrcFile, readFiles, &error)) { - files = &readFiles; - } else { - this->LogFileError(cmQtAutoGen::RCC, this->QrcFile, error); - success = false; + isOlder = FileSys().FileIsOlderThan(RccFileBuild_, InfoFile(), &error); + if (!error.empty()) { + Log().ErrorFile(GeneratorT::RCC, QrcFile_, error); + Error_ = true; } } - // Test if any input file is newer than the build file - if (files != nullptr) { - std::string error; - for (std::string const& resFile : *files) { - if (!cmSystemTools::FileExists(resFile.c_str())) { - error = "Could not find the file\n "; - error += cmQtAutoGen::Quoted(resFile); - error += "\nwhich is listed in\n "; - error += cmQtAutoGen::Quoted(this->QrcFile); - break; - } - if (FileIsOlderThan(rccFileAbs, resFile, &error)) { - if (this->GetVerbose()) { - generateReason = "Generating "; - generateReason += cmQtAutoGen::Quoted(rccFileAbs); - generateReason += " from "; - generateReason += cmQtAutoGen::Quoted(this->QrcFile); - generateReason += " because it is older than "; - generateReason += cmQtAutoGen::Quoted(resFile); - } - generate = true; - break; - } - if (!error.empty()) { - break; - } - } - // Print error - if (!error.empty()) { - this->LogError(cmQtAutoGen::RCC, error); - success = false; + if (isOlder) { + if (Log().Verbose()) { + std::string reason = "Touching "; + reason += Quoted(RccFileBuild_); + reason += " because it is older than "; + reason += Quoted(InfoFile()); + Log().Info(GeneratorT::RCC, reason); } + // Touch build file + FileSys().Touch(RccFileBuild_); + BuildFileChanged_ = true; } } - // Regenerate on demand - if (generate) { - // Log - if (this->GetVerbose()) { - this->LogBold("Generating RCC source " + rccFileRel); - this->LogInfo(cmQtAutoGen::RCC, generateReason); - } +} - // Make sure the parent directory exists - if (this->MakeParentDirectory(cmQtAutoGen::RCC, rccFileAbs)) { - // Compose rcc command - std::vector cmd; - cmd.push_back(this->RccExecutable); - cmd.insert(cmd.end(), this->Options.begin(), this->Options.end()); - cmd.push_back("-o"); - cmd.push_back(rccFileAbs); - cmd.push_back(this->QrcFile); - - std::string output; - if (this->RunCommand(cmd, output)) { - // Success - rccGenerated = true; +void cmQtAutoGeneratorRcc::GenerateParentDir() +{ + // Make sure the parent directory exists + if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileBuild_)) { + Error_ = true; + } +} + +/** + * @return True when finished + */ +bool cmQtAutoGeneratorRcc::GenerateRcc() +{ + if (!Generate_) { + // Nothing to do + return true; + } + + if (Process_) { + // Process is running already + if (Process_->IsFinished()) { + // Process is finished + if (!ProcessResult_.error()) { + // Process success + BuildFileChanged_ = true; } else { + // Process failed { - std::string emsg = "rcc failed for\n "; - emsg += cmQtAutoGen::Quoted(this->QrcFile); - this->LogCommandError(cmQtAutoGen::RCC, emsg, cmd, output); + std::string emsg = "The rcc process failed to compile\n "; + emsg += Quoted(QrcFile_); + emsg += "\ninto\n "; + emsg += Quoted(RccFileBuild_); + if (ProcessResult_.error()) { + emsg += "\n"; + emsg += ProcessResult_.ErrorMessage; + } + Log().ErrorCommand(GeneratorT::RCC, emsg, Process_->Setup().Command, + ProcessResult_.StdOut); } - cmSystemTools::RemoveFile(rccFileAbs); - success = false; + FileSys().FileRemove(RccFileBuild_); + Error_ = true; } + // Clean up + Process_.reset(); + ProcessResult_.reset(); } else { - // Parent directory creation failed - success = false; + // Process is not finished, yet. + return false; } + } else { + // Start a rcc process + std::vector cmd; + cmd.push_back(RccExecutable_); + cmd.insert(cmd.end(), Options_.begin(), Options_.end()); + cmd.push_back("-o"); + cmd.push_back(RccFileBuild_); + cmd.push_back(QrcFile_); + // We're done here if the process fails to start + return !StartProcess(AutogenBuildDir_, cmd, true); } + return true; +} + +void cmQtAutoGeneratorRcc::GenerateWrapper() +{ // Generate a wrapper source file on demand - if (success && (this->MultiConfig == cmQtAutoGen::WRAP)) { + if (MultiConfig_ == MultiConfigT::WRAPPER) { // Wrapper file name - std::string const& wrapperFileAbs = this->RccFile; - std::string const wrapperFileRel = cmSystemTools::RelativePath( - this->AutogenBuildDir.c_str(), wrapperFileAbs.c_str()); + std::string const& wrapperAbs = RccFile_; // Wrapper file content std::string content = "// This is an autogenerated configuration " "wrapper file. Changes will be overwritten.\n" "#include \""; - content += cmSystemTools::GetFilenameName(rccFileRel); + content += cmSystemTools::GetFilenameName(RccFileBuild_); content += "\"\n"; // Write content to file - if (this->FileDiffers(wrapperFileAbs, content)) { + if (FileSys().FileDiffers(wrapperAbs, content)) { // Write new wrapper file - if (this->GetVerbose()) { - this->LogBold("Generating RCC wrapper " + wrapperFileRel); + if (Log().Verbose()) { + Log().Info(GeneratorT::RCC, "Generating RCC wrapper " + wrapperAbs); } - if (!this->FileWrite(cmQtAutoGen::RCC, wrapperFileAbs, content)) { - this->LogFileError(cmQtAutoGen::RCC, wrapperFileAbs, - "rcc wrapper file writing failed"); - success = false; + if (!FileSys().FileWrite(GeneratorT::RCC, wrapperAbs, content)) { + Log().ErrorFile(GeneratorT::RCC, wrapperAbs, + "RCC wrapper file writing failed"); + Error_ = true; } - } else if (rccGenerated) { + } else if (BuildFileChanged_) { // Just touch the wrapper file - if (this->GetVerbose()) { - this->LogInfo(cmQtAutoGen::RCC, - "Touching RCC wrapper " + wrapperFileRel); + if (Log().Verbose()) { + Log().Info(GeneratorT::RCC, "Touching RCC wrapper " + wrapperAbs); } - cmSystemTools::Touch(wrapperFileAbs, false); + FileSys().Touch(wrapperAbs); } } +} + +bool cmQtAutoGeneratorRcc::StartProcess( + std::string const& workingDirectory, std::vector const& command, + bool mergedOutput) +{ + // Log command + if (Log().Verbose()) { + std::string msg = "Running command:\n"; + msg += QuotedCommand(command); + msg += '\n'; + Log().Info(GeneratorT::RCC, msg); + } - return success; + // Create process handler + Process_ = cm::make_unique(); + Process_->setup(&ProcessResult_, mergedOutput, command, workingDirectory); + // Start process + if (!Process_->start(UVLoop(), + std::bind(&cm::uv_async_ptr::send, &UVRequest()))) { + Log().ErrorFile(GeneratorT::RCC, QrcFile_, ProcessResult_.ErrorMessage); + Error_ = true; + // Clean up + Process_.reset(); + ProcessResult_.reset(); + return false; + } + return true; } diff --git a/Source/cmQtAutoGeneratorRcc.h b/Source/cmQtAutoGeneratorRcc.h index 0e3f690..8a69c6c 100644 --- a/Source/cmQtAutoGeneratorRcc.h +++ b/Source/cmQtAutoGeneratorRcc.h @@ -5,52 +5,97 @@ #include "cmConfigure.h" // IWYU pragma: keep -#include "cmFilePathChecksum.h" #include "cmQtAutoGen.h" #include "cmQtAutoGenerator.h" +#include "cm_uv.h" #include #include class cmMakefile; +// @brief AUTORCC generator class cmQtAutoGeneratorRcc : public cmQtAutoGenerator { CM_DISABLE_COPY(cmQtAutoGeneratorRcc) public: cmQtAutoGeneratorRcc(); + ~cmQtAutoGeneratorRcc() override; private: - // -- Initialization & settings - bool InfoFileRead(cmMakefile* makefile); - void SettingsFileRead(cmMakefile* makefile); - bool SettingsFileWrite(); - // -- Central processing - bool Process(cmMakefile* makefile) override; - bool RccGenerate(); + // -- Types + /// @brief Processing stage + enum class StageT + { + SETTINGS_READ, + TEST_QRC_RCC_FILES, + TEST_RESOURCES_READ, + TEST_RESOURCES, + TEST_INFO_FILE, + GENERATE, + GENERATE_RCC, + GENERATE_WRAPPER, + SETTINGS_WRITE, + FINISH, + END + }; + + // -- Abstract processing interface + bool Init(cmMakefile* makefile) override; + bool Process() override; + // -- Process stage + static void UVPollStage(uv_async_t* handle); + void PollStage(); + void SetStage(StageT stage); + // -- Settings file + void SettingsFileRead(); + void SettingsFileWrite(); + // -- Tests + bool TestQrcRccFiles(); + bool TestResourcesRead(); + bool TestResources(); + void TestInfoFile(); + // -- Generation + void GenerateParentDir(); + bool GenerateRcc(); + void GenerateWrapper(); + + // -- Utility + bool StartProcess(std::string const& workingDirectory, + std::vector const& command, + bool mergedOutput); + +private: // -- Config settings - std::string ConfigSuffix; - cmQtAutoGen::MultiConfig MultiConfig; - // -- Settings - bool SettingsChanged; - std::string SettingsFile; - std::string SettingsString; + bool SettingsChanged_; + std::string ConfigSuffix_; + MultiConfigT MultiConfig_; // -- Directories - std::string ProjectSourceDir; - std::string ProjectBinaryDir; - std::string CurrentSourceDir; - std::string CurrentBinaryDir; - std::string AutogenBuildDir; - cmFilePathChecksum FilePathChecksum; + std::string AutogenBuildDir_; // -- Qt environment - std::string RccExecutable; - std::vector RccListOptions; + std::string RccExecutable_; + std::vector RccListOptions_; // -- Job - std::string QrcFile; - std::string RccFile; - std::vector Options; - std::vector Inputs; + std::string QrcFile_; + std::string QrcFileName_; + std::string QrcFileDir_; + std::string RccFile_; + std::string RccFileWrapper_; + std::string RccFileBuild_; + std::vector Options_; + std::vector Inputs_; + // -- Subprocess + ProcessResultT ProcessResult_; + std::unique_ptr Process_; + // -- Settings file + std::string SettingsFile_; + std::string SettingsString_; + // -- libuv loop + StageT Stage_; + bool Error_; + bool Generate_; + bool BuildFileChanged_; }; #endif diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 1974be3..663a4c9 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -247,6 +247,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type, this->SetPropertyDefault("AUTOMOC", nullptr); this->SetPropertyDefault("AUTOUIC", nullptr); this->SetPropertyDefault("AUTORCC", nullptr); + this->SetPropertyDefault("AUTOGEN_PARALLEL", nullptr); this->SetPropertyDefault("AUTOMOC_COMPILER_PREDEFINES", nullptr); this->SetPropertyDefault("AUTOMOC_DEPEND_FILTERS", nullptr); this->SetPropertyDefault("AUTOMOC_MACRO_NAMES", nullptr); -- cgit v0.12