diff options
Diffstat (limited to 'Source')
23 files changed, 2566 insertions, 1552 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 924d997..49f237f 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -350,8 +350,8 @@ set(SRCS cmQtAutoGenGlobalInitializer.h cmQtAutoGenInitializer.cxx cmQtAutoGenInitializer.h - cmQtAutoGeneratorMocUic.cxx - cmQtAutoGeneratorMocUic.h + cmQtAutoMocUic.cxx + cmQtAutoMocUic.h cmQtAutoRcc.cxx cmQtAutoRcc.h cmRST.cxx @@ -391,6 +391,8 @@ set(SRCS cmVariableWatch.h cmVersion.cxx cmVersion.h + cmWorkerPool.cxx + cmWorkerPool.h cmWorkingDirectory.cxx cmWorkingDirectory.h cmXMLParser.cxx diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index 70c44e1..636d332 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,5 +1,5 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 14) -set(CMake_VERSION_PATCH 20190412) +set(CMake_VERSION_PATCH 20190416) #set(CMake_VERSION_RC 1) diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx index a7d4455..9ad9669 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.cxx +++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx @@ -126,11 +126,12 @@ public: cmSystemTools::SetStdoutCallback([&s](std::string const& m) { s += m; }); cmSystemTools::SetStderrCallback([&s](std::string const& m) { s += m; }); - this->CM.SetProgressCallback( - [&s](const std::string& msg, float /*unused*/) { + this->CM.SetProgressCallback([&s](const std::string& msg, float prog) { + if (prog < 0) { s += msg; s += "\n"; - }); + } + }); } ~cmCTestBuildAndTestCaptureRAII() diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index d8e1c42..af409e4 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -2297,6 +2297,119 @@ static const TargetOutputNameArtifact<ArtifactLinkerTag> static const TargetOutputNameArtifact<ArtifactPdbTag> targetPdbOutputNameNode; +class ArtifactFilePrefixTag; +class ArtifactLinkerFilePrefixTag; +class ArtifactFileSuffixTag; +class ArtifactLinkerFileSuffixTag; + +template <typename ArtifactT> +struct TargetFileArtifactResultGetter +{ + static std::string Get(cmGeneratorTarget* target, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content); +}; + +template <> +struct TargetFileArtifactResultGetter<ArtifactFilePrefixTag> +{ + static std::string Get(cmGeneratorTarget* target, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent*) + { + return target->GetFilePrefix(context->Config); + } +}; +template <> +struct TargetFileArtifactResultGetter<ArtifactLinkerFilePrefixTag> +{ + static std::string Get(cmGeneratorTarget* target, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content) + { + if (!target->IsLinkable()) { + ::reportError(context, content->GetOriginalExpression(), + "TARGET_LINKER_PREFIX is allowed only for libraries and " + "executables with ENABLE_EXPORTS."); + return std::string(); + } + + cmStateEnums::ArtifactType artifact = + target->HasImportLibrary(context->Config) + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + + return target->GetFilePrefix(context->Config, artifact); + } +}; +template <> +struct TargetFileArtifactResultGetter<ArtifactFileSuffixTag> +{ + static std::string Get(cmGeneratorTarget* target, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent*) + { + return target->GetFileSuffix(context->Config); + } +}; +template <> +struct TargetFileArtifactResultGetter<ArtifactLinkerFileSuffixTag> +{ + static std::string Get(cmGeneratorTarget* target, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content) + { + if (!target->IsLinkable()) { + ::reportError(context, content->GetOriginalExpression(), + "TARGET_LINKER_SUFFIX is allowed only for libraries and " + "executables with ENABLE_EXPORTS."); + return std::string(); + } + + cmStateEnums::ArtifactType artifact = + target->HasImportLibrary(context->Config) + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + + return target->GetFileSuffix(context->Config, artifact); + } +}; + +template <typename ArtifactT> +struct TargetFileArtifact : public TargetArtifactBase +{ + TargetFileArtifact() {} // NOLINT(modernize-use-equals-default) + + int NumExpectedParameters() const override { return 1; } + + std::string Evaluate( + const std::vector<std::string>& parameters, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content, + cmGeneratorExpressionDAGChecker* dagChecker) const override + { + cmGeneratorTarget* target = + this->GetTarget(parameters, context, content, dagChecker); + if (!target) { + return std::string(); + } + + std::string result = + TargetFileArtifactResultGetter<ArtifactT>::Get(target, context, content); + if (context->HadError) { + return std::string(); + } + return result; + } +}; + +static const TargetFileArtifact<ArtifactFilePrefixTag> targetFilePrefixNode; +static const TargetFileArtifact<ArtifactLinkerFilePrefixTag> + targetLinkerFilePrefixNode; +static const TargetFileArtifact<ArtifactFileSuffixTag> targetFileSuffixNode; +static const TargetFileArtifact<ArtifactLinkerFileSuffixTag> + targetLinkerFileSuffixNode; + static const struct ShellPathNode : public cmGeneratorExpressionNode { ShellPathNode() {} // NOLINT(modernize-use-equals-default) @@ -2361,6 +2474,10 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode( { "TARGET_LINKER_FILE", &targetLinkerNodeGroup.File }, { "TARGET_SONAME_FILE", &targetSoNameNodeGroup.File }, { "TARGET_PDB_FILE", &targetPdbNodeGroup.File }, + { "TARGET_FILE_PREFIX", &targetFilePrefixNode }, + { "TARGET_LINKER_FILE_PREFIX", &targetLinkerFilePrefixNode }, + { "TARGET_FILE_SUFFIX", &targetFileSuffixNode }, + { "TARGET_LINKER_FILE_SUFFIX", &targetLinkerFileSuffixNode }, { "TARGET_FILE_NAME", &targetNodeGroup.FileName }, { "TARGET_LINKER_FILE_NAME", &targetLinkerNodeGroup.FileName }, { "TARGET_SONAME_FILE_NAME", &targetSoNameNodeGroup.FileName }, diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index f8c16cc..3fb95bf 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -464,6 +464,134 @@ std::string cmGeneratorTarget::GetOutputName( return i->second; } +std::string cmGeneratorTarget::GetFilePrefix( + const std::string& config, cmStateEnums::ArtifactType artifact) const +{ + if (this->IsImported()) { + const char* prefix = this->GetFilePrefixInternal(artifact); + + return prefix ? prefix : std::string(); + } + + std::string prefix, suffix, base; + this->GetFullNameInternal(config, artifact, prefix, base, suffix); + return prefix; +} +std::string cmGeneratorTarget::GetFileSuffix( + const std::string& config, cmStateEnums::ArtifactType artifact) const +{ + if (this->IsImported()) { + const char* suffix = this->GetFileSuffixInternal(artifact); + + return suffix ? suffix : std::string(); + } + + std::string prefix, suffix, base; + this->GetFullNameInternal(config, artifact, prefix, base, suffix); + return suffix; +} + +const char* cmGeneratorTarget::GetFilePrefixInternal( + cmStateEnums::ArtifactType artifact, const std::string& language) const +{ + // no prefix for non-main target types. + if (this->GetType() != cmStateEnums::STATIC_LIBRARY && + this->GetType() != cmStateEnums::SHARED_LIBRARY && + this->GetType() != cmStateEnums::MODULE_LIBRARY && + this->GetType() != cmStateEnums::EXECUTABLE) { + return nullptr; + } + + const bool isImportedLibraryArtifact = + (artifact == cmStateEnums::ImportLibraryArtifact); + + // Return an empty prefix for the import library if this platform + // does not support import libraries. + if (isImportedLibraryArtifact && + !this->Makefile->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) { + return nullptr; + } + + // The implib option is only allowed for shared libraries, module + // libraries, and executables. + if (this->GetType() != cmStateEnums::SHARED_LIBRARY && + this->GetType() != cmStateEnums::MODULE_LIBRARY && + this->GetType() != cmStateEnums::EXECUTABLE) { + artifact = cmStateEnums::RuntimeBinaryArtifact; + } + + // Compute prefix value. + const char* targetPrefix = + (isImportedLibraryArtifact ? this->GetProperty("IMPORT_PREFIX") + : this->GetProperty("PREFIX")); + + if (!targetPrefix) { + const char* prefixVar = this->Target->GetPrefixVariableInternal(artifact); + if (!language.empty() && prefixVar && *prefixVar) { + std::string langPrefix = prefixVar + std::string("_") + language; + targetPrefix = this->Makefile->GetDefinition(langPrefix); + } + + // if there is no prefix on the target nor specific language + // use the cmake definition. + if (!targetPrefix && prefixVar) { + targetPrefix = this->Makefile->GetDefinition(prefixVar); + } + } + + return targetPrefix; +} +const char* cmGeneratorTarget::GetFileSuffixInternal( + cmStateEnums::ArtifactType artifact, const std::string& language) const +{ + // no suffix for non-main target types. + if (this->GetType() != cmStateEnums::STATIC_LIBRARY && + this->GetType() != cmStateEnums::SHARED_LIBRARY && + this->GetType() != cmStateEnums::MODULE_LIBRARY && + this->GetType() != cmStateEnums::EXECUTABLE) { + return nullptr; + } + + const bool isImportedLibraryArtifact = + (artifact == cmStateEnums::ImportLibraryArtifact); + + // Return an empty suffix for the import library if this platform + // does not support import libraries. + if (isImportedLibraryArtifact && + !this->Makefile->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) { + return nullptr; + } + + // The implib option is only allowed for shared libraries, module + // libraries, and executables. + if (this->GetType() != cmStateEnums::SHARED_LIBRARY && + this->GetType() != cmStateEnums::MODULE_LIBRARY && + this->GetType() != cmStateEnums::EXECUTABLE) { + artifact = cmStateEnums::RuntimeBinaryArtifact; + } + + // Compute suffix value. + const char* targetSuffix = + (isImportedLibraryArtifact ? this->GetProperty("IMPORT_SUFFIX") + : this->GetProperty("SUFFIX")); + + if (!targetSuffix) { + const char* suffixVar = this->Target->GetSuffixVariableInternal(artifact); + if (!language.empty() && suffixVar && *suffixVar) { + std::string langSuffix = suffixVar + std::string("_") + language; + targetSuffix = this->Makefile->GetDefinition(langSuffix); + } + + // if there is no suffix on the target nor specific language + // use the cmake definition. + if (!targetSuffix && suffixVar) { + targetSuffix = this->Makefile->GetDefinition(suffixVar); + } + } + + return targetSuffix; +} + void cmGeneratorTarget::ClearSourcesCache() { this->KindedSourcesMap.clear(); @@ -3788,6 +3916,11 @@ void cmGeneratorTarget::GetFullNameInternal( return; } + // retrieve prefix and suffix + std::string ll = this->GetLinkerLanguage(config); + const char* targetPrefix = this->GetFilePrefixInternal(artifact, ll); + const char* targetSuffix = this->GetFileSuffixInternal(artifact, ll); + // The implib option is only allowed for shared libraries, module // libraries, and executables. if (this->GetType() != cmStateEnums::SHARED_LIBRARY && @@ -3797,12 +3930,6 @@ void cmGeneratorTarget::GetFullNameInternal( } // Compute the full name for main target types. - const char* targetPrefix = - (isImportedLibraryArtifact ? this->GetProperty("IMPORT_PREFIX") - : this->GetProperty("PREFIX")); - const char* targetSuffix = - (isImportedLibraryArtifact ? this->GetProperty("IMPORT_SUFFIX") - : this->GetProperty("SUFFIX")); const char* configPostfix = nullptr; if (!config.empty()) { std::string configProp = cmSystemTools::UpperCase(config); @@ -3814,30 +3941,6 @@ void cmGeneratorTarget::GetFullNameInternal( configPostfix = nullptr; } } - const char* prefixVar = this->Target->GetPrefixVariableInternal(artifact); - const char* suffixVar = this->Target->GetSuffixVariableInternal(artifact); - - // Check for language-specific default prefix and suffix. - std::string ll = this->GetLinkerLanguage(config); - if (!ll.empty()) { - if (!targetSuffix && suffixVar && *suffixVar) { - std::string langSuff = suffixVar + std::string("_") + ll; - targetSuffix = this->Makefile->GetDefinition(langSuff); - } - if (!targetPrefix && prefixVar && *prefixVar) { - std::string langPrefix = prefixVar + std::string("_") + ll; - targetPrefix = this->Makefile->GetDefinition(langPrefix); - } - } - - // if there is no prefix on the target use the cmake definition - if (!targetPrefix && prefixVar) { - targetPrefix = this->Makefile->GetSafeDefinition(prefixVar).c_str(); - } - // if there is no suffix on the target use the cmake definition - if (!targetSuffix && suffixVar) { - targetSuffix = this->Makefile->GetSafeDefinition(suffixVar).c_str(); - } // frameworks have directory prefix but no suffix std::string fw_prefix; diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 065b457..81f5255 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -534,6 +534,15 @@ public: std::string GetOutputName(const std::string& config, cmStateEnums::ArtifactType artifact) const; + /** Get target file prefix */ + std::string GetFilePrefix(const std::string& config, + cmStateEnums::ArtifactType artifact = + cmStateEnums::RuntimeBinaryArtifact) const; + /** Get target file prefix */ + std::string GetFileSuffix(const std::string& config, + cmStateEnums::ArtifactType artifact = + cmStateEnums::RuntimeBinaryArtifact) const; + /** Clears cached meta data for local and external source files. * The meta data will be recomputed on demand. */ @@ -728,6 +737,11 @@ private: mutable std::map<std::string, bool> DebugCompatiblePropertiesDone; + const char* GetFilePrefixInternal(cmStateEnums::ArtifactType artifact, + const std::string& language = "") const; + const char* GetFileSuffixInternal(cmStateEnums::ArtifactType artifact, + const std::string& language = "") const; + std::string GetFullNameInternal(const std::string& config, cmStateEnums::ArtifactType artifact) const; void GetFullNameInternal(const std::string& config, diff --git a/Source/cmGhsMultiGpj.cxx b/Source/cmGhsMultiGpj.cxx index 8b69b51..da27971 100644 --- a/Source/cmGhsMultiGpj.cxx +++ b/Source/cmGhsMultiGpj.cxx @@ -9,7 +9,8 @@ static const char* GHS_TAG[] = { "[INTEGRITY Application]", "[Project]", "[Program]", "[Reference]", - "[Subproject]" }; + "[Subproject]", + "[Custom Target]" }; const char* GhsMultiGpj::GetGpjTag(Types gpjType) { @@ -21,6 +22,7 @@ const char* GhsMultiGpj::GetGpjTag(Types gpjType) case PROGRAM: case REFERENCE: case SUBPROJECT: + case CUSTOM_TARGET: tag = GHS_TAG[gpjType]; break; default: diff --git a/Source/cmGhsMultiGpj.h b/Source/cmGhsMultiGpj.h index 420eab1..e588150 100644 --- a/Source/cmGhsMultiGpj.h +++ b/Source/cmGhsMultiGpj.h @@ -16,7 +16,8 @@ public: PROJECT, PROGRAM, REFERENCE, - SUBPROJECT + SUBPROJECT, + CUSTOM_TARGET }; static void WriteGpjTag(Types gpjType, std::ostream& fout); diff --git a/Source/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx index 668bcbd..b80da72 100644 --- a/Source/cmGhsMultiTargetGenerator.cxx +++ b/Source/cmGhsMultiTargetGenerator.cxx @@ -3,7 +3,7 @@ #include "cmGhsMultiTargetGenerator.h" #include "cmCustomCommand.h" -#include "cmCustomCommandLines.h" +#include "cmCustomCommandGenerator.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobalGhsMultiGenerator.h" @@ -11,17 +11,17 @@ #include "cmLocalGenerator.h" #include "cmLocalGhsMultiGenerator.h" #include "cmMakefile.h" +#include "cmOutputConverter.h" #include "cmSourceFile.h" +#include "cmSourceFileLocation.h" #include "cmSourceGroup.h" #include "cmStateDirectory.h" #include "cmStateSnapshot.h" #include "cmStateTypes.h" #include "cmSystemTools.h" #include "cmTarget.h" -#include "cmTargetDepend.h" #include <algorithm> -#include <assert.h> #include <ostream> #include <set> #include <utility> @@ -32,6 +32,11 @@ cmGhsMultiTargetGenerator::cmGhsMultiTargetGenerator(cmGeneratorTarget* target) static_cast<cmLocalGhsMultiGenerator*>(target->GetLocalGenerator())) , Makefile(target->Target->GetMakefile()) , Name(target->GetName()) +#ifdef _WIN32 + , CmdWindowsShell(true) +#else + , CmdWindowsShell(false) +#endif { // Store the configuration name that is being used if (const char* config = this->Makefile->GetDefinition("CMAKE_BUILD_TYPE")) { @@ -85,10 +90,19 @@ void cmGhsMultiTargetGenerator::Generate() return; } case cmStateEnums::UTILITY: { - std::string msg = "add_custom_target(<name> ...) not supported: "; - msg += this->Name; - cmSystemTools::Message(msg); - return; + this->TargetNameReal = this->GeneratorTarget->GetName(); + this->TagType = GhsMultiGpj::CUSTOM_TARGET; + break; + } + case cmStateEnums::GLOBAL_TARGET: { + this->TargetNameReal = this->GeneratorTarget->GetName(); + if (this->TargetNameReal == + this->GetGlobalGenerator()->GetInstallTargetName()) { + this->TagType = GhsMultiGpj::CUSTOM_TARGET; + } else { + return; + } + break; } default: return; @@ -105,29 +119,29 @@ void cmGhsMultiTargetGenerator::Generate() void cmGhsMultiTargetGenerator::GenerateTarget() { - // Open the filestream in copy-if-different mode. - std::string fname = this->LocalGenerator->GetCurrentBinaryDirectory(); - fname += "/"; - fname += this->Name; - fname += cmGlobalGhsMultiGenerator::FILE_EXTENSION; - cmGeneratedFileStream fout(fname); + // Open the target file in copy-if-different mode. + std::string fproj = this->LocalGenerator->GetCurrentBinaryDirectory(); + fproj += "/"; + fproj += this->Name; + fproj += cmGlobalGhsMultiGenerator::FILE_EXTENSION; + cmGeneratedFileStream fout(fproj); fout.SetCopyIfDifferent(true); this->GetGlobalGenerator()->WriteFileHeader(fout); GhsMultiGpj::WriteGpjTag(this->TagType, fout); - const std::string language( - this->GeneratorTarget->GetLinkerLanguage(this->ConfigName)); - - this->WriteTargetSpecifics(fout, this->ConfigName); - this->SetCompilerFlags(this->ConfigName, language); - this->WriteCompilerFlags(fout, this->ConfigName, language); - this->WriteCompilerDefinitions(fout, this->ConfigName, language); - this->WriteIncludes(fout, this->ConfigName, language); - this->WriteTargetLinkLine(fout, this->ConfigName); - this->WriteCustomCommands(fout); + if (this->TagType != GhsMultiGpj::CUSTOM_TARGET) { + const std::string language( + this->GeneratorTarget->GetLinkerLanguage(this->ConfigName)); + this->WriteTargetSpecifics(fout, this->ConfigName); + this->SetCompilerFlags(this->ConfigName, language); + this->WriteCompilerFlags(fout, this->ConfigName, language); + this->WriteCompilerDefinitions(fout, this->ConfigName, language); + this->WriteIncludes(fout, this->ConfigName, language); + this->WriteTargetLinkLine(fout, this->ConfigName); + this->WriteBuildEvents(fout); + } this->WriteSources(fout); - this->WriteReferences(fout); fout.Close(); } @@ -304,47 +318,145 @@ void cmGhsMultiTargetGenerator::WriteTargetLinkLine(std::ostream& fout, } } -void cmGhsMultiTargetGenerator::WriteCustomCommands(std::ostream& fout) +void cmGhsMultiTargetGenerator::WriteBuildEvents(std::ostream& fout) { - WriteCustomCommandsHelper(fout, this->GeneratorTarget->GetPreBuildCommands(), - cmTarget::PRE_BUILD); - WriteCustomCommandsHelper( - fout, this->GeneratorTarget->GetPostBuildCommands(), cmTarget::POST_BUILD); + this->WriteBuildEventsHelper( + fout, this->GeneratorTarget->GetPreBuildCommands(), + std::string("prebuild"), std::string("preexecShell")); + + if (this->TagType != GhsMultiGpj::CUSTOM_TARGET) { + this->WriteBuildEventsHelper( + fout, this->GeneratorTarget->GetPreLinkCommands(), + std::string("prelink"), std::string("preexecShell")); + } + + this->WriteBuildEventsHelper( + fout, this->GeneratorTarget->GetPostBuildCommands(), + std::string("postbuild"), std::string("postexecShell")); +} + +void cmGhsMultiTargetGenerator::WriteBuildEventsHelper( + std::ostream& fout, const std::vector<cmCustomCommand>& ccv, + std::string const& name, std::string const& cmd) +{ + int cmdcount = 0; + + for (cmCustomCommand const& cc : ccv) { + cmCustomCommandGenerator ccg(cc, this->ConfigName, this->LocalGenerator); + // Open the filestream for this custom command + std::string fname = this->LocalGenerator->GetCurrentBinaryDirectory(); + fname += + "/" + this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget); + fname += "/" + this->Name + "_" + name; + fname += std::to_string(cmdcount++); + fname += this->CmdWindowsShell ? ".bat" : ".sh"; + cmGeneratedFileStream f(fname); + f.SetCopyIfDifferent(true); + this->WriteCustomCommandsHelper(f, ccg); + f.Close(); + if (this->TagType != GhsMultiGpj::CUSTOM_TARGET) { + fout << " :" << cmd << "=\"" << fname << "\"" << std::endl; + } else { + fout << fname << std::endl; + fout << " :outputName=\"" << fname << ".rule\"" << std::endl; + } + for (auto& byp : ccg.GetByproducts()) { + fout << " :extraOutputFile=\"" << byp << "\"" << std::endl; + } + } } void cmGhsMultiTargetGenerator::WriteCustomCommandsHelper( - std::ostream& fout, std::vector<cmCustomCommand> const& commandsSet, - cmTarget::CustomCommandType const commandType) + std::ostream& fout, cmCustomCommandGenerator const& ccg) { - for (cmCustomCommand const& customCommand : commandsSet) { - cmCustomCommandLines const& commandLines = customCommand.GetCommandLines(); - for (cmCustomCommandLine const& command : commandLines) { - switch (commandType) { - case cmTarget::PRE_BUILD: - fout << " :preexecShellSafe="; - break; - case cmTarget::POST_BUILD: - fout << " :postexecShellSafe="; - break; - default: - assert("Only pre and post are supported"); + std::vector<std::string> cmdLines; + + // if the command specified a working directory use it. + std::string dir = this->LocalGenerator->GetCurrentBinaryDirectory(); + std::string currentBinDir = dir; + std::string workingDir = ccg.GetWorkingDirectory(); + if (!workingDir.empty()) { + dir = workingDir; + } + + // Line to check for error between commands. +#ifdef _WIN32 + std::string check_error = "if %errorlevel% neq 0 exit /b %errorlevel%"; +#else + std::string check_error = "if [[ $? -ne 0 ]]; then exit 1; fi"; +#endif + +#ifdef _WIN32 + cmdLines.push_back("@echo off"); +#endif + // Echo the custom command's comment text. + const char* comment = ccg.GetComment(); + if (comment && *comment) { + std::string echocmd = "echo "; + echocmd += comment; + cmdLines.push_back(std::move(echocmd)); + } + + // Switch to working directory + std::string cdCmd; +#ifdef _WIN32 + std::string cdStr = "cd /D "; +#else + std::string cdStr = "cd "; +#endif + cdCmd = cdStr + + this->LocalGenerator->ConvertToOutputFormat(dir, cmOutputConverter::SHELL); + cmdLines.push_back(std::move(cdCmd)); + + for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) { + // Build the command line in a single string. + std::string cmd = ccg.GetCommand(c); + if (!cmd.empty()) { + // Use "call " before any invocations of .bat or .cmd files + // invoked as custom commands in the WindowsShell. + // + bool useCall = false; + + if (this->CmdWindowsShell) { + std::string suffix; + if (cmd.size() > 4) { + suffix = cmSystemTools::LowerCase(cmd.substr(cmd.size() - 4)); + if (suffix == ".bat" || suffix == ".cmd") { + useCall = true; + } + } } - bool firstIteration = true; - for (std::string const& commandLine : command) { - std::string subCommandE = - this->LocalGenerator->EscapeForShell(commandLine, true); - fout << (firstIteration ? "'" : " "); - // Need to double escape backslashes - cmSystemTools::ReplaceString(subCommandE, "\\", "\\\\"); - fout << subCommandE; - firstIteration = false; + cmSystemTools::ReplaceString(cmd, "/./", "/"); + // Convert the command to a relative path only if the current + // working directory will be the start-output directory. + bool had_slash = cmd.find('/') != std::string::npos; + if (workingDir.empty()) { + cmd = + this->LocalGenerator->MaybeConvertToRelativePath(currentBinDir, cmd); } - if (!command.empty()) { - fout << "'" << std::endl; + bool has_slash = cmd.find('/') != std::string::npos; + if (had_slash && !has_slash) { + // This command was specified as a path to a file in the + // current directory. Add a leading "./" so it can run + // without the current directory being in the search path. + cmd = "./" + cmd; } + cmd = this->LocalGenerator->ConvertToOutputFormat( + cmd, cmOutputConverter::SHELL); + if (useCall) { + cmd = "call " + cmd; + } + ccg.AppendArguments(c, cmd); + cmdLines.push_back(std::move(cmd)); } } + + // push back the custom commands + for (auto const& c : cmdLines) { + fout << c << std::endl; + fout << check_error << std::endl; + } } void cmGhsMultiTargetGenerator::WriteSourceProperty( @@ -385,7 +497,7 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) /* list of known groups and the order they are displayed in a project file */ const std::vector<std::string> standardGroups = { - "Header Files", "Source Files", "CMake Rules", + "CMake Rules", "Header Files", "Source Files", "Object Files", "Object Libraries", "Resources" }; @@ -403,6 +515,14 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) groupFilesList[i] = *n; i += 1; groupNames.erase(gn); + } else if (this->TagType == GhsMultiGpj::CUSTOM_TARGET && + gn == "CMake Rules") { + /* make sure that rules folder always exists in case of custom targets + * that have no custom commands except for pre or post build events. + */ + groupFilesList.resize(groupFilesList.size() + 1); + groupFilesList[i] = gn; + i += 1; } } @@ -433,7 +553,7 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) /* write files into the proper project file * -- groups go into main project file - * unless FOLDER property or variable is set. + * unless NO_SOURCE_GROUP_FILE property or variable is set. */ for (auto& sg : groupFilesList) { std::ostream* fout; @@ -472,33 +592,98 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) } else { *fout << "{comment} " << sg << std::endl; } + } else if (sg.empty()) { + *fout << "{comment} Others" << std::endl; } - /* output rule for each source file */ - for (const cmSourceFile* si : groupFiles[sg]) { - - // Convert filename to native system - // WORKAROUND: GHS MULTI 6.1.4 and 6.1.6 are known to need backslash on - // windows when opening some files from the search window. - std::string fname(si->GetFullPath()); - cmSystemTools::ConvertToOutputSlashes(fname); - *fout << fname << std::endl; - - if ("ld" != si->GetExtension() && "int" != si->GetExtension() && - "bsp" != si->GetExtension()) { - WriteObjectLangOverride(*fout, si); + if (sg != "CMake Rules") { + /* output rule for each source file */ + for (const cmSourceFile* si : groupFiles[sg]) { + bool compile = true; + // Convert filename to native system + // WORKAROUND: GHS MULTI 6.1.4 and 6.1.6 are known to need backslash on + // windows when opening some files from the search window. + std::string fname(si->GetFullPath()); + cmSystemTools::ConvertToOutputSlashes(fname); + + /* For custom targets list any associated sources, + * comment out source code to prevent it from being + * compiled when processing this target. + * Otherwise, comment out any custom command (main) dependencies that + * are listed as source files to prevent them from being considered + * part of the build. + */ + std::string comment; + if ((this->TagType == GhsMultiGpj::CUSTOM_TARGET && + !si->GetLanguage().empty()) || + si->GetCustomCommand()) { + comment = "{comment} "; + compile = false; + } + + *fout << comment << fname << std::endl; + if (compile) { + if ("ld" != si->GetExtension() && "int" != si->GetExtension() && + "bsp" != si->GetExtension()) { + WriteObjectLangOverride(*fout, si); + } + + this->WriteSourceProperty(*fout, si, "INCLUDE_DIRECTORIES", "-I"); + this->WriteSourceProperty(*fout, si, "COMPILE_DEFINITIONS", "-D"); + this->WriteSourceProperty(*fout, si, "COMPILE_OPTIONS", ""); + + /* to avoid clutter in the GUI only print out the objectName if it + * has been renamed */ + std::string objectName = this->GeneratorTarget->GetObjectName(si); + if (!objectName.empty() && + this->GeneratorTarget->HasExplicitObjectName(si)) { + *fout << " -o " << objectName << std::endl; + } + } } - - this->WriteSourceProperty(*fout, si, "INCLUDE_DIRECTORIES", "-I"); - this->WriteSourceProperty(*fout, si, "COMPILE_DEFINITIONS", "-D"); - this->WriteSourceProperty(*fout, si, "COMPILE_OPTIONS", ""); - - /* to avoid clutter in the gui only print out the objectName if it has - * been renamed */ - std::string objectName = this->GeneratorTarget->GetObjectName(si); - if (!objectName.empty() && - this->GeneratorTarget->HasExplicitObjectName(si)) { - *fout << " -o " << objectName << std::endl; + } else { + std::vector<cmSourceFile const*> customCommands; + if (ComputeCustomCommandOrder(customCommands)) { + std::string message = "The custom commands for target [" + + this->GeneratorTarget->GetName() + "] had a cycle.\n"; + cmSystemTools::Error(message); + } else { + /* Custom targets do not have a dependency on SOURCES files. + * Therefore the dependency list may include SOURCES files after the + * custom target. Because nothing can depend on the custom target just + * move it to the last item. + */ + for (auto sf = customCommands.begin(); sf != customCommands.end(); + ++sf) { + if (((*sf)->GetLocation()).GetName() == this->Name + ".rule") { + std::rotate(sf, sf + 1, customCommands.end()); + break; + } + } + int cmdcount = 0; + for (auto& sf : customCommands) { + const cmCustomCommand* cc = sf->GetCustomCommand(); + cmCustomCommandGenerator ccg(*cc, this->ConfigName, + this->LocalGenerator); + + // Open the filestream for this custom command + std::string fname = + this->LocalGenerator->GetCurrentBinaryDirectory(); + fname += "/" + + this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget); + fname += "/" + this->Name + "_cc"; + fname += std::to_string(cmdcount++) + "_"; + fname += (sf->GetLocation()).GetName(); + fname += this->CmdWindowsShell ? ".bat" : ".sh"; + cmGeneratedFileStream f(fname); + f.SetCopyIfDifferent(true); + this->WriteCustomCommandsHelper(f, ccg); + f.Close(); + this->WriteCustomCommandLine(*fout, fname, ccg); + } + } + if (this->TagType == GhsMultiGpj::CUSTOM_TARGET) { + this->WriteBuildEvents(*fout); } } } @@ -508,6 +693,33 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) } } +void cmGhsMultiTargetGenerator::WriteCustomCommandLine( + std::ostream& fout, std::string& fname, cmCustomCommandGenerator const& ccg) +{ + /* NOTE: Customization Files are not well documented. Testing showed + * that ":outputName=file" can only be used once per script. The + * script will only run if ":outputName=file" is missing or just run + * once if ":outputName=file" is not specified. If there are + * multiple outputs then the script needs to be listed multiple times + * for each output. Otherwise it won't rerun the script if one of + * the outputs is manually deleted. + */ + bool specifyExtra = true; + for (auto& out : ccg.GetOutputs()) { + fout << fname << std::endl; + fout << " :outputName=\"" << out << "\"" << std::endl; + if (specifyExtra) { + for (auto& byp : ccg.GetByproducts()) { + fout << " :extraOutputFile=\"" << byp << "\"" << std::endl; + } + for (auto& dep : ccg.GetDepends()) { + fout << " :depends=\"" << dep << "\"" << std::endl; + } + specifyExtra = false; + } + } +} + void cmGhsMultiTargetGenerator::WriteObjectLangOverride( std::ostream& fout, const cmSourceFile* sourceFile) { @@ -521,35 +733,6 @@ void cmGhsMultiTargetGenerator::WriteObjectLangOverride( } } -void cmGhsMultiTargetGenerator::WriteReferences(std::ostream& fout) -{ - // This only applies to INTEGRITY Applications - if (this->TagType != GhsMultiGpj::INTERGRITY_APPLICATION) { - return; - } - - // Get the targets that this one depends upon - cmTargetDependSet unordered = - this->GetGlobalGenerator()->GetTargetDirectDepends(this->GeneratorTarget); - cmGlobalGhsMultiGenerator::OrderedTargetDependSet ordered(unordered, - this->Name); - for (auto& t : ordered) { - std::string tname = t->GetName(); - std::string tpath = t->LocalGenerator->GetCurrentBinaryDirectory(); - std::string rootpath = this->LocalGenerator->GetCurrentBinaryDirectory(); - std::string outpath = - this->LocalGenerator->MaybeConvertToRelativePath(rootpath, tpath) + "/" + - tname + "REF" + cmGlobalGhsMultiGenerator::FILE_EXTENSION; - - fout << outpath; - fout << " "; - GhsMultiGpj::WriteGpjTag(GhsMultiGpj::REFERENCE, fout); - - // Tell the global generator that a refernce project needs to be created - t->Target->SetProperty("GHS_REFERENCE_PROJECT", "ON"); - } -} - bool cmGhsMultiTargetGenerator::DetermineIfIntegrityApp() { const char* p = this->GeneratorTarget->GetProperty("ghs_integrity_app"); @@ -566,3 +749,51 @@ bool cmGhsMultiTargetGenerator::DetermineIfIntegrityApp() } return false; } + +bool cmGhsMultiTargetGenerator::ComputeCustomCommandOrder( + std::vector<cmSourceFile const*>& order) +{ + std::set<cmSourceFile const*> temp; + std::set<cmSourceFile const*> perm; + + // Collect all custom commands for this target + std::vector<cmSourceFile const*> customCommands; + this->GeneratorTarget->GetCustomCommands(customCommands, this->ConfigName); + + for (cmSourceFile const* si : customCommands) { + bool r = VisitCustomCommand(temp, perm, order, si); + if (r) { + return r; + } + } + return false; +} + +bool cmGhsMultiTargetGenerator::VisitCustomCommand( + std::set<cmSourceFile const*>& temp, std::set<cmSourceFile const*>& perm, + std::vector<cmSourceFile const*>& order, cmSourceFile const* si) +{ + /* check if permanent mark is set*/ + if (perm.find(si) == perm.end()) { + /* set temporary mark; check if revisit*/ + if (temp.insert(si).second) { + for (auto& di : si->GetCustomCommand()->GetDepends()) { + cmSourceFile const* sf = this->GeneratorTarget->GetLocalGenerator() + ->GetMakefile() + ->GetSourceFileWithOutput(di); + /* if sf exists then visit */ + if (sf && this->VisitCustomCommand(temp, perm, order, sf)) { + return true; + } + } + /* mark as complete; insert into beginning of list*/ + perm.insert(si); + order.push_back(si); + return false; + } + /* revisiting item - not a DAG */ + return true; + } + /* already complete */ + return false; +} diff --git a/Source/cmGhsMultiTargetGenerator.h b/Source/cmGhsMultiTargetGenerator.h index a4e23d9..a131567 100644 --- a/Source/cmGhsMultiTargetGenerator.h +++ b/Source/cmGhsMultiTargetGenerator.h @@ -5,14 +5,14 @@ #include "cmGhsMultiGpj.h" -#include "cmTarget.h" - #include <iosfwd> #include <map> +#include <set> #include <string> #include <vector> class cmCustomCommand; +class cmCustomCommandGenerator; class cmGeneratorTarget; class cmGlobalGhsMultiGenerator; class cmLocalGhsMultiGenerator; @@ -49,15 +49,23 @@ private: void WriteIncludes(std::ostream& fout, const std::string& config, const std::string& language); void WriteTargetLinkLine(std::ostream& fout, std::string const& config); - void WriteCustomCommands(std::ostream& fout); - void WriteCustomCommandsHelper( - std::ostream& fout, std::vector<cmCustomCommand> const& commandsSet, - cmTarget::CustomCommandType commandType); + void WriteBuildEvents(std::ostream& fout); + void WriteBuildEventsHelper(std::ostream& fout, + const std::vector<cmCustomCommand>& ccv, + std::string const& name, std::string const& cmd); + void WriteCustomCommandsHelper(std::ostream& fout, + cmCustomCommandGenerator const& ccg); + void WriteCustomCommandLine(std::ostream& fout, std::string& fname, + cmCustomCommandGenerator const& ccg); + bool ComputeCustomCommandOrder(std::vector<cmSourceFile const*>& order); + bool VisitCustomCommand(std::set<cmSourceFile const*>& temp, + std::set<cmSourceFile const*>& perm, + std::vector<cmSourceFile const*>& order, + cmSourceFile const* sf); void WriteSources(std::ostream& fout_proj); void WriteSourceProperty(std::ostream& fout, const cmSourceFile* sf, std::string const& propName, std::string const& propFlag); - void WriteReferences(std::ostream& fout); static void WriteObjectLangOverride(std::ostream& fout, const cmSourceFile* sourceFile); @@ -71,7 +79,8 @@ private: std::string TargetNameReal; GhsMultiGpj::Types TagType; std::string const Name; - std::string ConfigName; /* CMAKE_BUILD_TYPE */ + std::string ConfigName; /* CMAKE_BUILD_TYPE */ + bool const CmdWindowsShell; /* custom commands run in cmd.exe or /bin/sh */ }; #endif // ! cmGhsMultiTargetGenerator_h diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx index 9f361f6..b69dea0 100644 --- a/Source/cmGlobalGhsMultiGenerator.cxx +++ b/Source/cmGlobalGhsMultiGenerator.cxx @@ -265,14 +265,68 @@ void cmGlobalGhsMultiGenerator::WriteFileHeader(std::ostream& fout) << std::endl; } -void cmGlobalGhsMultiGenerator::WriteTopLevelProject( - std::ostream& fout, cmLocalGenerator* root, - std::vector<cmLocalGenerator*>& generators) +void cmGlobalGhsMultiGenerator::WriteCustomRuleBOD(std::ostream& fout) { - WriteFileHeader(fout); + fout << "Commands {\n" + " Custom_Rule_Command {\n" + " name = \"Custom Rule Command\"\n" + " exec = \""; +#ifdef _WIN32 + fout << "cmd.exe"; +#else + fout << "/bin/sh"; +#endif + fout << "\"\n" + " options = {\"SpecialOptions\"}\n" + " }\n" + "}\n"; + + fout << "\n\n"; + fout << "FileTypes {\n" + " CmakeRule {\n" + " name = \"Custom Rule\"\n" + " action = \"&Run\"\n" + " extensions = {\""; +#ifdef _WIN32 + fout << "bat"; +#else + fout << "sh"; +#endif + fout << "\"}\n" + " grepable = false\n" + " command = \"Custom Rule Command\"\n" + " commandLine = \"$COMMAND "; +#ifdef _WIN32 + fout << "/c"; +#endif + fout << " $INPUTFILE\"\n" + " progress = \"Processing Custom Rule\"\n" + " promoteToFirstPass = true\n" + " outputType = \"None\"\n" + " color = \"#800080\"\n" + " }\n" + "}\n"; +} - this->WriteMacros(fout); - this->WriteHighLevelDirectives(fout); +void cmGlobalGhsMultiGenerator::WriteCustomTargetBOD(std::ostream& fout) +{ + fout << "FileTypes {\n" + " CmakeTarget {\n" + " name = \"Custom Target\"\n" + " action = \"&Execute\"\n" + " grepable = false\n" + " outputType = \"None\"\n" + " color = \"#800080\"\n" + " }\n" + "}\n"; +} + +void cmGlobalGhsMultiGenerator::WriteTopLevelProject(std::ostream& fout, + cmLocalGenerator* root) +{ + this->WriteFileHeader(fout); + this->WriteMacros(fout, root); + this->WriteHighLevelDirectives(root, fout); GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fout); fout << "# Top Level Project File" << std::endl; @@ -298,71 +352,152 @@ void cmGlobalGhsMultiGenerator::WriteTopLevelProject( } fout << "\"" << this->OsDir << "\"" << std::endl; } - - WriteSubProjects(fout, root, generators); } -void cmGlobalGhsMultiGenerator::WriteSubProjects( - std::ostream& fout, cmLocalGenerator* root, - std::vector<cmLocalGenerator*>& generators) +void cmGlobalGhsMultiGenerator::WriteSubProjects(std::ostream& fout, + std::string& all_target) { - // Collect all targets under this root generator and the transitive - // closure of their dependencies. - TargetDependSet projectTargets; - TargetDependSet originalTargets; - this->GetTargetSets(projectTargets, originalTargets, root, generators); - OrderedTargetDependSet orderedProjectTargets(projectTargets, ""); - - // write out all the sub-projects - std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); - for (cmGeneratorTarget const* target : orderedProjectTargets) { - if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY) { + fout << "CMakeFiles/" << all_target << " [Project]" << std::endl; + // All known targets + for (cmGeneratorTarget const* target : this->ProjectTargets) { + if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || + target->GetType() == cmStateEnums::MODULE_LIBRARY || + target->GetType() == cmStateEnums::SHARED_LIBRARY || + (target->GetType() == cmStateEnums::GLOBAL_TARGET && + target->GetName() != GetInstallTargetName())) { continue; } + fout << "CMakeFiles/" << target->GetName() + ".tgt" + FILE_EXTENSION + << " [Project]" << std::endl; + } +} - const char* projName = target->GetProperty("GENERATOR_FILE_NAME"); - const char* projType = target->GetProperty("GENERATOR_FILE_NAME_EXT"); - if (projName && projType) { - cmLocalGenerator* lg = target->GetLocalGenerator(); - std::string dir = lg->GetCurrentBinaryDirectory(); - dir = root->MaybeConvertToRelativePath(rootBinaryDir, dir); - if (dir == ".") { - dir.clear(); - } else { - if (dir.back() != '/') { - dir += "/"; - } +void cmGlobalGhsMultiGenerator::WriteProjectLine( + std::ostream& fout, cmGeneratorTarget const* target, cmLocalGenerator* root, + std::string& rootBinaryDir) +{ + const char* projName = target->GetProperty("GENERATOR_FILE_NAME"); + const char* projType = target->GetProperty("GENERATOR_FILE_NAME_EXT"); + if (projName && projType) { + cmLocalGenerator* lg = target->GetLocalGenerator(); + std::string dir = lg->GetCurrentBinaryDirectory(); + dir = root->MaybeConvertToRelativePath(rootBinaryDir, dir); + if (dir == ".") { + dir.clear(); + } else { + if (dir.back() != '/') { + dir += "/"; } + } - if (cmSystemTools::IsOn(target->GetProperty("EXCLUDE_FROM_ALL"))) { - fout << "{comment} "; - } - std::string projFile = dir + projName + FILE_EXTENSION; - fout << projFile; - fout << " " << projType << std::endl; + std::string projFile = dir + projName + FILE_EXTENSION; + fout << projFile; + fout << " " << projType << std::endl; + } else { + /* Should never happen */ + std::string message = + "The project file for target [" + target->GetName() + "] is missing.\n"; + cmSystemTools::Error(message); + fout << "{comment} " << target->GetName() << " [missing project file]\n"; + } +} - if (cmSystemTools::IsOn(target->GetProperty("GHS_REFERENCE_PROJECT"))) { - // create reference project - std::string fname = dir; - fname += target->GetName(); - fname += "REF"; - fname += FILE_EXTENSION; +void cmGlobalGhsMultiGenerator::WriteTargets(cmLocalGenerator* root) +{ + std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); + rootBinaryDir += "/CMakeFiles"; + + // All known targets + for (cmGeneratorTarget const* target : this->ProjectTargets) { + if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || + target->GetType() == cmStateEnums::MODULE_LIBRARY || + target->GetType() == cmStateEnums::SHARED_LIBRARY || + (target->GetType() == cmStateEnums::GLOBAL_TARGET && + target->GetName() != GetInstallTargetName())) { + continue; + } - cmGeneratedFileStream fref(fname); - fref.SetCopyIfDifferent(true); + // create target build file + std::string name = target->GetName() + ".tgt" + FILE_EXTENSION; + std::string fname = rootBinaryDir + "/" + name; + cmGeneratedFileStream fbld(fname); + fbld.SetCopyIfDifferent(true); + this->WriteFileHeader(fbld); + GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld); + std::vector<cmGeneratorTarget const*> build; + if (ComputeTargetBuildOrder(target, build)) { + std::string message = "The inter-target dependency graph for target [" + + target->GetName() + "] had a cycle.\n"; + cmSystemTools::Error(message); + } else { + for (auto& tgt : build) { + WriteProjectLine(fbld, tgt, root, rootBinaryDir); + } + } + fbld.Close(); + } +} - this->WriteFileHeader(fref); - GhsMultiGpj::WriteGpjTag(GhsMultiGpj::REFERENCE, fref); - fref << " :reference=" << projFile << std::endl; +void cmGlobalGhsMultiGenerator::WriteAllTarget( + cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators, + std::string& all_target) +{ + this->ProjectTargets.clear(); + + // create target build file + all_target = root->GetProjectName() + "." + this->GetAllTargetName() + + ".tgt" + FILE_EXTENSION; + std::string fname = + root->GetCurrentBinaryDirectory() + "/CMakeFiles/" + all_target; + cmGeneratedFileStream fbld(fname); + fbld.SetCopyIfDifferent(true); + this->WriteFileHeader(fbld); + GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld); - fref.Close(); + // Collect all targets under this root generator and the transitive + // closure of their dependencies. + TargetDependSet projectTargets; + TargetDependSet originalTargets; + this->GetTargetSets(projectTargets, originalTargets, root, generators); + OrderedTargetDependSet sortedProjectTargets(projectTargets, ""); + std::vector<cmGeneratorTarget const*> defaultTargets; + for (cmGeneratorTarget const* t : sortedProjectTargets) { + /* save list of all targets in sorted order */ + this->ProjectTargets.push_back(t); + } + for (cmGeneratorTarget const* t : sortedProjectTargets) { + if (t->GetType() == cmStateEnums::INTERFACE_LIBRARY) { + continue; + } + if (!cmSystemTools::IsOn(t->GetProperty("EXCLUDE_FROM_ALL"))) { + defaultTargets.push_back(t); + } + } + std::vector<cmGeneratorTarget const*> build; + if (ComputeTargetBuildOrder(defaultTargets, build)) { + std::string message = "The inter-target dependency graph for project [" + + root->GetProjectName() + "] had a cycle.\n"; + cmSystemTools::Error(message); + } else { + // determine the targets for ALL target + std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); + rootBinaryDir += "/CMakeFiles"; + for (cmGeneratorTarget const* target : build) { + if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || + target->GetType() == cmStateEnums::MODULE_LIBRARY || + target->GetType() == cmStateEnums::SHARED_LIBRARY) { + continue; } + this->WriteProjectLine(fbld, target, root, rootBinaryDir); } } + fbld.Close(); } void cmGlobalGhsMultiGenerator::Generate() { + std::string fname; + // first do the superclass method this->cmGlobalGenerator::Generate(); @@ -370,11 +505,32 @@ void cmGlobalGhsMultiGenerator::Generate() for (auto& it : this->ProjectMap) { this->OutputTopLevelProject(it.second[0], it.second); } + + // create custom rule BOD file + fname = this->GetCMakeInstance()->GetHomeOutputDirectory() + + "/CMakeFiles/custom_rule.bod"; + cmGeneratedFileStream frule(fname); + frule.SetCopyIfDifferent(true); + this->WriteFileHeader(frule); + this->WriteCustomRuleBOD(frule); + frule.Close(); + + // create custom target BOD file + fname = this->GetCMakeInstance()->GetHomeOutputDirectory() + + "/CMakeFiles/custom_target.bod"; + cmGeneratedFileStream ftarget(fname); + ftarget.SetCopyIfDifferent(true); + this->WriteFileHeader(ftarget); + this->WriteCustomTargetBOD(ftarget); + ftarget.Close(); } void cmGlobalGhsMultiGenerator::OutputTopLevelProject( cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators) { + std::string fname; + std::string all_target; + if (generators.empty()) { return; } @@ -383,18 +539,21 @@ void cmGlobalGhsMultiGenerator::OutputTopLevelProject( * with target projects. This avoid the issue where the project has * the same name as the executable target. */ - std::string fname = root->GetCurrentBinaryDirectory(); + fname = root->GetCurrentBinaryDirectory(); fname += "/"; fname += root->GetProjectName(); fname += ".top"; fname += FILE_EXTENSION; - cmGeneratedFileStream fout(fname); - fout.SetCopyIfDifferent(true); + cmGeneratedFileStream top(fname); + top.SetCopyIfDifferent(true); + this->WriteTopLevelProject(top, root); - this->WriteTopLevelProject(fout, root, generators); + this->WriteAllTarget(root, generators, all_target); + this->WriteTargets(root); - fout.Close(); + this->WriteSubProjects(top, all_target); + top.Close(); } std::vector<cmGlobalGenerator::GeneratedMakeCommand> @@ -426,6 +585,9 @@ cmGlobalGhsMultiGenerator::GenerateBuildCommand( std::vector<std::string> files; cmSystemTools::Glob(projectDir, ".*\\.top\\.gpj", files); if (!files.empty()) { + /* if multiple top-projects are found in build directory + * then prefer projectName top-project. + */ auto p = std::find(files.begin(), files.end(), proj); if (p == files.end()) { proj = files.at(0); @@ -440,20 +602,24 @@ cmGlobalGhsMultiGenerator::GenerateBuildCommand( } else { for (const auto& tname : targetNames) { if (!tname.empty()) { - if (tname.compare(tname.size() - 4, 4, ".gpj") == 0) { - makeCommand.Add(tname); - } else { - makeCommand.Add(tname + ".gpj"); - } + makeCommand.Add(tname + ".tgt.gpj"); } } } + } else { + /* transform name to default build */; + std::string all = proj; + all.replace(all.end() - 7, all.end(), + std::string(this->GetAllTargetName()) + ".tgt.gpj"); + makeCommand.Add(all); } return { makeCommand }; } -void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout) +void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout, + cmLocalGenerator* root) { + fout << "macro PROJ_NAME=" << root->GetProjectName() << std::endl; char const* ghsGpjMacros = this->GetCMakeInstance()->GetCacheDefinition("GHS_GPJ_MACROS"); if (nullptr != ghsGpjMacros) { @@ -465,13 +631,14 @@ void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout) } } -void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives(std::ostream& fout) +void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives( + cmLocalGenerator* root, std::ostream& fout) { /* set primary target */ std::string tgt; const char* t = this->GetCMakeInstance()->GetCacheDefinition("GHS_PRIMARY_TARGET"); - if (t) { + if (t && *t != '\0') { tgt = t; this->GetCMakeInstance()->MarkCliAsUsed("GHS_PRIMARY_TARGET"); } else { @@ -486,16 +653,20 @@ void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives(std::ostream& fout) } fout << "primaryTarget=" << tgt << std::endl; + fout << "customization=" << root->GetBinaryDirectory() + << "/CMakeFiles/custom_rule.bod" << std::endl; + fout << "customization=" << root->GetBinaryDirectory() + << "/CMakeFiles/custom_target.bod" << std::endl; char const* const customization = this->GetCMakeInstance()->GetCacheDefinition("GHS_CUSTOMIZATION"); if (nullptr != customization && strlen(customization) > 0) { - fout << "customization=" << trimQuotes(customization) << std::endl; + fout << "customization=" << this->TrimQuotes(customization) << std::endl; this->GetCMakeInstance()->MarkCliAsUsed("GHS_CUSTOMIZATION"); } } -std::string cmGlobalGhsMultiGenerator::trimQuotes(std::string const& str) +std::string cmGlobalGhsMultiGenerator::TrimQuotes(std::string const& str) { std::string result; result.reserve(str.size()); @@ -528,3 +699,56 @@ cmGlobalGhsMultiGenerator::OrderedTargetDependSet::OrderedTargetDependSet( { this->insert(targets.begin(), targets.end()); } + +bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder( + cmGeneratorTarget const* tgt, std::vector<cmGeneratorTarget const*>& build) +{ + std::vector<cmGeneratorTarget const*> t{ tgt }; + return ComputeTargetBuildOrder(t, build); +} + +bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder( + std::vector<cmGeneratorTarget const*>& tgt, + std::vector<cmGeneratorTarget const*>& build) +{ + std::set<cmGeneratorTarget const*> temp; + std::set<cmGeneratorTarget const*> perm; + + for (auto ti : tgt) { + bool r = VisitTarget(temp, perm, build, ti); + if (r) { + return r; + } + } + return false; +} + +bool cmGlobalGhsMultiGenerator::VisitTarget( + std::set<cmGeneratorTarget const*>& temp, + std::set<cmGeneratorTarget const*>& perm, + std::vector<cmGeneratorTarget const*>& order, cmGeneratorTarget const* ti) +{ + /* check if permanent mark is set*/ + if (perm.find(ti) == perm.end()) { + /* set temporary mark; check if revisit*/ + if (temp.insert(ti).second) { + /* sort targets lexicographically to ensure that nodes are always visited + * in the same order */ + OrderedTargetDependSet sortedTargets(this->GetTargetDirectDepends(ti), + ""); + for (auto& di : sortedTargets) { + if (this->VisitTarget(temp, perm, order, di)) { + return true; + } + } + /* mark as complete; insert into beginning of list*/ + perm.insert(ti); + order.push_back(ti); + return false; + } + /* revisiting item - not a DAG */ + return true; + } + /* already complete */ + return false; +} diff --git a/Source/cmGlobalGhsMultiGenerator.h b/Source/cmGlobalGhsMultiGenerator.h index 1aeb1dc..98358c7 100644 --- a/Source/cmGlobalGhsMultiGenerator.h +++ b/Source/cmGlobalGhsMultiGenerator.h @@ -78,23 +78,7 @@ public: // Write the common disclaimer text at the top of each build file. void WriteFileHeader(std::ostream& fout); - // Target dependency sorting - class TargetSet : public std::set<cmGeneratorTarget const*> - { - }; - class TargetCompare - { - std::string First; - - public: - TargetCompare(std::string first) - : First(std::move(first)) - { - } - bool operator()(cmGeneratorTarget const* l, - cmGeneratorTarget const* r) const; - }; - class OrderedTargetDependSet; + const char* GetInstallTargetName() const override { return "install"; } protected: void Generate() override; @@ -111,18 +95,53 @@ private: /* top-level project */ void OutputTopLevelProject(cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators); - void WriteTopLevelProject(std::ostream& fout, cmLocalGenerator* root, - std::vector<cmLocalGenerator*>& generators); - void WriteMacros(std::ostream& fout); - void WriteHighLevelDirectives(std::ostream& fout); - void WriteSubProjects(std::ostream& fout, cmLocalGenerator* root, - std::vector<cmLocalGenerator*>& generators); - - std::string trimQuotes(std::string const& str); + void WriteTopLevelProject(std::ostream& fout, cmLocalGenerator* root); + void WriteMacros(std::ostream& fout, cmLocalGenerator* root); + void WriteHighLevelDirectives(cmLocalGenerator* root, std::ostream& fout); + void WriteSubProjects(std::ostream& fout, std::string& all_target); + void WriteTargets(cmLocalGenerator* root); + void WriteProjectLine(std::ostream& fout, cmGeneratorTarget const* target, + cmLocalGenerator* root, std::string& rootBinaryDir); + void WriteCustomRuleBOD(std::ostream& fout); + void WriteCustomTargetBOD(std::ostream& fout); + void WriteAllTarget(cmLocalGenerator* root, + std::vector<cmLocalGenerator*>& generators, + std::string& all_target); + + std::string TrimQuotes(std::string const& str); std::string OsDir; static const char* DEFAULT_BUILD_PROGRAM; static const char* DEFAULT_TOOLSET_ROOT; + + bool ComputeTargetBuildOrder(cmGeneratorTarget const* tgt, + std::vector<cmGeneratorTarget const*>& build); + bool ComputeTargetBuildOrder(std::vector<cmGeneratorTarget const*>& tgt, + std::vector<cmGeneratorTarget const*>& build); + bool VisitTarget(std::set<cmGeneratorTarget const*>& temp, + std::set<cmGeneratorTarget const*>& perm, + std::vector<cmGeneratorTarget const*>& order, + cmGeneratorTarget const* ti); + + std::vector<cmGeneratorTarget const*> ProjectTargets; + + // Target sorting + class TargetSet : public std::set<cmGeneratorTarget const*> + { + }; + class TargetCompare + { + std::string First; + + public: + TargetCompare(std::string first) + : First(std::move(first)) + { + } + bool operator()(cmGeneratorTarget const* l, + cmGeneratorTarget const* r) const; + }; + class OrderedTargetDependSet; }; class cmGlobalGhsMultiGenerator::OrderedTargetDependSet diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index 9736b41..dba4bbb 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -666,8 +666,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args) // generators for them. bool createInstallGeneratorsForTargetFileSets = true; - if (target.IsFrameworkOnApple() || - target.GetType() == cmStateEnums::INTERFACE_LIBRARY) { + if (target.IsFrameworkOnApple()) { createInstallGeneratorsForTargetFileSets = false; } diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx index f115016..6fbea82 100644 --- a/Source/cmQtAutoGenerator.cxx +++ b/Source/cmQtAutoGenerator.cxx @@ -14,12 +14,6 @@ #include "cmSystemTools.h" #include "cmake.h" -#include <algorithm> -#include <sstream> -#include <utility> - -// -- Class methods - cmQtAutoGenerator::Logger::Logger() { // Initialize logger @@ -431,232 +425,6 @@ bool cmQtAutoGenerator::FileSystem::MakeParentDirectory( return cmQtAutoGenerator::MakeParentDirectory(filename); } -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<PipeT*>(handle->data); - pipe.Buffer_.resize(suggestedSize); - buf->base = pipe.Buffer_.data(); - 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<PipeT*>(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<std::string> const& command, std::string const& workingDirectory) -{ - Setup_.WorkingDirectory = workingDirectory; - Setup_.Command = command; - Setup_.Result = result; - Setup_.MergedOutput = mergedOutput; -} - -static std::string getUVError(const char* prefixString, int uvErrorCode) -{ - std::ostringstream ost; - ost << prefixString << ": " << uv_strerror(uvErrorCode); - return ost.str(); -} - -bool cmQtAutoGenerator::ReadOnlyProcessT::start( - uv_loop_t* uv_loop, std::function<void()>&& 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_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); - UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream(); - // stderr - UVOptionsStdIO_[2].flags = - static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); - UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream(); - - // -- Setup process options - std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0); - UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit; - UVOptions_.file = CommandPtr_[0]; - UVOptions_.args = const_cast<char**>(CommandPtr_.data()); - UVOptions_.cwd = Setup_.WorkingDirectory.c_str(); - UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE; - UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size()); - UVOptions_.stdio = UVOptionsStdIO_.data(); - - // -- Spawn process - int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this); - if (uvErrorCode != 0) { - Result()->ErrorMessage = - getUVError("libuv process spawn failed ", uvErrorCode); - } - } - // -- 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<ReadOnlyProcessT*>(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() = default; cmQtAutoGenerator::~cmQtAutoGenerator() = default; diff --git a/Source/cmQtAutoGenerator.h b/Source/cmQtAutoGenerator.h index 479d357..437fa20 100644 --- a/Source/cmQtAutoGenerator.h +++ b/Source/cmQtAutoGenerator.h @@ -7,14 +7,8 @@ #include "cmFilePathChecksum.h" #include "cmQtAutoGen.h" -#include "cmUVHandlePtr.h" -#include "cm_uv.h" -#include <array> -#include <functional> #include <mutex> -#include <stddef.h> -#include <stdint.h> #include <string> #include <vector> @@ -137,102 +131,6 @@ public: cmFilePathChecksum FilePathChecksum_; }; - /// @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_stream_t*>(uv_pipe()); - } - uv_handle_t* uv_handle() - { - return reinterpret_cast<uv_handle_t*>(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<char> Buffer_; - cm::uv_pipe_ptr UVPipe_; - }; - - /// @brief Process settings - struct SetupT - { - std::string WorkingDirectory; - std::vector<std::string> Command; - ProcessResultT* Result = nullptr; - bool MergedOutput = false; - }; - - // -- 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<std::string> const& command, - std::string const& workingDirectory = std::string()); - bool start(uv_loop_t* uv_loop, std::function<void()>&& 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<void()> FinishedCallback_; - std::vector<const char*> CommandPtr_; - std::array<uv_stdio_container_t, 3> UVOptionsStdIO_; - uv_process_options_t UVOptions_; - cm::uv_process_ptr UVProcess_; - PipeT UVPipeOut_; - PipeT UVPipeErr_; - }; - public: // -- Constructors cmQtAutoGenerator(); diff --git a/Source/cmQtAutoGeneratorMocUic.cxx b/Source/cmQtAutoMocUic.cxx index ec1a1aa..75c5d8a 100644 --- a/Source/cmQtAutoGeneratorMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -1,10 +1,10 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmQtAutoGeneratorMocUic.h" +#include "cmQtAutoMocUic.h" #include <algorithm> #include <array> -#include <cstddef> +#include <deque> #include <list> #include <memory> #include <set> @@ -24,7 +24,7 @@ // -- Class methods -std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( +std::string cmQtAutoMocUic::BaseSettingsT::AbsoluteBuildPath( std::string const& relativePath) const { return FileSys->CollapseFullPath(relativePath, AutogenBuildDir); @@ -35,7 +35,7 @@ std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath( * appending different header extensions * @return True on success */ -bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader( +bool cmQtAutoMocUic::BaseSettingsT::FindHeader( std::string& header, std::string const& testBasePath) const { for (std::string const& ext : HeaderExtensions) { @@ -50,8 +50,7 @@ bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader( return false; } -bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped( - std::string const& fileName) const +bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } @@ -60,7 +59,7 @@ bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped( * @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 cmQtAutoMocUic::MocSettingsT::FindMacro( std::string const& content) const { for (KeyExpT const& filter : MacroFilters) { @@ -77,7 +76,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro( return std::string(); } -std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const +std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const { std::string res; const auto itB = MacroFilters.cbegin(); @@ -99,7 +98,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const return res; } -std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile( +std::string cmQtAutoMocUic::MocSettingsT::FindIncludedFile( std::string const& sourcePath, std::string const& includeString) const { // Search in vicinity of the source @@ -123,7 +122,7 @@ std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile( return std::string(); } -void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies( +void cmQtAutoMocUic::MocSettingsT::FindDependencies( std::string const& content, std::set<std::string>& depends) const { if (!DependFilters.empty() && !content.empty()) { @@ -147,17 +146,124 @@ void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies( } } -bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped( - std::string const& fileName) const +bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const { return (!Enabled || (SkipList.find(fileName) != SkipList.end())); } -void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk) +void cmQtAutoMocUic::JobT::LogError(GenT genType, + std::string const& message) const +{ + Gen()->AbortError(); + Gen()->Log().Error(genType, message); +} + +void cmQtAutoMocUic::JobT::LogFileError(GenT genType, + std::string const& filename, + std::string const& message) const +{ + Gen()->AbortError(); + Gen()->Log().ErrorFile(genType, filename, message); +} + +void cmQtAutoMocUic::JobT::LogCommandError( + GenT genType, std::string const& message, + std::vector<std::string> const& command, std::string const& output) const +{ + Gen()->AbortError(); + Gen()->Log().ErrorCommand(genType, message, command, output); +} + +bool cmQtAutoMocUic::JobT::RunProcess(GenT genType, + cmWorkerPool::ProcessResultT& result, + std::vector<std::string> const& command) +{ + // Log command + if (Log().Verbose()) { + std::string msg = "Running command:\n"; + msg += QuotedCommand(command); + msg += '\n'; + Log().Info(genType, msg); + } + return cmWorkerPool::JobT::RunProcess(result, command, + Gen()->Base().AutogenBuildDir); +} + +void cmQtAutoMocUic::JobMocPredefsT::Process() +{ + // (Re)generate moc_predefs.h on demand + bool generate(false); + bool fileExists(FileSys().FileExists(Gen()->Moc().PredefsFileAbs)); + if (!fileExists) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(Gen()->Moc().PredefsFileRel); + reason += " because it doesn't exist"; + Log().Info(GenT::MOC, reason); + } + generate = true; + } else if (Gen()->Moc().SettingsChanged) { + if (Log().Verbose()) { + std::string reason = "Generating "; + reason += Quoted(Gen()->Moc().PredefsFileRel); + reason += " because the settings changed."; + Log().Info(GenT::MOC, reason); + } + generate = true; + } + if (generate) { + cmWorkerPool::ProcessResultT result; + { + // Compose command + std::vector<std::string> cmd = Gen()->Moc().PredefsCmd; + // Add includes + cmd.insert(cmd.end(), Gen()->Moc().Includes.begin(), + Gen()->Moc().Includes.end()); + // Add definitions + for (std::string const& def : Gen()->Moc().Definitions) { + cmd.push_back("-D" + def); + } + // Execute command + if (!RunProcess(GenT::MOC, result, cmd)) { + std::string emsg = "The content generation command for "; + emsg += Quoted(Gen()->Moc().PredefsFileRel); + emsg += " failed.\n"; + emsg += result.ErrorMessage; + LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); + } + } + + // (Re)write predefs file only on demand + if (!result.error()) { + if (!fileExists || + FileSys().FileDiffers(Gen()->Moc().PredefsFileAbs, result.StdOut)) { + if (FileSys().FileWrite(Gen()->Moc().PredefsFileAbs, result.StdOut)) { + // Success + } else { + std::string emsg = "Writing "; + emsg += Quoted(Gen()->Moc().PredefsFileRel); + emsg += " failed."; + LogFileError(GenT::MOC, Gen()->Moc().PredefsFileAbs, emsg); + } + } else { + // Touch to update the time stamp + if (Log().Verbose()) { + std::string msg = "Touching "; + msg += Quoted(Gen()->Moc().PredefsFileRel); + msg += "."; + Log().Info(GenT::MOC, msg); + } + FileSys().Touch(Gen()->Moc().PredefsFileAbs); + } + } + } +} + +void cmQtAutoMocUic::JobParseT::Process() { if (AutoMoc && Header) { // Don't parse header for moc if the file is included by a source already - if (wrk.Gen().ParallelMocIncluded(FileName)) { + if (Gen()->ParallelMocIncluded(FileName)) { AutoMoc = false; } } @@ -165,35 +271,32 @@ void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk) if (AutoMoc || AutoUic) { std::string error; MetaT meta; - if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) { + if (FileSys().FileRead(meta.Content, FileName, &error)) { if (!meta.Content.empty()) { - meta.FileDir = wrk.FileSys().SubDirPrefix(FileName); - meta.FileBase = - wrk.FileSys().GetFilenameWithoutLastExtension(FileName); + meta.FileDir = FileSys().SubDirPrefix(FileName); + meta.FileBase = FileSys().GetFilenameWithoutLastExtension(FileName); bool success = true; if (AutoMoc) { if (Header) { - success = ParseMocHeader(wrk, meta); + success = ParseMocHeader(meta); } else { - success = ParseMocSource(wrk, meta); + success = ParseMocSource(meta); } } if (AutoUic && success) { - ParseUic(wrk, meta); + ParseUic(meta); } } else { - wrk.LogFileWarning(GenT::GEN, FileName, "The source file is empty"); + Log().WarningFile(GenT::GEN, FileName, "The source file is empty"); } } else { - wrk.LogFileError(GenT::GEN, FileName, - "Could not read the file: " + error); + LogFileError(GenT::GEN, FileName, "Could not read the file: " + error); } } } -bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, - MetaT const& meta) +bool cmQtAutoMocUic::JobParseT::ParseMocSource(MetaT const& meta) { struct JobPre { @@ -211,7 +314,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, }; // Check if this source file contains a relevant macro - std::string const ownMacro = wrk.Moc().FindMacro(meta.Content); + std::string const ownMacro = Gen()->Moc().FindMacro(meta.Content); // Extract moc includes from file std::deque<MocInclude> mocIncsUsc; @@ -220,11 +323,11 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, 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)) { + while (Gen()->Moc().RegExpInclude.find(contentChars, match)) { std::string incString = match.match(2); - std::string incDir(wrk.FileSys().SubDirPrefix(incString)); + std::string incDir(FileSys().SubDirPrefix(incString)); std::string incBase = - wrk.FileSys().GetFilenameWithoutLastExtension(incString); + FileSys().GetFilenameWithoutLastExtension(incString); if (cmHasLiteralPrefix(incBase, "moc_")) { // moc_<BASE>.cxx // Remove the moc_ part from the base name @@ -253,10 +356,10 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, // Process moc_<BASE>.cxx includes for (const MocInclude& mocInc : mocIncsUsc) { std::string const header = - MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base); + MocFindIncludedHeader(meta.FileDir, mocInc.Dir + mocInc.Base); if (!header.empty()) { // Check if header is skipped - if (wrk.Moc().skipped(header)) { + if (Gen()->Moc().skipped(header)) { continue; } // Register moc job @@ -271,9 +374,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, std::string emsg = "The file includes the moc file "; emsg += Quoted(mocInc.Inc); emsg += ", but the header "; - emsg += Quoted(MocStringHeaders(wrk, mocInc.Base)); + emsg += Quoted(MocStringHeaders(mocInc.Base)); emsg += " could not be found."; - wrk.LogFileError(GenT::MOC, FileName, emsg); + LogFileError(GenT::MOC, FileName, emsg); } return false; } @@ -282,7 +385,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, // Process <BASE>.moc includes for (const MocInclude& mocInc : mocIncsDot) { const bool ownMoc = (mocInc.Base == meta.FileBase); - if (wrk.Moc().RelaxedMode) { + if (Gen()->Moc().RelaxedMode) { // Relaxed mode if (!ownMacro.empty() && ownMoc) { // Add self @@ -292,10 +395,10 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, // 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); + MocFindIncludedHeader(meta.FileDir, mocInc.Dir + mocInc.Base); if (!header.empty()) { // Check if header is skipped - if (wrk.Moc().skipped(header)) { + if (Gen()->Moc().skipped(header)) { continue; } // Register moc job @@ -305,14 +408,14 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, std::string emsg = "The file includes the moc file "; emsg += Quoted(mocInc.Inc); emsg += ", but does not contain a "; - emsg += wrk.Moc().MacrosString(); + emsg += Gen()->Moc().MacrosString(); emsg += " macro.\nRunning moc on\n "; emsg += Quoted(header); emsg += "!\nBetter include "; emsg += Quoted("moc_" + mocInc.Base + ".cpp"); emsg += " for a compatibility with strict mode.\n" "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - wrk.LogFileWarning(GenT::MOC, FileName, emsg); + Log().WarningFile(GenT::MOC, FileName, emsg); } else { std::string emsg = "The file includes the moc file "; emsg += Quoted(mocInc.Inc); @@ -324,7 +427,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, emsg += Quoted("moc_" + mocInc.Base + ".cpp"); emsg += " for compatibility with strict mode.\n" "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n"; - wrk.LogFileWarning(GenT::MOC, FileName, emsg); + Log().WarningFile(GenT::MOC, FileName, emsg); } } } else { @@ -334,9 +437,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, 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 += Quoted(MocStringHeaders(mocInc.Base)); emsg += " could not be found."; - wrk.LogFileError(GenT::MOC, FileName, emsg); + LogFileError(GenT::MOC, FileName, emsg); } return false; } @@ -352,9 +455,9 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, std::string emsg = "The file includes the moc file "; emsg += Quoted(mocInc.Inc); emsg += ", but does not contain a "; - emsg += wrk.Moc().MacrosString(); + emsg += Gen()->Moc().MacrosString(); emsg += " macro."; - wrk.LogFileWarning(GenT::MOC, FileName, emsg); + Log().WarningFile(GenT::MOC, FileName, emsg); } } else { // Don't allow <BASE>.moc include other than self in strict mode @@ -365,7 +468,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, "source file.\nThis is not supported. Include "; emsg += Quoted(meta.FileBase + ".moc"); emsg += " to run moc on this source file."; - wrk.LogFileError(GenT::MOC, FileName, emsg); + LogFileError(GenT::MOC, FileName, emsg); } return false; } @@ -379,7 +482,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, // 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) { + if (Gen()->Moc().RelaxedMode && ownMocUscIncluded) { JobPre uscJobPre; // Remove underscore job request { @@ -408,7 +511,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, emsg += Quoted(meta.FileBase + ".moc"); emsg += " for compatibility with strict mode.\n" "(CMAKE_AUTOMOC_RELAXED_MODE warning)"; - wrk.LogFileWarning(GenT::MOC, FileName, emsg); + Log().WarningFile(GenT::MOC, FileName, emsg); } // Add own source job jobs.emplace_back( @@ -423,7 +526,7 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, emsg += "!\nConsider to\n - add #include \""; emsg += meta.FileBase; emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file"; - wrk.LogFileError(GenT::MOC, FileName, emsg); + LogFileError(GenT::MOC, FileName, emsg); } return false; } @@ -431,76 +534,80 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk, // Convert pre jobs to actual jobs for (JobPre& jobPre : jobs) { - JobHandleT jobHandle = cm::make_unique<JobMocT>( + cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>( std::move(jobPre.SourceFile), FileName, std::move(jobPre.IncludeString)); if (jobPre.self) { // Read dependencies from this source - static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content); + JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle); + Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends); + jobMoc.DependsValid = true; } - if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) { + if (!Gen()->ParallelJobPushMoc(std::move(jobHandle))) { return false; } } return true; } -bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk, - MetaT const& meta) +bool cmQtAutoMocUic::JobParseT::ParseMocHeader(MetaT const& meta) { bool success = true; - std::string const macroName = wrk.Moc().FindMacro(meta.Content); + std::string const macroName = Gen()->Moc().FindMacro(meta.Content); if (!macroName.empty()) { - JobHandleT jobHandle = cm::make_unique<JobMocT>( + cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobMocT>( std::string(FileName), std::string(), std::string()); // Read dependencies from this source - static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content); - success = wrk.Gen().ParallelJobPushMoc(jobHandle); + { + JobMocT& jobMoc = static_cast<JobMocT&>(*jobHandle); + Gen()->Moc().FindDependencies(meta.Content, jobMoc.Depends); + jobMoc.DependsValid = true; + } + success = Gen()->ParallelJobPushMoc(std::move(jobHandle)); } return success; } -std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders( - WorkerT& wrk, std::string const& fileBase) const +std::string cmQtAutoMocUic::JobParseT::MocStringHeaders( + std::string const& fileBase) const { std::string res = fileBase; res += ".{"; - res += cmJoin(wrk.Base().HeaderExtensions, ","); + res += cmJoin(Gen()->Base().HeaderExtensions, ","); res += "}"; return res; } -std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader( - WorkerT& wrk, std::string const& includerDir, std::string const& includeBase) +std::string cmQtAutoMocUic::JobParseT::MocFindIncludedHeader( + std::string const& includerDir, std::string const& includeBase) { std::string header; // Search in vicinity of the source - if (!wrk.Base().FindHeader(header, includerDir + includeBase)) { + if (!Gen()->Base().FindHeader(header, includerDir + includeBase)) { // Search in include directories - for (std::string const& path : wrk.Moc().IncludePaths) { + for (std::string const& path : Gen()->Moc().IncludePaths) { std::string fullPath = path; fullPath.push_back('/'); fullPath += includeBase; - if (wrk.Base().FindHeader(header, fullPath)) { + if (Gen()->Base().FindHeader(header, fullPath)) { break; } } } // Sanitize if (!header.empty()) { - header = wrk.FileSys().GetRealPath(header); + header = FileSys().GetRealPath(header); } return header; } -bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk, - MetaT const& meta) +bool cmQtAutoMocUic::JobParseT::ParseUic(MetaT const& meta) { bool success = true; if (meta.Content.find("ui_") != std::string::npos) { const char* contentChars = meta.Content.c_str(); cmsys::RegularExpressionMatch match; - while (wrk.Uic().RegExpInclude.find(contentChars, match)) { - if (!ParseUicInclude(wrk, meta, match.match(2))) { + while (Gen()->Uic().RegExpInclude.find(contentChars, match)) { + if (!ParseUicInclude(meta, match.match(2))) { success = false; break; } @@ -510,16 +617,16 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk, return success; } -bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude( - WorkerT& wrk, MetaT const& meta, std::string&& includeString) +bool cmQtAutoMocUic::JobParseT::ParseUicInclude(MetaT const& meta, + std::string&& includeString) { bool success = false; - std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString); + std::string uiInputFile = UicFindIncludedFile(meta, includeString); if (!uiInputFile.empty()) { - if (!wrk.Uic().skipped(uiInputFile)) { - JobHandleT jobHandle = cm::make_unique<JobUicT>( + if (!Gen()->Uic().skipped(uiInputFile)) { + cmWorkerPool::JobHandleT jobHandle = cm::make_unique<JobUicT>( std::move(uiInputFile), FileName, std::move(includeString)); - success = wrk.Gen().ParallelJobPushUic(jobHandle); + success = Gen()->ParallelJobPushUic(std::move(jobHandle)); } else { // A skipped file is successful success = true; @@ -528,17 +635,17 @@ bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude( return success; } -std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile( - WorkerT& wrk, MetaT const& meta, std::string const& includeString) +std::string cmQtAutoMocUic::JobParseT::UicFindIncludedFile( + MetaT const& meta, std::string const& includeString) { std::string res; std::string searchFile = - wrk.FileSys().GetFilenameWithoutLastExtension(includeString).substr(3); + FileSys().GetFilenameWithoutLastExtension(includeString).substr(3); searchFile += ".ui"; // Collect search paths list std::deque<std::string> testFiles; { - std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString); + std::string const searchPath = FileSys().SubDirPrefix(includeString); std::string searchFileFull; if (!searchPath.empty()) { @@ -554,12 +661,12 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile( } } // AUTOUIC search paths - if (!wrk.Uic().SearchPaths.empty()) { - for (std::string const& sPath : wrk.Uic().SearchPaths) { + if (!Gen()->Uic().SearchPaths.empty()) { + for (std::string const& sPath : Gen()->Uic().SearchPaths) { testFiles.push_back((sPath + "/").append(searchFile)); } if (!searchPath.empty()) { - for (std::string const& sPath : wrk.Uic().SearchPaths) { + for (std::string const& sPath : Gen()->Uic().SearchPaths) { testFiles.push_back((sPath + "/").append(searchFileFull)); } } @@ -568,8 +675,8 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile( // Search for the .ui file! for (std::string const& testFile : testFiles) { - if (wrk.FileSys().FileExists(testFile)) { - res = wrk.FileSys().GetRealPath(testFile); + if (FileSys().FileExists(testFile)) { + res = FileSys().GetRealPath(testFile); break; } } @@ -584,162 +691,140 @@ std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile( emsg += Quoted(testFile); emsg += "\n"; } - wrk.LogFileError(GenT::UIC, FileName, emsg); + LogFileError(GenT::UIC, FileName, emsg); } return res; } -void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk) +void cmQtAutoMocUic::JobPostParseT::Process() { - // (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(GenT::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(GenT::MOC, reason); - } - generate = true; + if (Gen()->Moc().Enabled) { + // Add mocs compilations fence job + Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>(); } - if (generate) { - ProcessResultT result; - { - // Compose command - std::vector<std::string> cmd = wrk.Moc().PredefsCmd; - // Add includes - cmd.insert(cmd.end(), wrk.Moc().Includes.begin(), - wrk.Moc().Includes.end()); - // Add definitions - for (std::string const& def : wrk.Moc().Definitions) { - cmd.push_back("-D" + def); - } - // Execute command - if (!wrk.RunProcess(GenT::MOC, result, cmd)) { - std::string emsg = "The content generation command for "; - emsg += Quoted(wrk.Moc().PredefsFileRel); - emsg += " failed.\n"; - emsg += result.ErrorMessage; - wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); - } + // Add finish job + Gen()->WorkerPool().EmplaceJob<JobFinishT>(); +} + +void cmQtAutoMocUic::JobMocsCompilationT::Process() +{ + // Compose mocs compilation file content + std::string content = + "// This file is autogenerated. Changes will be overwritten.\n"; + if (Gen()->MocAutoFiles().empty()) { + // Placeholder content + content += "// No files found that require moc or the moc files are " + "included\n"; + content += "enum some_compilers { need_more_than_nothing };\n"; + } else { + // Valid content + char const sbeg = Gen()->Base().MultiConfig ? '<' : '"'; + char const send = Gen()->Base().MultiConfig ? '>' : '"'; + for (std::string const& mocfile : Gen()->MocAutoFiles()) { + content += "#include "; + content += sbeg; + content += mocfile; + content += send; + content += '\n'; } + } - // (Re)write predefs file only on demand - if (!result.error()) { - if (!fileExists || - wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) { - std::string error; - if (wrk.FileSys().FileWrite(wrk.Moc().PredefsFileAbs, result.StdOut, - &error)) { - // Success - } else { - std::string emsg = "Writing "; - emsg += Quoted(wrk.Moc().PredefsFileRel); - emsg += " failed. "; - emsg += error; - wrk.LogFileError(GenT::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(GenT::MOC, msg); - } - wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs); - } + std::string const& compAbs = Gen()->Moc().CompFileAbs; + if (FileSys().FileDiffers(compAbs, content)) { + // Actually write mocs compilation file + if (Log().Verbose()) { + Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs); + } + if (!FileSys().FileWrite(compAbs, content)) { + LogFileError(GenT::MOC, compAbs, + "mocs compilation file writing failed."); + } + } else if (Gen()->MocAutoFileUpdated()) { + // Only touch mocs compilation file + if (Log().Verbose()) { + Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs); } + FileSys().Touch(compAbs); } } -void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies( - WorkerT& wrk, std::string const& content) +void cmQtAutoMocUic::JobMocT::FindDependencies(std::string const& content) { - wrk.Moc().FindDependencies(content, Depends); + Gen()->Moc().FindDependencies(content, Depends); DependsValid = true; } -void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk) +void cmQtAutoMocUic::JobMocT::Process() { // Compute build file name if (!IncludeString.empty()) { - BuildFile = wrk.Base().AutogenIncludeDir; + BuildFile = Gen()->Base().AutogenIncludeDir; BuildFile += '/'; BuildFile += IncludeString; } else { // Relative build path - std::string relPath = wrk.FileSys().GetFilePathChecksum(SourceFile); + std::string relPath = FileSys().GetFilePathChecksum(SourceFile); relPath += "/moc_"; - relPath += wrk.FileSys().GetFilenameWithoutLastExtension(SourceFile); + relPath += FileSys().GetFilenameWithoutLastExtension(SourceFile); // Register relative file path with duplication check - relPath = wrk.Gen().ParallelMocAutoRegister(relPath); + relPath = Gen()->ParallelMocAutoRegister(relPath); // Absolute build path - if (wrk.Base().MultiConfig) { - BuildFile = wrk.Base().AutogenIncludeDir; + if (Gen()->Base().MultiConfig) { + BuildFile = Gen()->Base().AutogenIncludeDir; BuildFile += '/'; BuildFile += relPath; } else { - BuildFile = wrk.Base().AbsoluteBuildPath(relPath); + BuildFile = Gen()->Base().AbsoluteBuildPath(relPath); } } - if (UpdateRequired(wrk)) { - GenerateMoc(wrk); + if (UpdateRequired()) { + GenerateMoc(); } } -bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) +bool cmQtAutoMocUic::JobMocT::UpdateRequired() { - bool const verbose = wrk.Gen().Log().Verbose(); + bool const verbose = Log().Verbose(); // Test if the build file exists - if (!wrk.FileSys().FileExists(BuildFile)) { + if (!FileSys().FileExists(BuildFile)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from its source file "; reason += Quoted(SourceFile); reason += " because it doesn't exist"; - wrk.LogInfo(GenT::MOC, reason); + Log().Info(GenT::MOC, reason); } return true; } // Test if any setting changed - if (wrk.Moc().SettingsChanged) { + if (Gen()->Moc().SettingsChanged) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from "; reason += Quoted(SourceFile); reason += " because the MOC settings changed"; - wrk.LogInfo(GenT::MOC, reason); + Log().Info(GenT::MOC, reason); } return true; } // Test if the moc_predefs file is newer - if (!wrk.Moc().PredefsFileAbs.empty()) { + if (!Gen()->Moc().PredefsFileAbs.empty()) { bool isOlder = false; { std::string error; - isOlder = wrk.FileSys().FileIsOlderThan( - BuildFile, wrk.Moc().PredefsFileAbs, &error); + isOlder = FileSys().FileIsOlderThan(BuildFile, + Gen()->Moc().PredefsFileAbs, &error); if (!isOlder && !error.empty()) { - wrk.LogError(GenT::MOC, error); + LogError(GenT::MOC, error); return false; } } @@ -748,8 +833,8 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " because it's older than: "; - reason += Quoted(wrk.Moc().PredefsFileAbs); - wrk.LogInfo(GenT::MOC, reason); + reason += Quoted(Gen()->Moc().PredefsFileAbs); + Log().Info(GenT::MOC, reason); } return true; } @@ -760,9 +845,9 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) bool isOlder = false; { std::string error; - isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); + isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); if (!isOlder && !error.empty()) { - wrk.LogError(GenT::MOC, error); + LogError(GenT::MOC, error); return false; } } @@ -772,7 +857,7 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) reason += Quoted(BuildFile); reason += " because it's older than its source file "; reason += Quoted(SourceFile); - wrk.LogInfo(GenT::MOC, reason); + Log().Info(GenT::MOC, reason); } return true; } @@ -785,7 +870,7 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) std::string content; { std::string error; - if (!wrk.FileSys().FileRead(content, SourceFile, &error)) { + if (!FileSys().FileRead(content, SourceFile, &error)) { std::string emsg = "Could not read file\n "; emsg += Quoted(SourceFile); emsg += "\nrequired by moc include "; @@ -794,20 +879,20 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) emsg += Quoted(IncluderFile); emsg += ".\n"; emsg += error; - wrk.LogError(GenT::MOC, emsg); + LogError(GenT::MOC, emsg); return false; } } - FindDependencies(wrk, content); + FindDependencies(content); } // Check dependency timestamps std::string error; - std::string sourceDir = wrk.FileSys().SubDirPrefix(SourceFile); + std::string sourceDir = FileSys().SubDirPrefix(SourceFile); for (std::string const& depFileRel : Depends) { std::string depFileAbs = - wrk.Moc().FindIncludedFile(sourceDir, depFileRel); + Gen()->Moc().FindIncludedFile(sourceDir, depFileRel); if (!depFileAbs.empty()) { - if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) { + if (FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); @@ -815,18 +900,18 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) reason += Quoted(SourceFile); reason += " because it is older than it's dependency file "; reason += Quoted(depFileAbs); - wrk.LogInfo(GenT::MOC, reason); + Log().Info(GenT::MOC, reason); } return true; } if (!error.empty()) { - wrk.LogError(GenT::MOC, error); + LogError(GenT::MOC, error); return false; } } else { std::string message = "Could not find dependency file "; message += Quoted(depFileRel); - wrk.LogFileWarning(GenT::MOC, SourceFile, message); + Log().WarningFile(GenT::MOC, SourceFile, message); } } } @@ -834,41 +919,40 @@ bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk) return false; } -void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk) +void cmQtAutoMocUic::JobMocT::GenerateMoc() { // Make sure the parent directory exists - if (!wrk.FileSys().MakeParentDirectory(BuildFile)) { - wrk.LogFileError(GenT::MOC, BuildFile, - "Could not create parent directory."); + if (!FileSys().MakeParentDirectory(BuildFile)) { + LogFileError(GenT::MOC, BuildFile, "Could not create parent directory."); return; } { // Compose moc command std::vector<std::string> cmd; - cmd.push_back(wrk.Moc().Executable); + cmd.push_back(Gen()->Moc().Executable); // Add options - cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(), - wrk.Moc().AllOptions.end()); + cmd.insert(cmd.end(), Gen()->Moc().AllOptions.begin(), + Gen()->Moc().AllOptions.end()); // Add predefs include - if (!wrk.Moc().PredefsFileAbs.empty()) { + if (!Gen()->Moc().PredefsFileAbs.empty()) { cmd.emplace_back("--include"); - cmd.push_back(wrk.Moc().PredefsFileAbs); + cmd.push_back(Gen()->Moc().PredefsFileAbs); } cmd.emplace_back("-o"); cmd.push_back(BuildFile); cmd.push_back(SourceFile); // Execute moc command - ProcessResultT result; - if (wrk.RunProcess(GenT::MOC, result, cmd)) { + cmWorkerPool::ProcessResultT result; + if (RunProcess(GenT::MOC, result, cmd)) { // Moc command success // Print moc output if (!result.StdOut.empty()) { - wrk.LogInfo(GenT::MOC, result.StdOut); + Log().Info(GenT::MOC, result.StdOut); } // Notify the generator that a not included file changed (on demand) if (IncludeString.empty()) { - wrk.Gen().ParallelMocAutoUpdated(); + Gen()->ParallelMocAutoUpdated(); } } else { // Moc command failed @@ -879,51 +963,51 @@ void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk) emsg += Quoted(BuildFile); emsg += ".\n"; emsg += result.ErrorMessage; - wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); + LogCommandError(GenT::MOC, emsg, cmd, result.StdOut); } - wrk.FileSys().FileRemove(BuildFile); + FileSys().FileRemove(BuildFile); } } } -void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk) +void cmQtAutoMocUic::JobUicT::Process() { // Compute build file name - BuildFile = wrk.Base().AutogenIncludeDir; + BuildFile = Gen()->Base().AutogenIncludeDir; BuildFile += '/'; BuildFile += IncludeString; - if (UpdateRequired(wrk)) { - GenerateUic(wrk); + if (UpdateRequired()) { + GenerateUic(); } } -bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) +bool cmQtAutoMocUic::JobUicT::UpdateRequired() { - bool const verbose = wrk.Gen().Log().Verbose(); + bool const verbose = Log().Verbose(); // Test if the build file exists - if (!wrk.FileSys().FileExists(BuildFile)) { + if (!FileSys().FileExists(BuildFile)) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from its source file "; reason += Quoted(SourceFile); reason += " because it doesn't exist"; - wrk.LogInfo(GenT::UIC, reason); + Log().Info(GenT::UIC, reason); } return true; } // Test if the uic settings changed - if (wrk.Uic().SettingsChanged) { + if (Gen()->Uic().SettingsChanged) { if (verbose) { std::string reason = "Generating "; reason += Quoted(BuildFile); reason += " from "; reason += Quoted(SourceFile); reason += " because the UIC settings changed"; - wrk.LogInfo(GenT::UIC, reason); + Log().Info(GenT::UIC, reason); } return true; } @@ -933,9 +1017,9 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) bool isOlder = false; { std::string error; - isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); + isOlder = FileSys().FileIsOlderThan(BuildFile, SourceFile, &error); if (!isOlder && !error.empty()) { - wrk.LogError(GenT::UIC, error); + LogError(GenT::UIC, error); return false; } } @@ -945,7 +1029,7 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) reason += Quoted(BuildFile); reason += " because it's older than its source file "; reason += Quoted(SourceFile); - wrk.LogInfo(GenT::UIC, reason); + Log().Info(GenT::UIC, reason); } return true; } @@ -954,37 +1038,36 @@ bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk) return false; } -void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk) +void cmQtAutoMocUic::JobUicT::GenerateUic() { // Make sure the parent directory exists - if (!wrk.FileSys().MakeParentDirectory(BuildFile)) { - wrk.LogFileError(GenT::UIC, BuildFile, - "Could not create parent directory."); + if (!FileSys().MakeParentDirectory(BuildFile)) { + LogFileError(GenT::UIC, BuildFile, "Could not create parent directory."); return; } { // Compose uic command std::vector<std::string> cmd; - cmd.push_back(wrk.Uic().Executable); + cmd.push_back(Gen()->Uic().Executable); { - std::vector<std::string> allOpts = wrk.Uic().TargetOptions; - auto optionIt = wrk.Uic().Options.find(SourceFile); - if (optionIt != wrk.Uic().Options.end()) { + std::vector<std::string> allOpts = Gen()->Uic().TargetOptions; + auto optionIt = Gen()->Uic().Options.find(SourceFile); + if (optionIt != Gen()->Uic().Options.end()) { UicMergeOptions(allOpts, optionIt->second, - (wrk.Base().QtVersionMajor == 5)); + (Gen()->Base().QtVersionMajor == 5)); } cmd.insert(cmd.end(), allOpts.begin(), allOpts.end()); } cmd.emplace_back("-o"); - cmd.push_back(BuildFile); - cmd.push_back(SourceFile); + cmd.emplace_back(BuildFile); + cmd.emplace_back(SourceFile); - ProcessResultT result; - if (wrk.RunProcess(GenT::UIC, result, cmd)) { + cmWorkerPool::ProcessResultT result; + if (RunProcess(GenT::UIC, result, cmd)) { // Uic command success // Print uic output if (!result.StdOut.empty()) { - wrk.LogInfo(GenT::UIC, result.StdOut); + Log().Info(GenT::UIC, result.StdOut); } } else { // Uic command failed @@ -997,147 +1080,19 @@ void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk) emsg += Quoted(IncluderFile); emsg += ".\n"; emsg += result.ErrorMessage; - wrk.LogCommandError(GenT::UIC, emsg, cmd, result.StdOut); + LogCommandError(GenT::UIC, emsg, cmd, result.StdOut); } - wrk.FileSys().FileRemove(BuildFile); + FileSys().FileRemove(BuildFile); } } } -cmQtAutoGeneratorMocUic::WorkerT::WorkerT(cmQtAutoGeneratorMocUic* gen, - uv_loop_t* uvLoop) - : Gen_(gen) -{ - // Initialize uv asynchronous callback for process starting - ProcessRequest_.init(*uvLoop, &WorkerT::UVProcessStart, this); - // Start thread - Thread_ = std::thread(&WorkerT::Loop, this); -} - -cmQtAutoGeneratorMocUic::WorkerT::~WorkerT() -{ - // Join thread - if (Thread_.joinable()) { - Thread_.join(); - } -} - -void cmQtAutoGeneratorMocUic::WorkerT::LogInfo( - GenT genType, std::string const& message) const +void cmQtAutoMocUic::JobFinishT::Process() { - Log().Info(genType, message); + Gen()->AbortSuccess(); } -void cmQtAutoGeneratorMocUic::WorkerT::LogWarning( - GenT genType, std::string const& message) const -{ - Log().Warning(genType, message); -} - -void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning( - GenT genType, std::string const& filename, std::string const& message) const -{ - Log().WarningFile(genType, filename, message); -} - -void cmQtAutoGeneratorMocUic::WorkerT::LogError( - GenT genType, std::string const& message) const -{ - Gen().ParallelRegisterJobError(); - Log().Error(genType, message); -} - -void cmQtAutoGeneratorMocUic::WorkerT::LogFileError( - GenT genType, std::string const& filename, std::string const& message) const -{ - Gen().ParallelRegisterJobError(); - Log().ErrorFile(genType, filename, message); -} - -void cmQtAutoGeneratorMocUic::WorkerT::LogCommandError( - GenT genType, std::string const& message, - std::vector<std::string> const& command, std::string const& output) const -{ - Gen().ParallelRegisterJobError(); - Log().ErrorCommand(genType, message, command, output); -} - -bool cmQtAutoGeneratorMocUic::WorkerT::RunProcess( - GenT genType, ProcessResultT& result, - std::vector<std::string> const& command) -{ - if (command.empty()) { - return false; - } - - // Create process instance - { - std::lock_guard<std::mutex> lock(ProcessMutex_); - Process_ = cm::make_unique<ReadOnlyProcessT>(); - Process_->setup(&result, true, command, Gen().Base().AutogenBuildDir); - } - - // Send asynchronous process start request to libuv loop - ProcessRequest_.send(); - - // Log command - if (this->Log().Verbose()) { - std::string msg = "Running command:\n"; - msg += QuotedCommand(command); - msg += '\n'; - this->LogInfo(genType, msg); - } - - // Wait until the process has been finished and destroyed - { - std::unique_lock<std::mutex> ulock(ProcessMutex_); - while (Process_) { - ProcessCondition_.wait(ulock); - } - } - return !result.error(); -} - -void cmQtAutoGeneratorMocUic::WorkerT::Loop() -{ - while (true) { - Gen().WorkerSwapJob(JobHandle_); - if (JobHandle_) { - JobHandle_->Process(*this); - } else { - break; - } - } -} - -void cmQtAutoGeneratorMocUic::WorkerT::UVProcessStart(uv_async_t* handle) -{ - auto& wrk = *reinterpret_cast<WorkerT*>(handle->data); - { - std::lock_guard<std::mutex> lock(wrk.ProcessMutex_); - if (wrk.Process_ && !wrk.Process_->IsStarted()) { - wrk.Process_->start(handle->loop, [&wrk] { wrk.UVProcessFinished(); }); - } - } - - if (!wrk.Process_->IsStarted()) { - wrk.UVProcessFinished(); - } -} - -void cmQtAutoGeneratorMocUic::WorkerT::UVProcessFinished() -{ - { - std::lock_guard<std::mutex> lock(ProcessMutex_); - if (Process_ && (Process_->IsFinished() || !Process_->IsStarted())) { - Process_.reset(); - } - } - // Notify idling thread - ProcessCondition_.notify_one(); -} - -cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() +cmQtAutoMocUic::cmQtAutoMocUic() : Base_(&FileSys()) , Moc_(&FileSys()) { @@ -1147,26 +1102,11 @@ cmQtAutoGeneratorMocUic::cmQtAutoGeneratorMocUic() "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); Uic_.RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); - - // Initialize libuv loop - uv_disable_stdio_inheritance(); -#ifdef CMAKE_UV_SIGNAL_HACK - UVHackRAII_ = cm::make_unique<cmUVSignalHackRAII>(); -#endif - UVLoop_ = cm::make_unique<uv_loop_t>(); - uv_loop_init(UVLoop()); - - // Initialize libuv asynchronous iteration request - UVRequest().init(*UVLoop(), &cmQtAutoGeneratorMocUic::UVPollStage, this); } -cmQtAutoGeneratorMocUic::~cmQtAutoGeneratorMocUic() -{ - // Close libuv loop - uv_loop_close(UVLoop()); -} +cmQtAutoMocUic::~cmQtAutoMocUic() = default; -bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) +bool cmQtAutoMocUic::Init(cmMakefile* makefile) { // -- Meta Base_.HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); @@ -1364,7 +1304,7 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) Moc_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD"); // Install moc predefs job if (!Moc().PredefsCmd.empty()) { - JobQueues_.MocPredefs.emplace_back(cm::make_unique<JobMocPredefsT>()); + WorkerPool().EmplaceJob<JobMocPredefsT>(); } } @@ -1400,46 +1340,48 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) } // - Headers and sources + // Add sources { - auto addHeader = [this](std::string&& hdr, bool moc, bool uic) { - this->JobQueues_.Headers.emplace_back( - cm::make_unique<JobParseT>(std::move(hdr), moc, uic, true)); - }; auto addSource = [this](std::string&& src, bool moc, bool uic) { - this->JobQueues_.Sources.emplace_back( - cm::make_unique<JobParseT>(std::move(src), moc, uic, false)); + WorkerPool().EmplaceJob<JobParseT>(std::move(src), moc, uic, false); }; - - // Add headers - for (std::string& hdr : InfoGetList("AM_HEADERS")) { - addHeader(std::move(hdr), true, true); + for (std::string& src : InfoGetList("AM_SOURCES")) { + addSource(std::move(src), true, true); } if (Moc().Enabled) { - for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) { - addHeader(std::move(hdr), true, false); + for (std::string& src : InfoGetList("AM_MOC_SOURCES")) { + addSource(std::move(src), true, false); } } if (Uic().Enabled) { - for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) { - addHeader(std::move(hdr), false, true); + for (std::string& src : InfoGetList("AM_UIC_SOURCES")) { + addSource(std::move(src), false, true); } } - - // Add sources - for (std::string& src : InfoGetList("AM_SOURCES")) { - addSource(std::move(src), true, true); + } + // Add Fence job + WorkerPool().EmplaceJob<JobFenceT>(); + // Add headers + { + auto addHeader = [this](std::string&& hdr, bool moc, bool uic) { + WorkerPool().EmplaceJob<JobParseT>(std::move(hdr), moc, uic, true); + }; + for (std::string& hdr : InfoGetList("AM_HEADERS")) { + addHeader(std::move(hdr), true, true); } if (Moc().Enabled) { - for (std::string& src : InfoGetList("AM_MOC_SOURCES")) { - addSource(std::move(src), true, false); + for (std::string& hdr : InfoGetList("AM_MOC_HEADERS")) { + addHeader(std::move(hdr), true, false); } } if (Uic().Enabled) { - for (std::string& src : InfoGetList("AM_UIC_SOURCES")) { - addSource(std::move(src), false, true); + for (std::string& hdr : InfoGetList("AM_UIC_HEADERS")) { + addHeader(std::move(hdr), false, true); } } } + // Addpost parse fence job + WorkerPool().EmplaceJob<JobPostParseT>(); // Init derived information // ------------------------ @@ -1534,98 +1476,25 @@ bool cmQtAutoGeneratorMocUic::Init(cmMakefile* makefile) return true; } -bool cmQtAutoGeneratorMocUic::Process() +bool cmQtAutoMocUic::Process() { - // Run libuv event loop - UVRequest().send(); - if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) { - if (JobError_) { - return false; - } - } else { + SettingsFileRead(); + if (!CreateDirectories()) { return false; } - return true; -} -void cmQtAutoGeneratorMocUic::UVPollStage(uv_async_t* handle) -{ - reinterpret_cast<cmQtAutoGeneratorMocUic*>(handle->data)->PollStage(); -} - -void cmQtAutoGeneratorMocUic::PollStage() -{ - switch (Stage_) { - case StageT::SETTINGS_READ: - SettingsFileRead(); - SetStage(StageT::CREATE_DIRECTORIES); - break; - case StageT::CREATE_DIRECTORIES: - CreateDirectories(); - SetStage(StageT::PARSE_SOURCES); - break; - case StageT::PARSE_SOURCES: - if (ThreadsStartJobs(JobQueues_.Sources)) { - SetStage(StageT::PARSE_HEADERS); - } - break; - case StageT::PARSE_HEADERS: - if (ThreadsStartJobs(JobQueues_.Headers)) { - SetStage(StageT::MOC_PREDEFS); - } - break; - case StageT::MOC_PREDEFS: - if (ThreadsStartJobs(JobQueues_.MocPredefs)) { - SetStage(StageT::MOC_PROCESS); - } - break; - case StageT::MOC_PROCESS: - if (ThreadsStartJobs(JobQueues_.Moc)) { - SetStage(StageT::MOCS_COMPILATION); - } - break; - case StageT::MOCS_COMPILATION: - if (ThreadsJobsDone()) { - MocGenerateCompilation(); - SetStage(StageT::UIC_PROCESS); - } - break; - case StageT::UIC_PROCESS: - if (ThreadsStartJobs(JobQueues_.Uic)) { - SetStage(StageT::SETTINGS_WRITE); - } - break; - case StageT::SETTINGS_WRITE: - SettingsFileWrite(); - SetStage(StageT::FINISH); - break; - case StageT::FINISH: - if (ThreadsJobsDone()) { - // Clear all libuv handles - ThreadsStop(); - UVRequest().reset(); - // Set highest END stage manually - Stage_ = StageT::END; - } - break; - case StageT::END: - break; + if (!WorkerPool_.Process(Base().NumThreads, this)) { + return false; } -} -void cmQtAutoGeneratorMocUic::SetStage(StageT stage) -{ if (JobError_) { - stage = StageT::FINISH; - } - // Only allow to increase the stage - if (Stage_ < stage) { - Stage_ = stage; - UVRequest().send(); + return false; } + + return SettingsFileWrite(); } -void cmQtAutoGeneratorMocUic::SettingsFileRead() +void cmQtAutoMocUic::SettingsFileRead() { // Compose current settings strings { @@ -1691,11 +1560,10 @@ void cmQtAutoGeneratorMocUic::SettingsFileRead() } } -void cmQtAutoGeneratorMocUic::SettingsFileWrite() +bool cmQtAutoMocUic::SettingsFileWrite() { - std::lock_guard<std::mutex> jobsLock(JobsMutex_); // Only write if any setting changed - if (!JobError_ && (Moc().SettingsChanged || Uic().SettingsChanged)) { + if (Moc().SettingsChanged || Uic().SettingsChanged) { if (Log().Verbose()) { Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_)); } @@ -1721,255 +1589,142 @@ void cmQtAutoGeneratorMocUic::SettingsFileWrite() "Settings file writing failed. " + error); // Remove old settings file to trigger a full rebuild on the next run FileSys().FileRemove(SettingsFile_); - RegisterJobError(); + return false; } } + return true; } -void cmQtAutoGeneratorMocUic::CreateDirectories() +bool cmQtAutoMocUic::CreateDirectories() { // Create AUTOGEN include directory if (!FileSys().MakeDirectory(Base().AutogenIncludeDir)) { Log().ErrorFile(GenT::GEN, Base().AutogenIncludeDir, "Could not create directory."); - RegisterJobError(); + return false; } + return true; } -bool cmQtAutoGeneratorMocUic::ThreadsStartJobs(JobQueueT& queue) +// Private method that requires cmQtAutoMocUic::JobsMutex_ to be +// locked +void cmQtAutoMocUic::Abort(bool error) { - bool done = false; - std::size_t queueSize = queue.size(); - - // Change the active queue - { - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - // Check if there are still unfinished jobs from the previous queue - if (JobsRemain_ == 0) { - if (!JobThreadsAbort_) { - JobQueue_.swap(queue); - JobsRemain_ = queueSize; - } else { - // Abort requested - queue.clear(); - queueSize = 0; - } - done = true; - } + if (error) { + JobError_.store(true); } + WorkerPool_.Abort(); +} - if (done && (queueSize != 0)) { - // Start new threads on demand - if (Workers_.empty()) { - Workers_.resize(Base().NumThreads); - for (auto& item : Workers_) { - item = cm::make_unique<WorkerT>(this, UVLoop()); +bool cmQtAutoMocUic::ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle) +{ + JobMocT const& mocJob(static_cast<JobMocT&>(*jobHandle)); + // Do additional tests if this is an included moc job + if (!mocJob.IncludeString.empty()) { + std::lock_guard<std::mutex> guard(MocMetaMutex_); + // Register included moc file + MocIncludedFiles_.emplace(mocJob.SourceFile); + + // Check if the same moc file would be generated from a different + // source file. + auto const range = MocIncludes_.equal_range(mocJob.IncludeString); + for (auto it = range.first; it != range.second; ++it) { + if (it->second[0] == mocJob.SourceFile) { + // The output file already gets generated + return true; } - } else { - // Notify threads - if (queueSize == 1) { - JobsConditionRead_.notify_one(); - } else { - JobsConditionRead_.notify_all(); + { + // The output file already gets generated - from a different source + // file! + std::string error = "The two source files\n "; + error += Quoted(mocJob.IncluderFile); + error += " and\n "; + error += Quoted(it->second[1]); + error += "\ncontain the same moc include string "; + error += Quoted(mocJob.IncludeString); + error += "\nbut the moc file would be generated from different " + "source files\n "; + error += Quoted(mocJob.SourceFile); + error += " and\n "; + error += Quoted(it->second[0]); + error += ".\nConsider to\n" + "- not include the \"moc_<NAME>.cpp\" file\n" + "- add a directory prefix to a \"<NAME>.moc\" include " + "(e.g \"sub/<NAME>.moc\")\n" + "- rename the source file(s)\n"; + Log().Error(GenT::MOC, error); + AbortError(); + return false; } } - } - return done; -} - -void cmQtAutoGeneratorMocUic::ThreadsStop() -{ - if (!Workers_.empty()) { - // Clear all jobs - { - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - JobThreadsAbort_ = true; - JobsRemain_ -= JobQueue_.size(); - JobQueue_.clear(); - - JobQueues_.Sources.clear(); - JobQueues_.Headers.clear(); - JobQueues_.MocPredefs.clear(); - JobQueues_.Moc.clear(); - JobQueues_.Uic.clear(); - } - // Wake threads - JobsConditionRead_.notify_all(); - // Join and clear threads - Workers_.clear(); + // We're still here so register this job + MocIncludes_.emplace_hint(range.first, mocJob.IncludeString, + std::array<std::string, 2>{ + { mocJob.SourceFile, mocJob.IncluderFile } }); } + return WorkerPool_.PushJob(std::move(jobHandle)); } -bool cmQtAutoGeneratorMocUic::ThreadsJobsDone() -{ - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - return (JobsRemain_ == 0); -} - -void cmQtAutoGeneratorMocUic::WorkerSwapJob(JobHandleT& jobHandle) +bool cmQtAutoMocUic::ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle) { - bool const jobProcessed(jobHandle); - if (jobProcessed) { - jobHandle.reset(); - } + const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle)); { - std::unique_lock<std::mutex> jobsLock(JobsMutex_); - // Reduce the remaining job count and notify the libuv loop - // when all jobs are done - if (jobProcessed) { - --JobsRemain_; - if (JobsRemain_ == 0) { - UVRequest().send(); + std::lock_guard<std::mutex> guard(UicMetaMutex_); + // Check if the same uic file would be generated from a different + // source file. + auto const range = UicIncludes_.equal_range(uicJob.IncludeString); + for (auto it = range.first; it != range.second; ++it) { + if (it->second[0] == uicJob.SourceFile) { + // The output file already gets generated + return true; } - } - // Wait for new jobs - while (!JobThreadsAbort_ && JobQueue_.empty()) { - JobsConditionRead_.wait(jobsLock); - } - // Try to pick up a new job handle - if (!JobThreadsAbort_ && !JobQueue_.empty()) { - jobHandle = std::move(JobQueue_.front()); - JobQueue_.pop_front(); - } - } -} - -void cmQtAutoGeneratorMocUic::ParallelRegisterJobError() -{ - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - RegisterJobError(); -} - -// Private method that requires cmQtAutoGeneratorMocUic::JobsMutex_ to be -// locked -void cmQtAutoGeneratorMocUic::RegisterJobError() -{ - JobError_ = true; - if (!JobThreadsAbort_) { - JobThreadsAbort_ = true; - // Clear remaining jobs - if (JobsRemain_ != 0) { - JobsRemain_ -= JobQueue_.size(); - JobQueue_.clear(); - } - } -} - -bool cmQtAutoGeneratorMocUic::ParallelJobPushMoc(JobHandleT& jobHandle) -{ - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - if (!JobThreadsAbort_) { - bool pushJobHandle = true; - // Do additional tests if this is an included moc job - const JobMocT& mocJob(static_cast<JobMocT&>(*jobHandle)); - if (!mocJob.IncludeString.empty()) { - // Register included moc file and look for collisions - MocIncludedFiles_.emplace(mocJob.SourceFile); - if (!MocIncludedStrings_.emplace(mocJob.IncludeString).second) { - // Another source file includes the same moc file! - for (const JobHandleT& otherHandle : JobQueues_.Moc) { - const JobMocT& otherJob(static_cast<JobMocT&>(*otherHandle)); - if (otherJob.IncludeString == mocJob.IncludeString) { - // Check if the same moc file would be generated from different - // source files which is an error. - if (otherJob.SourceFile != mocJob.SourceFile) { - // Include string collision - std::string error = "The two source files\n "; - error += Quoted(mocJob.IncluderFile); - error += " and\n "; - error += Quoted(otherJob.IncluderFile); - error += "\ncontain the same moc include string "; - error += Quoted(mocJob.IncludeString); - error += "\nbut the moc file would be generated from different " - "source files\n "; - error += Quoted(mocJob.SourceFile); - error += " and\n "; - error += Quoted(otherJob.SourceFile); - error += ".\nConsider to\n" - "- not include the \"moc_<NAME>.cpp\" file\n" - "- add a directory prefix to a \"<NAME>.moc\" include " - "(e.g \"sub/<NAME>.moc\")\n" - "- rename the source file(s)\n"; - Log().Error(GenT::MOC, error); - RegisterJobError(); - } - // Do not push this job in since the included moc file already - // gets generated by an other job. - pushJobHandle = false; - break; - } - } + { + // The output file already gets generated - from a different .ui + // file! + std::string error = "The two source files\n "; + error += Quoted(uicJob.IncluderFile); + error += " and\n "; + error += Quoted(it->second[1]); + error += "\ncontain the same uic include string "; + error += Quoted(uicJob.IncludeString); + error += "\nbut the uic file would be generated from different " + "source files\n "; + error += Quoted(uicJob.SourceFile); + error += " and\n "; + error += Quoted(it->second[0]); + error += + ".\nConsider to\n" + "- add a directory prefix to a \"ui_<NAME>.h\" include " + "(e.g \"sub/ui_<NAME>.h\")\n" + "- rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" " + "include(s)\n"; + Log().Error(GenT::UIC, error); + AbortError(); + return false; } } - // Push job on demand - if (pushJobHandle) { - JobQueues_.Moc.emplace_back(std::move(jobHandle)); - } - } - return !JobError_; -} -bool cmQtAutoGeneratorMocUic::ParallelJobPushUic(JobHandleT& jobHandle) -{ - std::lock_guard<std::mutex> jobsLock(JobsMutex_); - if (!JobThreadsAbort_) { - bool pushJobHandle = true; - // Look for include collisions. - const JobUicT& uicJob(static_cast<JobUicT&>(*jobHandle)); - for (const JobHandleT& otherHandle : JobQueues_.Uic) { - const JobUicT& otherJob(static_cast<JobUicT&>(*otherHandle)); - if (otherJob.IncludeString == uicJob.IncludeString) { - // Check if the same uic file would be generated from different - // source files which would be an error. - if (otherJob.SourceFile != uicJob.SourceFile) { - // Include string collision - std::string error = "The two source files\n "; - error += Quoted(uicJob.IncluderFile); - error += " and\n "; - error += Quoted(otherJob.IncluderFile); - error += "\ncontain the same uic include string "; - error += Quoted(uicJob.IncludeString); - error += "\nbut the uic file would be generated from different " - "source files\n "; - error += Quoted(uicJob.SourceFile); - error += " and\n "; - error += Quoted(otherJob.SourceFile); - error += - ".\nConsider to\n" - "- add a directory prefix to a \"ui_<NAME>.h\" include " - "(e.g \"sub/ui_<NAME>.h\")\n" - "- rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" " - "include(s)\n"; - Log().Error(GenT::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)); - } + // We're still here so register this job + UicIncludes_.emplace_hint(range.first, uicJob.IncludeString, + std::array<std::string, 2>{ + { uicJob.SourceFile, uicJob.IncluderFile } }); } - return !JobError_; + return WorkerPool_.PushJob(std::move(jobHandle)); } -bool cmQtAutoGeneratorMocUic::ParallelMocIncluded( - std::string const& sourceFile) +bool cmQtAutoMocUic::ParallelMocIncluded(std::string const& sourceFile) { - std::lock_guard<std::mutex> mocLock(JobsMutex_); + std::lock_guard<std::mutex> guard(MocMetaMutex_); return (MocIncludedFiles_.find(sourceFile) != MocIncludedFiles_.end()); } -std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( +std::string cmQtAutoMocUic::ParallelMocAutoRegister( std::string const& baseName) { std::string res; { - std::lock_guard<std::mutex> mocLock(JobsMutex_); + std::lock_guard<std::mutex> mocLock(MocMetaMutex_); res = baseName; res += ".cpp"; if (MocAutoFiles_.find(res) == MocAutoFiles_.end()) { @@ -1990,63 +1745,3 @@ std::string cmQtAutoGeneratorMocUic::ParallelMocAutoRegister( } return res; } - -void cmQtAutoGeneratorMocUic::ParallelMocAutoUpdated() -{ - std::lock_guard<std::mutex> mocLock(JobsMutex_); - MocAutoFileUpdated_ = true; -} - -void cmQtAutoGeneratorMocUic::MocGenerateCompilation() -{ - std::lock_guard<std::mutex> mocLock(JobsMutex_); - if (!JobError_ && Moc().Enabled) { - // Write mocs compilation build file - { - // Compose mocs compilation file content - std::string content = - "// This file is autogenerated. Changes will be overwritten.\n"; - if (MocAutoFiles_.empty()) { - // Placeholder content - content += "// No files found that require moc or the moc files are " - "included\n"; - content += "enum some_compilers { need_more_than_nothing };\n"; - } else { - // Valid content - char const sbeg = Base().MultiConfig ? '<' : '"'; - char const send = Base().MultiConfig ? '>' : '"'; - for (std::string const& mocfile : MocAutoFiles_) { - content += "#include "; - content += sbeg; - content += mocfile; - content += send; - content += '\n'; - } - } - - std::string const& compAbs = Moc().CompFileAbs; - if (FileSys().FileDiffers(compAbs, content)) { - // Actually write mocs compilation file - if (Log().Verbose()) { - Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs); - } - std::string error; - if (!FileSys().FileWrite(compAbs, content, &error)) { - Log().ErrorFile(GenT::MOC, compAbs, - "mocs compilation file writing failed. " + error); - RegisterJobError(); - return; - } - } else if (MocAutoFileUpdated_) { - // Only touch mocs compilation file - if (Log().Verbose()) { - Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs); - } - FileSys().Touch(compAbs); - } - } - // Write mocs compilation wrapper file - if (Base().MultiConfig) { - } - } -} diff --git a/Source/cmQtAutoGeneratorMocUic.h b/Source/cmQtAutoMocUic.h index 27d73a7..3902abb 100644 --- a/Source/cmQtAutoGeneratorMocUic.h +++ b/Source/cmQtAutoMocUic.h @@ -1,26 +1,22 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ -#ifndef cmQtAutoGeneratorMocUic_h -#define cmQtAutoGeneratorMocUic_h +#ifndef cmQtAutoMocUic_h +#define cmQtAutoMocUic_h #include "cmConfigure.h" // IWYU pragma: keep #include "cmQtAutoGen.h" #include "cmQtAutoGenerator.h" -#include "cmUVHandlePtr.h" -#include "cmUVSignalHackRAII.h" // IWYU pragma: keep -#include "cm_uv.h" +#include "cmWorkerPool.h" #include "cmsys/RegularExpression.hxx" -#include <condition_variable> -#include <cstddef> -#include <deque> +#include <array> +#include <atomic> #include <map> #include <memory> // IWYU pragma: keep #include <mutex> #include <set> #include <string> -#include <thread> #include <unordered_set> #include <utility> #include <vector> @@ -28,18 +24,18 @@ class cmMakefile; // @brief AUTOMOC and AUTOUIC generator -class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator +class cmQtAutoMocUic : public cmQtAutoGenerator { public: - cmQtAutoGeneratorMocUic(); - ~cmQtAutoGeneratorMocUic() override; + cmQtAutoMocUic(); + ~cmQtAutoMocUic() override; - cmQtAutoGeneratorMocUic(cmQtAutoGeneratorMocUic const&) = delete; - cmQtAutoGeneratorMocUic& operator=(cmQtAutoGeneratorMocUic const&) = delete; + cmQtAutoMocUic(cmQtAutoMocUic const&) = delete; + cmQtAutoMocUic& operator=(cmQtAutoMocUic const&) = delete; public: // -- Types - class WorkerT; + typedef std::multimap<std::string, std::array<std::string, 2>> IncludesMap; /// @brief Search key plus regular expression pair /// @@ -173,31 +169,71 @@ public: cmsys::RegularExpression RegExpInclude; }; - /// @brief Abstract job class for threaded processing + /// @brief Abstract job class for concurrent job processing /// - class JobT + class JobT : public cmWorkerPool::JobT { - public: - JobT() = default; - virtual ~JobT() = default; + protected: + /** + * @brief Protected default constructor + */ + JobT(bool fence = false) + : cmWorkerPool::JobT(fence) + { + } + + //! Get the generator. Only valid during Process() call! + cmQtAutoMocUic* Gen() const + { + return static_cast<cmQtAutoMocUic*>(UserData()); + }; + + //! Get the file system interface. Only valid during Process() call! + FileSystem& FileSys() { return Gen()->FileSys(); } + //! Get the logger. Only valid during Process() call! + Logger& Log() { return Gen()->Log(); } + + // -- Error logging with automatic abort + void LogError(GenT genType, std::string const& message) const; + void LogFileError(GenT genType, std::string const& filename, + std::string const& message) const; + void LogCommandError(GenT genType, std::string const& message, + std::vector<std::string> const& command, + std::string const& output) const; - JobT(JobT const&) = delete; - JobT& operator=(JobT const&) = delete; + /** + * @brief Run an external process. Use only during Process() call! + */ + bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, + std::vector<std::string> const& command); + }; - // -- Abstract processing interface - virtual void Process(WorkerT& wrk) = 0; + /// @brief Fence job utility class + /// + class JobFenceT : public JobT + { + public: + JobFenceT() + : JobT(true) + { + } + void Process() override{}; }; - // Job management types - typedef std::unique_ptr<JobT> JobHandleT; - typedef std::deque<JobHandleT> JobQueueT; + /// @brief Generate moc_predefs.h + /// + class JobMocPredefsT : public JobT + { + private: + void Process() override; + }; - /// @brief Parse source job + /// @brief Parses a source file /// class JobParseT : public JobT { public: - JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false) + JobParseT(std::string fileName, bool moc, bool uic, bool header = false) : FileName(std::move(fileName)) , AutoMoc(moc) , AutoUic(uic) @@ -213,18 +249,15 @@ public: 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, + void Process() override; + bool ParseMocSource(MetaT const& meta); + bool ParseMocHeader(MetaT const& meta); + std::string MocStringHeaders(std::string const& fileBase) const; + std::string MocFindIncludedHeader(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, + bool ParseUic(MetaT const& meta); + bool ParseUicInclude(MetaT const& meta, std::string&& includeString); + std::string UicFindIncludedFile(MetaT const& meta, std::string const& includeString); private: @@ -234,12 +267,20 @@ public: bool Header = false; }; - /// @brief Generate moc_predefs + /// @brief Generates additional jobs after all files have been parsed /// - class JobMocPredefsT : public JobT + class JobPostParseT : public JobFenceT { private: - void Process(WorkerT& wrk) override; + void Process() override; + }; + + /// @brief Generate mocs_compilation.cpp + /// + class JobMocsCompilationT : public JobFenceT + { + private: + void Process() override; }; /// @brief Moc a file job @@ -247,20 +288,20 @@ public: class JobMocT : public JobT { public: - JobMocT(std::string&& sourceFile, std::string includerFile, - std::string&& includeString) + JobMocT(std::string sourceFile, std::string includerFile, + std::string includeString) : SourceFile(std::move(sourceFile)) , IncluderFile(std::move(includerFile)) , IncludeString(std::move(includeString)) { } - void FindDependencies(WorkerT& wrk, std::string const& content); + void FindDependencies(std::string const& content); private: - void Process(WorkerT& wrk) override; - bool UpdateRequired(WorkerT& wrk); - void GenerateMoc(WorkerT& wrk); + void Process() override; + bool UpdateRequired(); + void GenerateMoc(); public: std::string SourceFile; @@ -276,8 +317,8 @@ public: class JobUicT : public JobT { public: - JobUicT(std::string&& sourceFile, std::string includerFile, - std::string&& includeString) + JobUicT(std::string sourceFile, std::string includerFile, + std::string includeString) : SourceFile(std::move(sourceFile)) , IncluderFile(std::move(includerFile)) , IncludeString(std::move(includeString)) @@ -285,9 +326,9 @@ public: } private: - void Process(WorkerT& wrk) override; - bool UpdateRequired(WorkerT& wrk); - void GenerateUic(WorkerT& wrk); + void Process() override; + bool UpdateRequired(); + void GenerateUic(); public: std::string SourceFile; @@ -296,80 +337,12 @@ public: std::string BuildFile; }; - /// @brief Worker Thread + /// @brief The last job /// - class WorkerT + class JobFinishT : public JobFenceT { - public: - WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop); - ~WorkerT(); - - WorkerT(WorkerT const&) = delete; - WorkerT& operator=(WorkerT const&) = delete; - - // -- 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(); } - - // -- Log info - void LogInfo(GenT genType, std::string const& message) const; - // -- Log warning - void LogWarning(GenT genType, std::string const& message) const; - void LogFileWarning(GenT genType, std::string const& filename, - std::string const& message) const; - // -- Log error - void LogError(GenT genType, std::string const& message) const; - void LogFileError(GenT genType, std::string const& filename, - std::string const& message) const; - void LogCommandError(GenT genType, std::string const& message, - std::vector<std::string> const& command, - std::string const& output) const; - - // -- External processes - /// @brief Verbose logging version - bool RunProcess(GenT genType, ProcessResultT& result, - std::vector<std::string> 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<ReadOnlyProcessT> Process_; - // -- System thread - std::thread Thread_; - }; - - /// @brief Processing stage - enum class StageT - { - SETTINGS_READ, - CREATE_DIRECTORIES, - PARSE_SOURCES, - PARSE_HEADERS, - MOC_PREDEFS, - MOC_PROCESS, - MOCS_COMPILATION, - UIC_PROCESS, - SETTINGS_WRITE, - FINISH, - END + void Process() override; }; // -- Const settings interface @@ -377,41 +350,39 @@ public: 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); + cmWorkerPool& WorkerPool() { return WorkerPool_; } + void AbortError() { Abort(true); } + void AbortSuccess() { Abort(false); } + bool ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle); + bool ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle); + + // -- Mocs compilation include file updated flag + void ParallelMocAutoUpdated() { MocAutoFileUpdated_.store(true); } + bool MocAutoFileUpdated() const { return MocAutoFileUpdated_.load(); } + + // -- Mocs compilation file register std::string ParallelMocAutoRegister(std::string const& baseName); - void ParallelMocAutoUpdated(); + bool ParallelMocIncluded(std::string const& sourceFile); + std::set<std::string> const& MocAutoFiles() const + { + return this->MocAutoFiles_; + } private: // -- Utility accessors Logger& Log() { return Logger_; } FileSystem& FileSys() { return FileSys_; } - // -- libuv loop accessors - uv_loop_t* UVLoop() { return UVLoop_.get(); } - cm::uv_async_ptr& UVRequest() { return UVRequest_; } // -- 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(); + bool SettingsFileWrite(); // -- Thread processing - bool ThreadsStartJobs(JobQueueT& queue); - bool ThreadsJobsDone(); - void ThreadsStop(); - void RegisterJobError(); + void Abort(bool error); // -- Generation - void CreateDirectories(); - void MocGenerateCompilation(); + bool CreateDirectories(); private: // -- Utility @@ -421,39 +392,22 @@ private: BaseSettingsT Base_; MocSettingsT Moc_; UicSettingsT Uic_; - // -- libuv loop -#ifdef CMAKE_UV_SIGNAL_HACK - std::unique_ptr<cmUVSignalHackRAII> UVHackRAII_; -#endif - std::unique_ptr<uv_loop_t> UVLoop_; - cm::uv_async_ptr UVRequest_; - StageT Stage_ = StageT::SETTINGS_READ; - // -- Job queues - std::mutex JobsMutex_; - struct - { - JobQueueT Sources; - JobQueueT Headers; - JobQueueT MocPredefs; - JobQueueT Moc; - JobQueueT Uic; - } JobQueues_; - JobQueueT JobQueue_; - std::size_t volatile JobsRemain_ = 0; - bool volatile JobError_ = false; - bool volatile JobThreadsAbort_ = false; - std::condition_variable JobsConditionRead_; // -- Moc meta - std::set<std::string> MocIncludedStrings_; + std::mutex MocMetaMutex_; std::set<std::string> MocIncludedFiles_; + IncludesMap MocIncludes_; std::set<std::string> MocAutoFiles_; - bool volatile MocAutoFileUpdated_ = false; + std::atomic<bool> MocAutoFileUpdated_ = ATOMIC_VAR_INIT(false); + // -- Uic meta + std::mutex UicMetaMutex_; + IncludesMap UicIncludes_; // -- Settings file std::string SettingsFile_; std::string SettingsStringMoc_; std::string SettingsStringUic_; - // -- Threads and loops - std::vector<std::unique_ptr<WorkerT>> Workers_; + // -- Thread pool and job queue + std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false); + cmWorkerPool WorkerPool_; }; #endif diff --git a/Source/cmTargetPropertyComputer.cxx b/Source/cmTargetPropertyComputer.cxx index d2c3496..994fcf7 100644 --- a/Source/cmTargetPropertyComputer.cxx +++ b/Source/cmTargetPropertyComputer.cxx @@ -67,6 +67,8 @@ bool cmTargetPropertyComputer::WhiteListedInterfaceProperty( builtIns.insert("IMPORTED_GLOBAL"); builtIns.insert("MANUALLY_ADDED_DEPENDENCIES"); builtIns.insert("NAME"); + builtIns.insert("PRIVATE_HEADER"); + builtIns.insert("PUBLIC_HEADER"); builtIns.insert("TYPE"); } diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index 5195957..0cec2fb 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -1430,10 +1430,10 @@ std::string cmVisualStudio10TargetGenerator::ConvertPath( static void ConvertToWindowsSlash(std::string& s) { // first convert all of the slashes - std::string::size_type pos = 0; - while ((pos = s.find('/', pos)) != std::string::npos) { - s[pos] = '\\'; - pos++; + for (auto& ch : s) { + if (ch == '/') { + ch = '\\'; + } } } @@ -1449,7 +1449,7 @@ void cmVisualStudio10TargetGenerator::WriteGroups() std::vector<cmGeneratorTarget::AllConfigSource> const& sources = this->GeneratorTarget->GetAllConfigSources(); - std::set<cmSourceGroup*> groupsUsed; + std::set<cmSourceGroup const*> groupsUsed; for (cmGeneratorTarget::AllConfigSource const& si : sources) { std::string const& source = si.Source->GetFullPath(); cmSourceGroup* sourceGroup = @@ -1534,13 +1534,13 @@ void cmVisualStudio10TargetGenerator::WriteGroups() { Elem e1(e0, "ItemGroup"); e1.SetHasElements(); - std::vector<cmSourceGroup*> groupsVec(groupsUsed.begin(), - groupsUsed.end()); + std::vector<cmSourceGroup const*> groupsVec(groupsUsed.begin(), + groupsUsed.end()); std::sort(groupsVec.begin(), groupsVec.end(), - [](cmSourceGroup* l, cmSourceGroup* r) { + [](cmSourceGroup const* l, cmSourceGroup const* r) { return l->GetFullName() < r->GetFullName(); }); - for (cmSourceGroup* sg : groupsVec) { + for (cmSourceGroup const* sg : groupsVec) { std::string const& name = sg->GetFullName(); if (!name.empty()) { std::string guidName = "SG_Filter_" + name; @@ -1572,7 +1572,7 @@ void cmVisualStudio10TargetGenerator::WriteGroups() // Add to groupsUsed empty source groups that have non-empty children. void cmVisualStudio10TargetGenerator::AddMissingSourceGroups( - std::set<cmSourceGroup*>& groupsUsed, + std::set<cmSourceGroup const*>& groupsUsed, const std::vector<cmSourceGroup>& allGroups) { for (cmSourceGroup const& current : allGroups) { @@ -1583,17 +1583,15 @@ void cmVisualStudio10TargetGenerator::AddMissingSourceGroups( this->AddMissingSourceGroups(groupsUsed, children); - cmSourceGroup* current_ptr = const_cast<cmSourceGroup*>(¤t); - if (groupsUsed.find(current_ptr) != groupsUsed.end()) { + if (groupsUsed.count(¤t) > 0) { continue; // group has already been added to set } // check if it least one of the group's descendants is not empty // (at least one child must already have been added) - std::vector<cmSourceGroup>::const_iterator child_it = children.begin(); + auto child_it = children.begin(); while (child_it != children.end()) { - cmSourceGroup* child_ptr = const_cast<cmSourceGroup*>(&(*child_it)); - if (groupsUsed.find(child_ptr) != groupsUsed.end()) { + if (groupsUsed.count(&(*child_it)) > 0) { break; // found a child that was already added => add current group too } child_it++; @@ -1603,7 +1601,7 @@ void cmVisualStudio10TargetGenerator::AddMissingSourceGroups( continue; // no descendants have source files => ignore this group } - groupsUsed.insert(current_ptr); + groupsUsed.insert(¤t); } } @@ -2530,10 +2528,9 @@ bool cmVisualStudio10TargetGenerator::ComputeClOptions( } else { std::set<std::string> languages; this->GeneratorTarget->GetLanguages(languages, configName); - for (const char* const* l = cm::cbegin(clLangs); l != cm::cend(clLangs); - ++l) { - if (languages.find(*l) != languages.end()) { - langForClCompile = *l; + for (const char* l : clLangs) { + if (languages.count(l)) { + langForClCompile = l; break; } } @@ -4058,10 +4055,7 @@ bool cmVisualStudio10TargetGenerator::IsResxHeader( { std::set<std::string> expectedResxHeaders; this->GeneratorTarget->GetExpectedResxHeaders(expectedResxHeaders, ""); - - std::set<std::string>::const_iterator it = - expectedResxHeaders.find(headerFile); - return it != expectedResxHeaders.end(); + return expectedResxHeaders.count(headerFile) > 0; } bool cmVisualStudio10TargetGenerator::IsXamlHeader( @@ -4069,10 +4063,7 @@ bool cmVisualStudio10TargetGenerator::IsXamlHeader( { std::set<std::string> expectedXamlHeaders; this->GeneratorTarget->GetExpectedXamlHeaders(expectedXamlHeaders, ""); - - std::set<std::string>::const_iterator it = - expectedXamlHeaders.find(headerFile); - return it != expectedXamlHeaders.end(); + return expectedXamlHeaders.count(headerFile) > 0; } bool cmVisualStudio10TargetGenerator::IsXamlSource( @@ -4080,10 +4071,7 @@ bool cmVisualStudio10TargetGenerator::IsXamlSource( { std::set<std::string> expectedXamlSources; this->GeneratorTarget->GetExpectedXamlSources(expectedXamlSources, ""); - - std::set<std::string>::const_iterator it = - expectedXamlSources.find(sourceFile); - return it != expectedXamlSources.end(); + return expectedXamlSources.count(sourceFile) > 0; } void cmVisualStudio10TargetGenerator::WriteApplicationTypeSettings(Elem& e1) @@ -4654,10 +4642,8 @@ void cmVisualStudio10TargetGenerator::GetCSharpSourceProperties( void cmVisualStudio10TargetGenerator::WriteCSharpSourceProperties( Elem& e2, const std::map<std::string, std::string>& tags) { - if (!tags.empty()) { - for (const auto& i : tags) { - e2.Element(i.first.c_str(), i.second); - } + for (const auto& i : tags) { + e2.Element(i.first.c_str(), i.second); } } diff --git a/Source/cmVisualStudio10TargetGenerator.h b/Source/cmVisualStudio10TargetGenerator.h index 5901004..6a1ee1d 100644 --- a/Source/cmVisualStudio10TargetGenerator.h +++ b/Source/cmVisualStudio10TargetGenerator.h @@ -165,7 +165,7 @@ private: void WriteGroupSources(Elem& e0, std::string const& name, ToolSources const& sources, std::vector<cmSourceGroup>&); - void AddMissingSourceGroups(std::set<cmSourceGroup*>& groupsUsed, + void AddMissingSourceGroups(std::set<cmSourceGroup const*>& groupsUsed, const std::vector<cmSourceGroup>& allGroups); bool IsResxHeader(const std::string& headerFile); bool IsXamlHeader(const std::string& headerFile); diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx new file mode 100644 index 0000000..464182c --- /dev/null +++ b/Source/cmWorkerPool.cxx @@ -0,0 +1,770 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWorkerPool.h" + +#include "cmRange.h" +#include "cmUVHandlePtr.h" +#include "cmUVSignalHackRAII.h" // IWYU pragma: keep +#include "cm_uv.h" + +#include <algorithm> +#include <array> +#include <condition_variable> +#include <deque> +#include <functional> +#include <mutex> +#include <stddef.h> +#include <thread> + +/** + * @brief libuv pipe buffer class + */ +class cmUVPipeBuffer +{ +public: + typedef cmRange<char const*> DataRange; + typedef std::function<void(DataRange)> DataFunction; + /// On error the ssize_t argument is a non zero libuv error code + typedef std::function<void(ssize_t)> EndFunction; + +public: + /** + * Reset to construction state + */ + void reset(); + + /** + * Initializes uv_pipe(), uv_stream() and uv_handle() + * @return true on success + */ + bool init(uv_loop_t* uv_loop); + + /** + * Start reading + * @return true on success + */ + bool startRead(DataFunction dataFunction, EndFunction endFunction); + + //! libuv pipe + uv_pipe_t* uv_pipe() const { return UVPipe_.get(); } + //! uv_pipe() casted to libuv stream + uv_stream_t* uv_stream() const { return static_cast<uv_stream_t*>(UVPipe_); } + //! uv_pipe() casted to libuv handle + uv_handle_t* uv_handle() { return static_cast<uv_handle_t*>(UVPipe_); } + +private: + // -- 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: + cm::uv_pipe_ptr UVPipe_; + std::vector<char> Buffer_; + DataFunction DataFunction_; + EndFunction EndFunction_; +}; + +void cmUVPipeBuffer::reset() +{ + if (UVPipe_.get() != nullptr) { + EndFunction_ = nullptr; + DataFunction_ = nullptr; + Buffer_.clear(); + Buffer_.shrink_to_fit(); + UVPipe_.reset(); + } +} + +bool cmUVPipeBuffer::init(uv_loop_t* uv_loop) +{ + reset(); + if (uv_loop == nullptr) { + return false; + } + int ret = UVPipe_.init(*uv_loop, 0, this); + return (ret == 0); +} + +bool cmUVPipeBuffer::startRead(DataFunction dataFunction, + EndFunction endFunction) +{ + if (UVPipe_.get() == nullptr) { + return false; + } + if (!dataFunction || !endFunction) { + return false; + } + DataFunction_ = std::move(dataFunction); + EndFunction_ = std::move(endFunction); + int ret = uv_read_start(uv_stream(), &cmUVPipeBuffer::UVAlloc, + &cmUVPipeBuffer::UVData); + return (ret == 0); +} + +void cmUVPipeBuffer::UVAlloc(uv_handle_t* handle, size_t suggestedSize, + uv_buf_t* buf) +{ + auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(handle->data); + pipe.Buffer_.resize(suggestedSize); + buf->base = pipe.Buffer_.data(); + buf->len = static_cast<unsigned long>(pipe.Buffer_.size()); +} + +void cmUVPipeBuffer::UVData(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf) +{ + auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(stream->data); + if (nread > 0) { + if (buf->base != nullptr) { + // Call data function + pipe.DataFunction_(DataRange(buf->base, buf->base + nread)); + } + } else if (nread < 0) { + // Save the end function on the stack before resetting the pipe + EndFunction efunc; + efunc.swap(pipe.EndFunction_); + // Reset pipe before calling the end function + pipe.reset(); + // Call end function + efunc((nread == UV_EOF) ? 0 : nread); + } +} + +/** + * @brief External process management class + */ +class cmUVReadOnlyProcess +{ +public: + // -- Types + //! @brief Process settings + struct SetupT + { + std::string WorkingDirectory; + std::vector<std::string> Command; + cmWorkerPool::ProcessResultT* Result = nullptr; + bool MergedOutput = false; + }; + +public: + // -- Const accessors + SetupT const& Setup() const { return Setup_; } + cmWorkerPool::ProcessResultT* Result() const { return Setup_.Result; } + bool IsStarted() const { return IsStarted_; } + bool IsFinished() const { return IsFinished_; } + + // -- Runtime + void setup(cmWorkerPool::ProcessResultT* result, bool mergedOutput, + std::vector<std::string> const& command, + std::string const& workingDirectory = std::string()); + bool start(uv_loop_t* uv_loop, std::function<void()> finishedCallback); + +private: + // -- Libuv callbacks + static void UVExit(uv_process_t* handle, int64_t exitStatus, int termSignal); + void UVPipeOutData(cmUVPipeBuffer::DataRange data); + void UVPipeOutEnd(ssize_t error); + void UVPipeErrData(cmUVPipeBuffer::DataRange data); + void UVPipeErrEnd(ssize_t error); + void UVTryFinish(); + +private: + // -- Setup + SetupT Setup_; + // -- Runtime + bool IsStarted_ = false; + bool IsFinished_ = false; + std::function<void()> FinishedCallback_; + std::vector<const char*> CommandPtr_; + std::array<uv_stdio_container_t, 3> UVOptionsStdIO_; + uv_process_options_t UVOptions_; + cm::uv_process_ptr UVProcess_; + cmUVPipeBuffer UVPipeOut_; + cmUVPipeBuffer UVPipeErr_; +}; + +void cmUVReadOnlyProcess::setup(cmWorkerPool::ProcessResultT* result, + bool mergedOutput, + std::vector<std::string> const& command, + std::string const& workingDirectory) +{ + Setup_.WorkingDirectory = workingDirectory; + Setup_.Command = command; + Setup_.Result = result; + Setup_.MergedOutput = mergedOutput; +} + +bool cmUVReadOnlyProcess::start(uv_loop_t* uv_loop, + std::function<void()> 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)) { + Result()->ErrorMessage = "libuv stdout pipe initialization failed"; + } + } + if (!Result()->error()) { + if (!UVPipeErr_.init(uv_loop)) { + 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_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream(); + // stderr + UVOptionsStdIO_[2].flags = + static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream(); + + // -- Setup process options + std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0); + UVOptions_.exit_cb = &cmUVReadOnlyProcess::UVExit; + UVOptions_.file = CommandPtr_[0]; + UVOptions_.args = const_cast<char**>(CommandPtr_.data()); + UVOptions_.cwd = Setup_.WorkingDirectory.c_str(); + UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE; + UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size()); + UVOptions_.stdio = UVOptionsStdIO_.data(); + + // -- Spawn process + int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this); + if (uvErrorCode != 0) { + Result()->ErrorMessage = "libuv process spawn failed"; + if (const char* uvErr = uv_strerror(uvErrorCode)) { + Result()->ErrorMessage += ": "; + Result()->ErrorMessage += uvErr; + } + } + } + // -- Start reading from stdio streams + if (!Result()->error()) { + if (!UVPipeOut_.startRead( + [this](cmUVPipeBuffer::DataRange range) { + this->UVPipeOutData(range); + }, + [this](ssize_t error) { this->UVPipeOutEnd(error); })) { + Result()->ErrorMessage = "libuv start reading from stdout pipe failed"; + } + } + if (!Result()->error()) { + if (!UVPipeErr_.startRead( + [this](cmUVPipeBuffer::DataRange range) { + this->UVPipeErrData(range); + }, + [this](ssize_t error) { this->UVPipeErrEnd(error); })) { + 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 cmUVReadOnlyProcess::UVExit(uv_process_t* handle, int64_t exitStatus, + int termSignal) +{ + auto& proc = *reinterpret_cast<cmUVReadOnlyProcess*>(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 + proc.UVProcess_.reset(); + // Try finish + proc.UVTryFinish(); + } +} + +void cmUVReadOnlyProcess::UVPipeOutData(cmUVPipeBuffer::DataRange data) +{ + Result()->StdOut.append(data.begin(), data.end()); +} + +void cmUVReadOnlyProcess::UVPipeOutEnd(ssize_t error) +{ + // Process pipe error + if ((error != 0) && !Result()->error()) { + Result()->ErrorMessage = + "Reading from stdout pipe failed with libuv error code "; + Result()->ErrorMessage += std::to_string(error); + } + // Try finish + UVTryFinish(); +} + +void cmUVReadOnlyProcess::UVPipeErrData(cmUVPipeBuffer::DataRange data) +{ + std::string* str = + Setup_.MergedOutput ? &Result()->StdOut : &Result()->StdErr; + str->append(data.begin(), data.end()); +} + +void cmUVReadOnlyProcess::UVPipeErrEnd(ssize_t error) +{ + // Process pipe error + if ((error != 0) && !Result()->error()) { + Result()->ErrorMessage = + "Reading from stderr pipe failed with libuv error code "; + Result()->ErrorMessage += std::to_string(error); + } + // Try finish + UVTryFinish(); +} + +void cmUVReadOnlyProcess::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) || (UVPipeOut_.uv_pipe() != nullptr) || + (UVPipeErr_.uv_pipe() != nullptr)) { + return; + } + IsFinished_ = true; + FinishedCallback_(); +} + +/** + * @brief Private worker pool internals + */ +class cmWorkerPoolInternal +{ +public: + // -- Types + + /** + * @brief Worker thread + */ + class WorkerT + { + public: + WorkerT(unsigned int index); + ~WorkerT(); + + WorkerT(WorkerT const&) = delete; + WorkerT& operator=(WorkerT const&) = delete; + + /** + * Start the thread + */ + void Start(cmWorkerPoolInternal* internal); + + /** + * @brief Run an external process + */ + bool RunProcess(cmWorkerPool::ProcessResultT& result, + std::vector<std::string> const& command, + std::string const& workingDirectory); + + // -- Accessors + unsigned int Index() const { return Index_; } + cmWorkerPool::JobHandleT& JobHandle() { return JobHandle_; } + + private: + // -- Libuv callbacks + static void UVProcessStart(uv_async_t* handle); + void UVProcessFinished(); + + private: + //! @brief Job handle + cmWorkerPool::JobHandleT JobHandle_; + //! @brief Worker index + unsigned int Index_; + // -- Process management + struct + { + std::mutex Mutex; + cm::uv_async_ptr Request; + std::condition_variable Condition; + std::unique_ptr<cmUVReadOnlyProcess> ROP; + } Proc_; + // -- System thread + std::thread Thread_; + }; + +public: + // -- Constructors + cmWorkerPoolInternal(cmWorkerPool* pool); + ~cmWorkerPoolInternal(); + + /** + * @brief Runs the libuv loop + */ + bool Process(); + + /** + * @brief Clear queue and abort threads + */ + void Abort(); + + /** + * @brief Push a job to the queue and notify a worker + */ + bool PushJob(cmWorkerPool::JobHandleT&& jobHandle); + + /** + * @brief Worker thread main loop method + */ + void Work(WorkerT* worker); + + // -- Request slots + static void UVSlotBegin(uv_async_t* handle); + static void UVSlotEnd(uv_async_t* handle); + +public: + // -- UV loop +#ifdef CMAKE_UV_SIGNAL_HACK + std::unique_ptr<cmUVSignalHackRAII> UVHackRAII; +#endif + std::unique_ptr<uv_loop_t> UVLoop; + cm::uv_async_ptr UVRequestBegin; + cm::uv_async_ptr UVRequestEnd; + + // -- Thread pool and job queue + std::mutex Mutex; + bool Aborting = false; + bool FenceProcessing = false; + unsigned int WorkersRunning = 0; + unsigned int WorkersIdle = 0; + unsigned int JobsProcessing = 0; + std::deque<cmWorkerPool::JobHandleT> Queue; + std::condition_variable Condition; + std::vector<std::unique_ptr<WorkerT>> Workers; + + // -- References + cmWorkerPool* Pool = nullptr; +}; + +cmWorkerPoolInternal::WorkerT::WorkerT(unsigned int index) + : Index_(index) +{ +} + +cmWorkerPoolInternal::WorkerT::~WorkerT() +{ + if (Thread_.joinable()) { + Thread_.join(); + } +} + +void cmWorkerPoolInternal::WorkerT::Start(cmWorkerPoolInternal* internal) +{ + Proc_.Request.init(*(internal->UVLoop), &WorkerT::UVProcessStart, this); + Thread_ = std::thread(&cmWorkerPoolInternal::Work, internal, this); +} + +bool cmWorkerPoolInternal::WorkerT::RunProcess( + cmWorkerPool::ProcessResultT& result, + std::vector<std::string> const& command, std::string const& workingDirectory) +{ + if (command.empty()) { + return false; + } + // Create process instance + { + std::lock_guard<std::mutex> lock(Proc_.Mutex); + Proc_.ROP = cm::make_unique<cmUVReadOnlyProcess>(); + Proc_.ROP->setup(&result, true, command, workingDirectory); + } + // Send asynchronous process start request to libuv loop + Proc_.Request.send(); + // Wait until the process has been finished and destroyed + { + std::unique_lock<std::mutex> ulock(Proc_.Mutex); + while (Proc_.ROP) { + Proc_.Condition.wait(ulock); + } + } + return !result.error(); +} + +void cmWorkerPoolInternal::WorkerT::UVProcessStart(uv_async_t* handle) +{ + auto* wrk = reinterpret_cast<WorkerT*>(handle->data); + bool startFailed = false; + { + auto& Proc = wrk->Proc_; + std::lock_guard<std::mutex> lock(Proc.Mutex); + if (Proc.ROP && !Proc.ROP->IsStarted()) { + startFailed = + !Proc.ROP->start(handle->loop, [wrk] { wrk->UVProcessFinished(); }); + } + } + // Clean up if starting of the process failed + if (startFailed) { + wrk->UVProcessFinished(); + } +} + +void cmWorkerPoolInternal::WorkerT::UVProcessFinished() +{ + { + std::lock_guard<std::mutex> lock(Proc_.Mutex); + if (Proc_.ROP && (Proc_.ROP->IsFinished() || !Proc_.ROP->IsStarted())) { + Proc_.ROP.reset(); + } + } + // Notify idling thread + Proc_.Condition.notify_one(); +} + +void cmWorkerPool::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(); + } +} + +cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool) + : Pool(pool) +{ + // Initialize libuv loop + uv_disable_stdio_inheritance(); +#ifdef CMAKE_UV_SIGNAL_HACK + UVHackRAII = cm::make_unique<cmUVSignalHackRAII>(); +#endif + UVLoop = cm::make_unique<uv_loop_t>(); + uv_loop_init(UVLoop.get()); +} + +cmWorkerPoolInternal::~cmWorkerPoolInternal() +{ + uv_loop_close(UVLoop.get()); +} + +bool cmWorkerPoolInternal::Process() +{ + // Reset state + Aborting = false; + // Initialize libuv asynchronous request + UVRequestBegin.init(*UVLoop, &cmWorkerPoolInternal::UVSlotBegin, this); + UVRequestEnd.init(*UVLoop, &cmWorkerPoolInternal::UVSlotEnd, this); + // Send begin request + UVRequestBegin.send(); + // Run libuv loop + return (uv_run(UVLoop.get(), UV_RUN_DEFAULT) == 0); +} + +void cmWorkerPoolInternal::Abort() +{ + bool firstCall = false; + // Clear all jobs and set abort flag + { + std::lock_guard<std::mutex> guard(Mutex); + if (!Aborting) { + // Register abort and clear queue + Aborting = true; + Queue.clear(); + firstCall = true; + } + } + if (firstCall) { + // Wake threads + Condition.notify_all(); + } +} + +inline bool cmWorkerPoolInternal::PushJob(cmWorkerPool::JobHandleT&& jobHandle) +{ + std::lock_guard<std::mutex> guard(Mutex); + if (Aborting) { + return false; + } + + // Append the job to the queue + Queue.emplace_back(std::move(jobHandle)); + + // Notify an idle worker if there's one + if (WorkersIdle != 0) { + Condition.notify_one(); + } + + return true; +} + +void cmWorkerPoolInternal::UVSlotBegin(uv_async_t* handle) +{ + auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data); + // Create worker threads + { + unsigned int const num = gint.Pool->ThreadCount(); + // Create workers + gint.Workers.reserve(num); + for (unsigned int ii = 0; ii != num; ++ii) { + gint.Workers.emplace_back(cm::make_unique<WorkerT>(ii)); + } + // Start workers + for (auto& wrk : gint.Workers) { + wrk->Start(&gint); + } + } + // Destroy begin request + gint.UVRequestBegin.reset(); +} + +void cmWorkerPoolInternal::UVSlotEnd(uv_async_t* handle) +{ + auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data); + // Join and destroy worker threads + gint.Workers.clear(); + // Destroy end request + gint.UVRequestEnd.reset(); +} + +void cmWorkerPoolInternal::Work(WorkerT* worker) +{ + std::unique_lock<std::mutex> uLock(Mutex); + // Increment running workers count + ++WorkersRunning; + // Enter worker main loop + while (true) { + // Abort on request + if (Aborting) { + break; + } + // Wait for new jobs + if (Queue.empty()) { + ++WorkersIdle; + Condition.wait(uLock); + --WorkersIdle; + continue; + } + + // Check for fence jobs + if (FenceProcessing || Queue.front()->IsFence()) { + if (JobsProcessing != 0) { + Condition.wait(uLock); + continue; + } + // No jobs get processed. Set the fence job processing flag. + FenceProcessing = true; + } + + // Pop next job from queue + worker->JobHandle() = std::move(Queue.front()); + Queue.pop_front(); + + // Unlocked scope for job processing + ++JobsProcessing; + { + uLock.unlock(); + worker->JobHandle()->Work(Pool, worker->Index()); // Process job + worker->JobHandle().reset(); // Destroy job + uLock.lock(); + } + --JobsProcessing; + + // Was this a fence job? + if (FenceProcessing) { + FenceProcessing = false; + Condition.notify_all(); + } + } + + // Decrement running workers count + if (--WorkersRunning == 0) { + // Last worker thread about to finish. Send libuv event. + UVRequestEnd.send(); + } +} + +cmWorkerPool::JobT::~JobT() = default; + +bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result, + std::vector<std::string> const& command, + std::string const& workingDirectory) +{ + // Get worker by index + auto* wrk = Pool_->Int_->Workers.at(WorkerIndex_).get(); + return wrk->RunProcess(result, command, workingDirectory); +} + +cmWorkerPool::cmWorkerPool() + : Int_(cm::make_unique<cmWorkerPoolInternal>(this)) +{ +} + +cmWorkerPool::~cmWorkerPool() = default; + +bool cmWorkerPool::Process(unsigned int threadCount, void* userData) +{ + // Setup user data + UserData_ = userData; + ThreadCount_ = (threadCount > 0) ? threadCount : 1u; + + // Run libuv loop + bool success = Int_->Process(); + + // Clear user data + UserData_ = nullptr; + ThreadCount_ = 0; + + return success; +} + +bool cmWorkerPool::PushJob(JobHandleT&& jobHandle) +{ + return Int_->PushJob(std::move(jobHandle)); +} + +void cmWorkerPool::Abort() +{ + Int_->Abort(); +} diff --git a/Source/cmWorkerPool.h b/Source/cmWorkerPool.h new file mode 100644 index 0000000..71c7d84 --- /dev/null +++ b/Source/cmWorkerPool.h @@ -0,0 +1,219 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWorkerPool_h +#define cmWorkerPool_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmAlgorithms.h" // IWYU pragma: keep + +#include <memory> // IWYU pragma: keep +#include <stdint.h> +#include <string> +#include <utility> +#include <vector> + +// -- Types +class cmWorkerPoolInternal; + +/** @class cmWorkerPool + * @brief Thread pool with job queue + */ +class cmWorkerPool +{ +public: + /** + * 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; + }; + + /** + * Abstract job class for concurrent job processing. + */ + class JobT + { + public: + JobT(JobT const&) = delete; + JobT& operator=(JobT const&) = delete; + + /** + * @brief Virtual destructor. + */ + virtual ~JobT(); + + /** + * @brief Fence job flag + * + * Fence jobs require that: + * - all jobs before in the queue have been processed + * - no jobs later in the queue will be processed before this job was + * processed + */ + bool IsFence() const { return Fence_; } + + protected: + /** + * @brief Protected default constructor + */ + JobT(bool fence = false) + : Fence_(fence) + { + } + + /** + * Abstract processing interface that must be implement in derived classes. + */ + virtual void Process() = 0; + + /** + * Get the worker pool. + * Only valid during the JobT::Process() call! + */ + cmWorkerPool* Pool() const { return Pool_; } + + /** + * Get the user data. + * Only valid during the JobT::Process() call! + */ + void* UserData() const { return Pool_->UserData(); }; + + /** + * Get the worker index. + * This is the index of the thread processing this job and is in the range + * [0..ThreadCount). + * Concurrently processing jobs will never have the same WorkerIndex(). + * Only valid during the JobT::Process() call! + */ + unsigned int WorkerIndex() const { return WorkerIndex_; } + + /** + * Run an external read only process. + * Use only during JobT::Process() call! + */ + bool RunProcess(ProcessResultT& result, + std::vector<std::string> const& command, + std::string const& workingDirectory); + + private: + //! Needs access to Work() + friend class cmWorkerPoolInternal; + //! Worker thread entry method. + void Work(cmWorkerPool* pool, unsigned int workerIndex) + { + Pool_ = pool; + WorkerIndex_ = workerIndex; + this->Process(); + } + + private: + cmWorkerPool* Pool_ = nullptr; + unsigned int WorkerIndex_ = 0; + bool Fence_ = false; + }; + + /** + * @brief Job handle type + */ + typedef std::unique_ptr<JobT> JobHandleT; + + /** + * @brief Fence job base class + */ + class JobFenceT : public JobT + { + public: + JobFenceT() + : JobT(true) + { + } + //! Does nothing + void Process() override{}; + }; + + /** + * @brief Fence job that aborts the worker pool. + * This class is useful as the last job in the job queue. + */ + class JobEndT : JobFenceT + { + public: + //! Does nothing + void Process() override { Pool()->Abort(); } + }; + +public: + // -- Methods + cmWorkerPool(); + ~cmWorkerPool(); + + /** + * @brief Blocking function that starts threads to process all Jobs in + * the queue. + * + * This method blocks until a job calls the Abort() method. + * @arg threadCount Number of threads to process jobs. + * @arg userData Common user data pointer available in all Jobs. + */ + bool Process(unsigned int threadCount, void* userData = nullptr); + + /** + * Number of worker threads passed to Process(). + * Only valid during Process(). + */ + unsigned int ThreadCount() const { return ThreadCount_; } + + /** + * User data reference passed to Process(). + * Only valid during Process(). + */ + void* UserData() const { return UserData_; } + + // -- Job processing interface + + /** + * @brief Clears the job queue and aborts all worker threads. + * + * This method is thread safe and can be called from inside a job. + */ + void Abort(); + + /** + * @brief Push job to the queue. + * + * This method is thread safe and can be called from inside a job or before + * Process(). + */ + bool PushJob(JobHandleT&& jobHandle); + + /** + * @brief Push job to the queue + * + * This method is thread safe and can be called from inside a job or before + * Process(). + */ + template <class T, typename... Args> + bool EmplaceJob(Args&&... args) + { + return PushJob(cm::make_unique<T>(std::forward<Args>(args)...)); + } + +private: + void* UserData_ = nullptr; + unsigned int ThreadCount_ = 0; + std::unique_ptr<cmWorkerPoolInternal> Int_; +}; + +#endif diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index c18c256..3c75957 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -7,7 +7,7 @@ #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" -#include "cmQtAutoGeneratorMocUic.h" +#include "cmQtAutoMocUic.h" #include "cmQtAutoRcc.h" #include "cmRange.h" #include "cmState.h" @@ -1018,7 +1018,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args) #ifdef CMAKE_BUILD_WITH_CMAKE if ((args[1] == "cmake_autogen") && (args.size() >= 4)) { - cmQtAutoGeneratorMocUic autoGen; + cmQtAutoMocUic autoGen; std::string const& infoDir = args[2]; std::string const& config = args[3]; return autoGen.Run(infoDir, config) ? 0 : 1; |