/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGenInitializer.h" #include #include #include #include #include #include #include // for basic_ios, istringstream #include #include #include #include #include #include #include #include #include #include #include #include "cmsys/SystemInformation.hxx" #include "cmAlgorithms.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmEvaluatedTargetProperty.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorExpressionDAGChecker.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmLinkItem.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmQtAutoGen.h" #include "cmQtAutoGenGlobalInitializer.h" #include "cmSourceFile.h" #include "cmSourceFileLocationKind.h" #include "cmSourceGroup.h" #include "cmStandardLevelResolver.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmValue.h" #include "cmake.h" namespace { unsigned int GetParallelCPUCount() { static unsigned int count = 0; // Detect only on the first call if (count == 0) { cmsys::SystemInformation info; info.RunCPUCheck(); count = cm::clamp(info.GetNumberOfPhysicalCPU(), 1u, cmQtAutoGen::ParallelMax); } return count; } std::string FileProjectRelativePath(cmMakefile* makefile, std::string const& fileName) { std::string res; { std::string pSource = cmSystemTools::RelativePath( makefile->GetCurrentSourceDirectory(), fileName); std::string pBinary = cmSystemTools::RelativePath( makefile->GetCurrentBinaryDirectory(), fileName); if (pSource.size() < pBinary.size()) { res = std::move(pSource); } else if (pBinary.size() < fileName.size()) { res = std::move(pBinary); } else { res = fileName; } } return res; } /** * Tests if targetDepend is a STATIC_LIBRARY and if any of its * recursive STATIC_LIBRARY dependencies depends on targetOrigin * (STATIC_LIBRARY cycle). */ bool StaticLibraryCycle(cmGeneratorTarget const* targetOrigin, cmGeneratorTarget const* targetDepend, std::string const& config) { bool cycle = false; if ((targetOrigin->GetType() == cmStateEnums::STATIC_LIBRARY) && (targetDepend->GetType() == cmStateEnums::STATIC_LIBRARY)) { std::set knownLibs; std::deque testLibs; // Insert initial static_library dependency knownLibs.insert(targetDepend); testLibs.push_back(targetDepend); while (!testLibs.empty()) { cmGeneratorTarget const* testTarget = testLibs.front(); testLibs.pop_front(); // Check if the test target is the origin target (cycle) if (testTarget == targetOrigin) { cycle = true; break; } // Collect all static_library dependencies from the test target cmLinkImplementationLibraries const* libs = testTarget->GetLinkImplementationLibraries( config, cmGeneratorTarget::LinkInterfaceFor::Link); if (libs) { for (cmLinkItem const& item : libs->Libraries) { cmGeneratorTarget const* depTarget = item.Target; if (depTarget && (depTarget->GetType() == cmStateEnums::STATIC_LIBRARY) && knownLibs.insert(depTarget).second) { testLibs.push_back(depTarget); } } } } } return cycle; } /** Sanitizes file search paths. */ class SearchPathSanitizer { public: SearchPathSanitizer(cmMakefile* makefile) : SourcePath_(makefile->GetCurrentSourceDirectory()) { } std::vector operator()( std::vector const& paths) const; private: std::string SourcePath_; }; std::vector SearchPathSanitizer::operator()( std::vector const& paths) const { std::vector res; res.reserve(paths.size()); for (std::string const& srcPath : paths) { // Collapse relative paths std::string path = cmSystemTools::CollapseFullPath(srcPath, this->SourcePath_); // Remove suffix slashes while (cmHasSuffix(path, '/')) { path.pop_back(); } // Accept only non empty paths if (!path.empty()) { res.emplace_back(std::move(path)); } } return res; } /** @brief Writes a CMake info file. */ class InfoWriter { public: // -- Single value void Set(std::string const& key, std::string const& value) { this->Value_[key] = value; } void SetConfig(std::string const& key, cmQtAutoGenInitializer::ConfigString const& cfgStr); void SetBool(std::string const& key, bool value) { this->Value_[key] = value; } void SetUInt(std::string const& key, unsigned int value) { this->Value_[key] = value; } // -- Array utility template static bool MakeArray(Json::Value& jval, CONT const& container); template static void MakeStringArray(Json::Value& jval, CONT const& container); // -- Array value template void SetArray(std::string const& key, CONT const& container); template void SetConfigArray( std::string const& key, cmQtAutoGenInitializer::ConfigStrings const& cfgStr); // -- Array of arrays template void SetArrayArray(std::string const& key, CONT const& container, FUNC func); // -- Save to json file bool Save(std::string const& filename); private: Json::Value Value_; }; void InfoWriter::SetConfig(std::string const& key, cmQtAutoGenInitializer::ConfigString const& cfgStr) { this->Set(key, cfgStr.Default); for (auto const& item : cfgStr.Config) { this->Set(cmStrCat(key, '_', item.first), item.second); } } template bool InfoWriter::MakeArray(Json::Value& jval, CONT const& container) { jval = Json::arrayValue; std::size_t const listSize = cm::size(container); if (listSize == 0) { return false; } jval.resize(static_cast(listSize)); return true; } template void InfoWriter::MakeStringArray(Json::Value& jval, CONT const& container) { if (MakeArray(jval, container)) { Json::ArrayIndex ii = 0; for (std::string const& item : container) { jval[ii++] = item; } } } template void InfoWriter::SetArray(std::string const& key, CONT const& container) { MakeStringArray(this->Value_[key], container); } template void InfoWriter::SetArrayArray(std::string const& key, CONT const& container, FUNC func) { Json::Value& jval = this->Value_[key]; if (MakeArray(jval, container)) { Json::ArrayIndex ii = 0; for (auto const& citem : container) { Json::Value& aval = jval[ii++]; aval = Json::arrayValue; func(aval, citem); } } } template void InfoWriter::SetConfigArray( std::string const& key, cmQtAutoGenInitializer::ConfigStrings const& cfgStr) { this->SetArray(key, cfgStr.Default); for (auto const& item : cfgStr.Config) { this->SetArray(cmStrCat(key, '_', item.first), item.second); } } bool InfoWriter::Save(std::string const& filename) { cmGeneratedFileStream fileStream; fileStream.SetCopyIfDifferent(true); fileStream.Open(filename, false, true); if (!fileStream) { return false; } Json::StyledStreamWriter jsonWriter; try { jsonWriter.write(fileStream, this->Value_); } catch (...) { return false; } return fileStream.Close(); } void AddAutogenExecutableToDependencies( cmQtAutoGenInitializer::GenVarsT const& genVars, std::vector& dependencies) { if (genVars.ExecutableTarget != nullptr) { dependencies.push_back(genVars.ExecutableTarget->Target->GetName()); } else if (!genVars.Executable.empty()) { dependencies.push_back(genVars.Executable); } } } // End of unnamed namespace cmQtAutoGenInitializer::cmQtAutoGenInitializer( cmQtAutoGenGlobalInitializer* globalInitializer, cmGeneratorTarget* genTarget, IntegerVersion const& qtVersion, bool mocEnabled, bool uicEnabled, bool rccEnabled, bool globalAutogenTarget, bool globalAutoRccTarget) : GlobalInitializer(globalInitializer) , GenTarget(genTarget) , GlobalGen(genTarget->GetGlobalGenerator()) , LocalGen(genTarget->GetLocalGenerator()) , Makefile(genTarget->Makefile) , PathCheckSum(genTarget->Makefile) , QtVersion(qtVersion) { this->AutogenTarget.GlobalTarget = globalAutogenTarget; this->Moc.Enabled = mocEnabled; this->Uic.Enabled = uicEnabled; this->Rcc.Enabled = rccEnabled; this->Rcc.GlobalTarget = globalAutoRccTarget; this->CrossConfig = !this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty(); } bool cmQtAutoGenInitializer::InitCustomTargets() { // Configurations this->MultiConfig = this->GlobalGen->IsMultiConfig(); this->ConfigDefault = this->Makefile->GetDefaultConfiguration(); this->ConfigsList = this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); // Verbosity { std::string const def = this->Makefile->GetSafeDefinition("CMAKE_AUTOGEN_VERBOSE"); if (!def.empty()) { unsigned long iVerb = 0; if (cmStrToULong(def, &iVerb)) { // Numeric verbosity this->Verbosity = static_cast(iVerb); } else { // Non numeric verbosity if (cmIsOn(def)) { this->Verbosity = 1; } } } } // Targets FOLDER { cmValue folder = this->Makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER"); if (!folder) { folder = this->Makefile->GetState()->GetGlobalProperty( "AUTOGEN_TARGETS_FOLDER"); } // Inherit FOLDER property from target (#13688) if (!folder) { folder = this->GenTarget->GetProperty("FOLDER"); } if (folder) { this->TargetsFolder = *folder; } } // Check status of policy CMP0071 regarding handling of GENERATED files switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0071)) { case cmPolicies::WARN: // Ignore GENERATED files but warn this->CMP0071Warn = true; CM_FALLTHROUGH; case cmPolicies::OLD: // Ignore GENERATED files break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: // Process GENERATED files this->CMP0071Accept = true; break; } // Check status of policy CMP0100 regarding handling of .hh headers switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0100)) { case cmPolicies::WARN: // Ignore but .hh files but warn this->CMP0100Warn = true; CM_FALLTHROUGH; case cmPolicies::OLD: // Ignore .hh files break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: // Process .hh file this->CMP0100Accept = true; break; } // Common directories std::string relativeBuildDir; { // Collapsed current binary directory std::string const cbd = cmSystemTools::CollapseFullPath( std::string(), this->Makefile->GetCurrentBinaryDirectory()); // Info directory this->Dir.Info = cmStrCat(cbd, "/CMakeFiles/", this->GenTarget->GetName(), "_autogen.dir"); cmSystemTools::ConvertToUnixSlashes(this->Dir.Info); // Build directory this->Dir.Build = this->GenTarget->GetSafeProperty("AUTOGEN_BUILD_DIR"); if (this->Dir.Build.empty()) { this->Dir.Build = cmStrCat(cbd, '/', this->GenTarget->GetName(), "_autogen"); } cmSystemTools::ConvertToUnixSlashes(this->Dir.Build); this->Dir.RelativeBuild = cmSystemTools::RelativePath(cbd, this->Dir.Build); // Cleanup build directory this->AddCleanFile(this->Dir.Build); // Working directory this->Dir.Work = cbd; cmSystemTools::ConvertToUnixSlashes(this->Dir.Work); // Include directory this->ConfigFileNamesAndGenex(this->Dir.Include, this->Dir.IncludeGenExp, cmStrCat(this->Dir.Build, "/include"), ""); } // Moc, Uic and _autogen target settings if (this->MocOrUicEnabled()) { // Init moc specific settings if (this->Moc.Enabled && !this->InitMoc()) { return false; } // Init uic specific settings if (this->Uic.Enabled && !this->InitUic()) { return false; } // Autogen target name this->AutogenTarget.Name = cmStrCat(this->GenTarget->GetName(), "_autogen"); // Autogen target parallel processing { using ParallelType = decltype(this->AutogenTarget.Parallel); unsigned long propInt = 0; std::string const& prop = this->GenTarget->GetSafeProperty("AUTOGEN_PARALLEL"); if (prop.empty() || (prop == "AUTO")) { // Autodetect number of CPUs this->AutogenTarget.Parallel = GetParallelCPUCount(); } else if (cmStrToULong(prop, &propInt) && propInt > 0 && propInt <= std::numeric_limits::max()) { this->AutogenTarget.Parallel = static_cast(propInt); } else { // Warn the project author that AUTOGEN_PARALLEL is not valid. this->Makefile->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat("AUTOGEN_PARALLEL=\"", prop, "\" for target \"", this->GenTarget->GetName(), "\" is not valid. Using AUTOGEN_PARALLEL=1")); this->AutogenTarget.Parallel = 1; } } #ifdef _WIN32 { const auto& value = this->GenTarget->GetProperty("AUTOGEN_COMMAND_LINE_LENGTH_MAX"); if (value.IsSet()) { using maxCommandLineLengthType = decltype(this->AutogenTarget.MaxCommandLineLength); unsigned long propInt = 0; if (cmStrToULong(value, &propInt) && propInt > 0 && propInt <= std::numeric_limits::max()) { this->AutogenTarget.MaxCommandLineLength = static_cast(propInt); } else { // Warn the project author that AUTOGEN_PARALLEL is not valid. this->Makefile->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat("AUTOGEN_COMMAND_LINE_LENGTH_MAX=\"", *value, "\" for target \"", this->GenTarget->GetName(), "\" is not valid. Using no limit for " "AUTOGEN_COMMAND_LINE_LENGTH_MAX")); this->AutogenTarget.MaxCommandLineLength = std::numeric_limits::max(); } } else { // Actually 32767 (see // https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) but // we allow for a small margin this->AutogenTarget.MaxCommandLineLength = 32000; } } #endif // Autogen target info and settings files { // Info file this->AutogenTarget.InfoFile = cmStrCat(this->Dir.Info, "/AutogenInfo.json"); // Used settings file this->ConfigFileNames(this->AutogenTarget.SettingsFile, cmStrCat(this->Dir.Info, "/AutogenUsed"), ".txt"); this->ConfigFileClean(this->AutogenTarget.SettingsFile); // Parse cache file this->ConfigFileNames(this->AutogenTarget.ParseCacheFile, cmStrCat(this->Dir.Info, "/ParseCache"), ".txt"); this->ConfigFileClean(this->AutogenTarget.ParseCacheFile); } // Autogen target: Compute user defined dependencies { this->AutogenTarget.DependOrigin = this->GenTarget->GetPropertyAsBool("AUTOGEN_ORIGIN_DEPENDS"); std::string const& deps = this->GenTarget->GetSafeProperty("AUTOGEN_TARGET_DEPENDS"); if (!deps.empty()) { for (auto const& depName : cmList{ deps }) { // Allow target and file dependencies auto* depTarget = this->Makefile->FindTargetToUse(depName); if (depTarget) { this->AutogenTarget.DependTargets.insert(depTarget); } else { this->AutogenTarget.DependFiles.insert(depName); } } } } if (this->Moc.Enabled) { // Path prefix if (cmIsOn(this->GenTarget->GetProperty("AUTOMOC_PATH_PREFIX"))) { this->Moc.PathPrefix = true; } // CMAKE_AUTOMOC_RELAXED_MODE if (this->Makefile->IsOn("CMAKE_AUTOMOC_RELAXED_MODE")) { this->Moc.RelaxedMode = true; this->Makefile->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat("AUTOMOC: CMAKE_AUTOMOC_RELAXED_MODE is " "deprecated an will be removed in the future. Consider " "disabling it and converting the target ", this->GenTarget->GetName(), " to regular mode.")); } // Options cmExpandList(this->GenTarget->GetSafeProperty("AUTOMOC_MOC_OPTIONS"), this->Moc.Options); // Filters cmExpandList(this->GenTarget->GetSafeProperty("AUTOMOC_MACRO_NAMES"), this->Moc.MacroNames); this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames), this->Moc.MacroNames.end()); { cmList const filterList = { this->GenTarget->GetSafeProperty( "AUTOMOC_DEPEND_FILTERS") }; if ((filterList.size() % 2) != 0) { cmSystemTools::Error( cmStrCat("AutoMoc: AUTOMOC_DEPEND_FILTERS predefs size ", filterList.size(), " is not a multiple of 2.")); return false; } this->Moc.DependFilters.reserve(1 + (filterList.size() / 2)); this->Moc.DependFilters.emplace_back( "Q_PLUGIN_METADATA", "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\(" "[^\\)]*FILE[ \t]*\"([^\"]+)\""); for (cmList::size_type ii = 0; ii != filterList.size(); ii += 2) { this->Moc.DependFilters.emplace_back(filterList[ii], filterList[ii + 1]); } } } } // Init rcc specific settings if (this->Rcc.Enabled && !this->InitRcc()) { return false; } // Add autogen include directory to the origin target INCLUDE_DIRECTORIES if (this->MocOrUicEnabled() || (this->Rcc.Enabled && this->MultiConfig)) { auto addBefore = false; auto const& value = this->GenTarget->GetProperty("AUTOGEN_USE_SYSTEM_INCLUDE"); if (value.IsSet()) { if (cmIsOn(value)) { this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp, "CXX"); } else { addBefore = true; } } else { switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0151)) { case cmPolicies::WARN: case cmPolicies::OLD: addBefore = true; break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp, "CXX"); break; } } this->GenTarget->AddIncludeDirectory(this->Dir.IncludeGenExp, addBefore); } // Scan files if (!this->InitScanFiles()) { return false; } // Create autogen target if (this->MocOrUicEnabled() && !this->InitAutogenTarget()) { return false; } // Create rcc targets if (this->Rcc.Enabled && !this->InitRccTargets()) { return false; } return true; } bool cmQtAutoGenInitializer::InitMoc() { // Mocs compilation file if (this->GlobalGen->IsXcode()) { // XXX(xcode-per-cfg-src): Drop this Xcode-specific code path // when the Xcode generator supports per-config sources. this->Moc.CompilationFile.Default = cmStrCat(this->Dir.Build, "/mocs_compilation.cpp"); this->Moc.CompilationFileGenex = this->Moc.CompilationFile.Default; } else { this->ConfigFileNamesAndGenex( this->Moc.CompilationFile, this->Moc.CompilationFileGenex, cmStrCat(this->Dir.Build, "/mocs_compilation"_s), ".cpp"_s); } // Moc predefs if (this->GenTarget->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") && (this->QtVersion >= IntegerVersion(5, 8))) { // Command cmList::assign( this->Moc.PredefsCmd, this->Makefile->GetDefinition("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND")); // Header if (!this->Moc.PredefsCmd.empty()) { this->ConfigFileNames(this->Moc.PredefsFile, cmStrCat(this->Dir.Build, "/moc_predefs"), ".h"); } } // Moc includes { SearchPathSanitizer const sanitizer(this->Makefile); auto getDirs = [this, &sanitizer](std::string const& cfg) -> std::vector { // Get the include dirs for this target, without stripping the implicit // include dirs off, see issue #13667. std::vector dirs; bool const appendImplicit = (this->QtVersion.Major >= 5); this->LocalGen->GetIncludeDirectoriesImplicit( dirs, this->GenTarget, "CXX", cfg, false, appendImplicit); return sanitizer(dirs); }; // Other configuration settings if (this->MultiConfig) { for (std::string const& cfg : this->ConfigsList) { std::vector dirs = getDirs(cfg); if (dirs == this->Moc.Includes.Default) { continue; } this->Moc.Includes.Config[cfg] = std::move(dirs); } } else { // Default configuration include directories this->Moc.Includes.Default = getDirs(this->ConfigDefault); } } // Moc compile definitions { auto getDefs = [this](std::string const& cfg) -> std::set { std::set defines; this->LocalGen->GetTargetDefines(this->GenTarget, cfg, "CXX", defines); if (this->Moc.PredefsCmd.empty() && this->Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == "Windows") { // Add WIN32 definition if we don't have a moc_predefs.h defines.insert("WIN32"); } return defines; }; // Other configuration defines if (this->MultiConfig) { for (std::string const& cfg : this->ConfigsList) { std::set defines = getDefs(cfg); if (defines == this->Moc.Defines.Default) { continue; } this->Moc.Defines.Config[cfg] = std::move(defines); } } else { // Default configuration defines this->Moc.Defines.Default = getDefs(this->ConfigDefault); } } // Moc executable { if (!this->GetQtExecutable(this->Moc, "moc", false)) { return false; } // Let the _autogen target depend on the moc executable if (this->Moc.ExecutableTarget) { this->AutogenTarget.DependTargets.insert( this->Moc.ExecutableTarget->Target); } } return true; } bool cmQtAutoGenInitializer::InitUic() { // Uic search paths { std::string const& usp = this->GenTarget->GetSafeProperty("AUTOUIC_SEARCH_PATHS"); if (!usp.empty()) { this->Uic.SearchPaths = SearchPathSanitizer(this->Makefile)(cmList{ usp }); } } // Uic target options { auto getOpts = [this](std::string const& cfg) -> std::vector { std::vector opts; this->GenTarget->GetAutoUicOptions(opts, cfg); return opts; }; // Default options this->Uic.Options.Default = getOpts(this->ConfigDefault); // Configuration specific options if (this->MultiConfig) { for (std::string const& cfg : this->ConfigsList) { std::vector options = getOpts(cfg); if (options == this->Uic.Options.Default) { continue; } this->Uic.Options.Config[cfg] = std::move(options); } } } // Uic executable { if (!this->GetQtExecutable(this->Uic, "uic", true)) { return false; } // Let the _autogen target depend on the uic executable if (this->Uic.ExecutableTarget) { this->AutogenTarget.DependTargets.insert( this->Uic.ExecutableTarget->Target); } } return true; } bool cmQtAutoGenInitializer::InitRcc() { // Rcc executable { if (!this->GetQtExecutable(this->Rcc, "rcc", false)) { return false; } // Evaluate test output on demand CompilerFeatures& features = *this->Rcc.ExecutableFeatures; if (!features.Evaluated) { // Look for list options if (this->QtVersion.Major == 5 || this->QtVersion.Major == 6) { if (features.HelpOutput.find("--list") != std::string::npos) { features.ListOptions.emplace_back("--list"); } else if (features.HelpOutput.find("-list") != std::string::npos) { features.ListOptions.emplace_back("-list"); } } // Evaluation finished features.Evaluated = true; } } return true; } bool cmQtAutoGenInitializer::InitScanFiles() { cmake const* cm = this->Makefile->GetCMakeInstance(); auto const& kw = this->GlobalInitializer->kw(); auto makeMUFile = [this, &kw](cmSourceFile* sf, std::string const& fullPath, std::vector const& configs, bool muIt) -> MUFileHandle { MUFileHandle muf = cm::make_unique(); muf->FullPath = fullPath; muf->SF = sf; if (!configs.empty() && configs.size() != this->ConfigsList.size()) { muf->Configs = configs; } muf->Generated = sf->GetIsGenerated(); bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN); muf->SkipMoc = this->Moc.Enabled && (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOMOC)); muf->SkipUic = this->Uic.Enabled && (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC)); if (muIt) { muf->MocIt = this->Moc.Enabled && !muf->SkipMoc; muf->UicIt = this->Uic.Enabled && !muf->SkipUic; } return muf; }; auto addMUHeader = [this](MUFileHandle&& muf, cm::string_view extension) { cmSourceFile* sf = muf->SF; const bool muIt = (muf->MocIt || muf->UicIt); if (this->CMP0100Accept || (extension != "hh")) { // Accept if (muIt && muf->Generated) { this->AutogenTarget.FilesGenerated.emplace_back(muf.get()); } this->AutogenTarget.Headers.emplace(sf, std::move(muf)); } else if (muIt && this->CMP0100Warn) { // Store file for warning message this->AutogenTarget.CMP0100HeadersWarn.push_back(sf); } }; auto addMUSource = [this](MUFileHandle&& muf) { if ((muf->MocIt || muf->UicIt) && muf->Generated) { this->AutogenTarget.FilesGenerated.emplace_back(muf.get()); } this->AutogenTarget.Sources.emplace(muf->SF, std::move(muf)); }; // Scan through target files { // Scan through target files for (cmGeneratorTarget::AllConfigSource const& acs : this->GenTarget->GetAllConfigSources()) { std::string const& fullPath = acs.Source->GetFullPath(); std::string const& extLower = cmSystemTools::LowerCase(acs.Source->GetExtension()); // Register files that will be scanned by moc or uic if (this->MocOrUicEnabled()) { if (cm->IsAHeaderExtension(extLower)) { addMUHeader(makeMUFile(acs.Source, fullPath, acs.Configs, true), extLower); } else if (cm->IsACLikeSourceExtension(extLower)) { addMUSource(makeMUFile(acs.Source, fullPath, acs.Configs, true)); } } // Register rcc enabled files if (this->Rcc.Enabled) { if ((extLower == kw.qrc) && !acs.Source->GetPropertyAsBool(kw.SKIP_AUTOGEN) && !acs.Source->GetPropertyAsBool(kw.SKIP_AUTORCC)) { // Register qrc file Qrc qrc; qrc.QrcFile = fullPath; qrc.QrcName = cmSystemTools::GetFilenameWithoutLastExtension(qrc.QrcFile); qrc.Generated = acs.Source->GetIsGenerated(); // RCC options { std::string const& opts = acs.Source->GetSafeProperty(kw.AUTORCC_OPTIONS); if (!opts.empty()) { cmExpandList(opts, qrc.Options); } } this->Rcc.Qrcs.push_back(std::move(qrc)); } } } } // cmGeneratorTarget::GetAllConfigSources computes the target's // sources meta data cache. Clear it so that OBJECT library targets that // are AUTOGEN initialized after this target get their added // mocs_compilation.cpp source acknowledged by this target. this->GenTarget->ClearSourcesCache(); // For source files find additional headers and private headers if (this->MocOrUicEnabled()) { // Header search suffixes and extensions static std::initializer_list const suffixes{ "", "_p" }; auto const& exts = cm->GetHeaderExtensions(); // Scan through sources for (auto const& pair : this->AutogenTarget.Sources) { MUFile const& muf = *pair.second; if (muf.MocIt || muf.UicIt) { // Search for the default header file and a private header std::string const& srcFullPath = muf.SF->ResolveFullPath(); std::string const basePath = cmStrCat( cmQtAutoGen::SubDirPrefix(srcFullPath), cmSystemTools::GetFilenameWithoutLastExtension(srcFullPath)); for (auto const& suffix : suffixes) { std::string const suffixedPath = cmStrCat(basePath, suffix); for (auto const& ext : exts) { std::string const fullPath = cmStrCat(suffixedPath, '.', ext); auto constexpr locationKind = cmSourceFileLocationKind::Known; cmSourceFile* sf = this->Makefile->GetSource(fullPath, locationKind); if (sf) { // Check if we know about this header already if (cm::contains(this->AutogenTarget.Headers, sf)) { continue; } // We only accept not-GENERATED files that do exist. if (!sf->GetIsGenerated() && !cmSystemTools::FileExists(fullPath)) { continue; } } else if (cmSystemTools::FileExists(fullPath)) { // Create a new source file for the existing file sf = this->Makefile->CreateSource(fullPath, false, locationKind); } if (sf) { auto eMuf = makeMUFile(sf, fullPath, muf.Configs, true); // Only process moc/uic when the parent is processed as well if (!muf.MocIt) { eMuf->MocIt = false; } if (!muf.UicIt) { eMuf->UicIt = false; } addMUHeader(std::move(eMuf), ext); } } } } } } // Scan through all source files in the makefile to extract moc and uic // parameters. Historically we support non target source file parameters. // The reason is that their file names might be discovered from source files // at generation time. if (this->MocOrUicEnabled()) { for (const auto& sf : this->Makefile->GetSourceFiles()) { // sf->GetExtension() is only valid after sf->ResolveFullPath() ... // Since we're iterating over source files that might be not in the // target we need to check for path errors (not existing files). std::string pathError; std::string const& fullPath = sf->ResolveFullPath(&pathError); if (!pathError.empty() || fullPath.empty()) { continue; } std::string const& extLower = cmSystemTools::LowerCase(sf->GetExtension()); if (cm->IsAHeaderExtension(extLower)) { if (!cm::contains(this->AutogenTarget.Headers, sf.get())) { auto muf = makeMUFile(sf.get(), fullPath, {}, false); if (muf->SkipMoc || muf->SkipUic) { addMUHeader(std::move(muf), extLower); } } } else if (cm->IsACLikeSourceExtension(extLower)) { if (!cm::contains(this->AutogenTarget.Sources, sf.get())) { auto muf = makeMUFile(sf.get(), fullPath, {}, false); if (muf->SkipMoc || muf->SkipUic) { addMUSource(std::move(muf)); } } } else if (this->Uic.Enabled && (extLower == kw.ui)) { // .ui file bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN); bool const skipUic = (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC)); if (!skipUic) { // Check if the .ui file has uic options std::string const uicOpts = sf->GetSafeProperty(kw.AUTOUIC_OPTIONS); if (uicOpts.empty()) { this->Uic.UiFilesNoOptions.emplace_back(fullPath); } else { this->Uic.UiFilesWithOptions.emplace_back( fullPath, std::move(cmList{ uicOpts }.data())); } auto uiHeaderRelativePath = cmSystemTools::RelativePath( this->LocalGen->GetCurrentSourceDirectory(), cmSystemTools::GetFilenamePath(fullPath)); // Avoid creating a path containing adjacent slashes if (!uiHeaderRelativePath.empty() && uiHeaderRelativePath.back() != '/') { uiHeaderRelativePath += '/'; } auto uiHeaderFilePath = cmStrCat( '/', uiHeaderRelativePath, "ui_"_s, cmSystemTools::GetFilenameWithoutLastExtension(fullPath), ".h"_s); ConfigString uiHeader; std::string uiHeaderGenex; this->ConfigFileNamesAndGenex( uiHeader, uiHeaderGenex, cmStrCat(this->Dir.Build, "/include"_s), uiHeaderFilePath); this->Uic.UiHeaders.emplace_back(uiHeader, uiHeaderGenex); } else { // Register skipped .ui file this->Uic.SkipUi.insert(fullPath); } } } } // Process GENERATED sources and headers if (this->MocOrUicEnabled() && !this->AutogenTarget.FilesGenerated.empty()) { if (this->CMP0071Accept) { // Let the autogen target depend on the GENERATED files if (this->MultiConfig && !this->CrossConfig) { for (MUFile const* muf : this->AutogenTarget.FilesGenerated) { if (muf->Configs.empty()) { this->AutogenTarget.DependFiles.insert(muf->FullPath); } else { for (size_t ci : muf->Configs) { std::string const& config = this->ConfigsList[ci]; std::string const& pathWithConfig = cmStrCat("$<$:", muf->FullPath, '>'); this->AutogenTarget.DependFiles.insert(pathWithConfig); } } } } else { for (MUFile const* muf : this->AutogenTarget.FilesGenerated) { this->AutogenTarget.DependFiles.insert(muf->FullPath); } } } else if (this->CMP0071Warn) { cm::string_view property; if (this->Moc.Enabled && this->Uic.Enabled) { property = "SKIP_AUTOGEN"; } else if (this->Moc.Enabled) { property = "SKIP_AUTOMOC"; } else if (this->Uic.Enabled) { property = "SKIP_AUTOUIC"; } std::string files; for (MUFile const* muf : this->AutogenTarget.FilesGenerated) { files += cmStrCat(" ", Quoted(muf->FullPath), '\n'); } this->Makefile->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat( cmPolicies::GetPolicyWarning(cmPolicies::CMP0071), '\n', "For compatibility, CMake is excluding the GENERATED source " "file(s):\n", files, "from processing by ", cmQtAutoGen::Tools(this->Moc.Enabled, this->Uic.Enabled, false), ". If any of the files should be processed, set CMP0071 to NEW. " "If any of the files should not be processed, " "explicitly exclude them by setting the source file property ", property, ":\n set_property(SOURCE file.h PROPERTY ", property, " ON)\n")); } } // Generate CMP0100 warning if (this->MocOrUicEnabled() && !this->AutogenTarget.CMP0100HeadersWarn.empty()) { cm::string_view property; if (this->Moc.Enabled && this->Uic.Enabled) { property = "SKIP_AUTOGEN"; } else if (this->Moc.Enabled) { property = "SKIP_AUTOMOC"; } else if (this->Uic.Enabled) { property = "SKIP_AUTOUIC"; } std::string files; for (cmSourceFile const* sf : this->AutogenTarget.CMP0100HeadersWarn) { files += cmStrCat(" ", Quoted(sf->GetFullPath()), '\n'); } this->Makefile->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat( cmPolicies::GetPolicyWarning(cmPolicies::CMP0100), '\n', "For compatibility, CMake is excluding the header file(s):\n", files, "from processing by ", cmQtAutoGen::Tools(this->Moc.Enabled, this->Uic.Enabled, false), ". If any of the files should be processed, set CMP0100 to NEW. " "If any of the files should not be processed, " "explicitly exclude them by setting the source file property ", property, ":\n set_property(SOURCE file.hh PROPERTY ", property, " ON)\n")); } // Process qrc files if (!this->Rcc.Qrcs.empty()) { const bool modernQt = (this->QtVersion.Major >= 5); // Target rcc options cmList const optionsTarget{ this->GenTarget->GetSafeProperty( kw.AUTORCC_OPTIONS) }; // Check if file name is unique for (Qrc& qrc : this->Rcc.Qrcs) { qrc.Unique = true; for (Qrc const& qrc2 : this->Rcc.Qrcs) { if ((&qrc != &qrc2) && (qrc.QrcName == qrc2.QrcName)) { qrc.Unique = false; break; } } } // Path checksum and file names for (Qrc& qrc : this->Rcc.Qrcs) { // Path checksum qrc.QrcPathChecksum = this->PathCheckSum.getPart(qrc.QrcFile); // Output file name qrc.OutputFile = cmStrCat(this->Dir.Build, '/', qrc.QrcPathChecksum, "/qrc_", qrc.QrcName, ".cpp"); std::string const base = cmStrCat(this->Dir.Info, "/AutoRcc_", qrc.QrcName, '_', qrc.QrcPathChecksum); qrc.LockFile = cmStrCat(base, "_Lock.lock"); qrc.InfoFile = cmStrCat(base, "_Info.json"); this->ConfigFileNames(qrc.SettingsFile, cmStrCat(base, "_Used"), ".txt"); } // rcc options for (Qrc& qrc : this->Rcc.Qrcs) { // Target options std::vector opts = optionsTarget; // Merge computed "-name XYZ" option { std::string name = qrc.QrcName; // Replace '-' with '_'. The former is not valid for symbol names. std::replace(name.begin(), name.end(), '-', '_'); if (!qrc.Unique) { name += cmStrCat('_', qrc.QrcPathChecksum); } std::vector nameOpts; nameOpts.emplace_back("-name"); nameOpts.emplace_back(std::move(name)); RccMergeOptions(opts, nameOpts, modernQt); } // Merge file option RccMergeOptions(opts, qrc.Options, modernQt); qrc.Options = std::move(opts); } // rcc resources for (Qrc& qrc : this->Rcc.Qrcs) { if (!qrc.Generated) { std::string error; RccLister const lister(this->Rcc.Executable, this->Rcc.ExecutableFeatures->ListOptions); if (!lister.list(qrc.QrcFile, qrc.Resources, error)) { cmSystemTools::Error(error); return false; } } } } return true; } bool cmQtAutoGenInitializer::InitAutogenTarget() { // Register info file as generated by CMake this->Makefile->AddCMakeOutputFile(this->AutogenTarget.InfoFile); // Determine whether to use a depfile for the AUTOGEN target. bool const useDepfile = [this]() -> bool { auto const& gen = this->GlobalGen->GetName(); return this->QtVersion >= IntegerVersion(5, 15) && (gen.find("Ninja") != std::string::npos || gen.find("Make") != std::string::npos); }(); // Files provided by the autogen target std::vector autogenByproducts; std::vector timestampByproducts; if (this->Moc.Enabled) { this->AddGeneratedSource(this->Moc.CompilationFile, this->Moc, true); if (useDepfile) { if (this->MultiConfig && this->CrossConfig && this->GlobalGen->GetName().find("Ninja") != std::string::npos) { // Make all mocs_compilation_.cpp files byproducts of the // ${target}_autogen/timestamp custom command. // We cannot just use Moc.CompilationFileGenex here, because that // custom command runs cmake_autogen for each configuration. for (const auto& p : this->Moc.CompilationFile.Config) { timestampByproducts.push_back(p.second); } } else { timestampByproducts.push_back(this->Moc.CompilationFileGenex); } } else { autogenByproducts.push_back(this->Moc.CompilationFileGenex); } } if (this->Uic.Enabled) { for (const auto& file : this->Uic.UiHeaders) { this->AddGeneratedSource(file.first, this->Uic); autogenByproducts.push_back(file.second); } } // Compose target comment std::string autogenComment; { std::string tools; if (this->Moc.Enabled) { tools += "MOC"; } if (this->Uic.Enabled) { if (!tools.empty()) { tools += " and "; } tools += "UIC"; } autogenComment = cmStrCat("Automatic ", tools, " for target ", this->GenTarget->GetName()); } // Compose command lines // FIXME: Take advantage of our per-config mocs_compilation_$.cpp // instead of fiddling with the include directories std::vector configs; this->GlobalGen->GetQtAutoGenConfigs(configs); bool constexpr stdPipesUTF8 = true; cmCustomCommandLines commandLines; if (!this->CrossConfig) { std::string autogenInfoFileConfig; if (this->MultiConfig) { autogenInfoFileConfig = "$"; } else { autogenInfoFileConfig = configs[0]; } commandLines.push_back(cmMakeCommandLine( { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen", this->AutogenTarget.InfoFile, autogenInfoFileConfig })); } else { for (auto const& config : configs) { commandLines.push_back(cmMakeCommandLine( { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen", this->AutogenTarget.InfoFile, config })); } } // Use PRE_BUILD on demand bool usePRE_BUILD = false; if (this->GlobalGen->GetName().find("Visual Studio") != std::string::npos) { // Under VS use a PRE_BUILD event instead of a separate target to // reduce the number of targets loaded into the IDE. // This also works around a VS 11 bug that may skip updating the target: // https://connect.microsoft.com/VisualStudio/feedback/details/769495 usePRE_BUILD = true; } // Disable PRE_BUILD in some cases if (usePRE_BUILD) { // Cannot use PRE_BUILD with file depends if (!this->AutogenTarget.DependFiles.empty()) { usePRE_BUILD = false; } // Cannot use PRE_BUILD when a global autogen target is in place if (this->AutogenTarget.GlobalTarget) { usePRE_BUILD = false; } } // Create the autogen target/command if (usePRE_BUILD) { // Add additional autogen target dependencies to origin target for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) { this->GenTarget->Target->AddUtility(depTarget->GetName(), false, this->Makefile); } if (!this->Uic.UiFilesNoOptions.empty() || !this->Uic.UiFilesWithOptions.empty()) { // Add a generated timestamp file ConfigString timestampFile; std::string timestampFileGenex; ConfigFileNamesAndGenex(timestampFile, timestampFileGenex, cmStrCat(this->Dir.Build, "/autouic"_s), ".stamp"_s); this->AddGeneratedSource(timestampFile, this->Uic); // Add a step in the pre-build command to touch the timestamp file commandLines.push_back( cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", "touch", timestampFileGenex })); // UIC needs to be re-run if any of the known UI files change or the // executable itself has been updated auto uicDependencies = this->Uic.UiFilesNoOptions; for (auto const& uiFile : this->Uic.UiFilesWithOptions) { uicDependencies.push_back(uiFile.first); } AddAutogenExecutableToDependencies(this->Uic, uicDependencies); // Add a rule file to cause the target to build if a dependency has // changed, which will trigger the pre-build command to run autogen auto cc = cm::make_unique(); cc->SetOutputs(timestampFileGenex); cc->SetDepends(uicDependencies); cc->SetComment(""); cc->SetWorkingDirectory(this->Dir.Work.c_str()); cc->SetEscapeOldStyle(false); cc->SetStdPipesUTF8(stdPipesUTF8); this->LocalGen->AddCustomCommandToOutput(std::move(cc)); } // Add the pre-build command directly to bypass the OBJECT_LIBRARY // rejection in cmMakefile::AddCustomCommandToTarget because we know // PRE_BUILD will work for an OBJECT_LIBRARY in this specific case. // // PRE_BUILD does not support file dependencies! cmCustomCommand cc; cc.SetByproducts(autogenByproducts); cc.SetCommandLines(commandLines); cc.SetComment(autogenComment.c_str()); cc.SetBacktrace(this->Makefile->GetBacktrace()); cc.SetWorkingDirectory(this->Dir.Work.c_str()); cc.SetStdPipesUTF8(stdPipesUTF8); cc.SetEscapeOldStyle(false); cc.SetEscapeAllowMakeVars(true); this->GenTarget->Target->AddPreBuildCommand(std::move(cc)); } else { // Add link library target dependencies to the autogen target // dependencies if (this->AutogenTarget.DependOrigin) { // add_dependencies/addUtility do not support generator expressions. // We depend only on the libraries found in all configs therefore. std::map commonTargets; for (std::string const& config : this->ConfigsList) { cmLinkImplementationLibraries const* libs = this->GenTarget->GetLinkImplementationLibraries( config, cmGeneratorTarget::LinkInterfaceFor::Link); if (libs) { for (cmLinkItem const& item : libs->Libraries) { cmGeneratorTarget const* libTarget = item.Target; if (libTarget && !StaticLibraryCycle(this->GenTarget, libTarget, config)) { // Increment target config count commonTargets[libTarget]++; } } } } for (auto const& item : commonTargets) { if (item.second == this->ConfigsList.size()) { this->AutogenTarget.DependTargets.insert(item.first->Target); } } } std::vector dependencies( this->AutogenTarget.DependFiles.begin(), this->AutogenTarget.DependFiles.end()); if (useDepfile) { // Create a custom command that generates a timestamp file and // has a depfile assigned. The depfile is created by JobDepFilesMergeT. // // Also create an additional '_autogen_timestamp_deps' that the custom // command will depend on. It will have no sources or commands to // execute, but it will have dependencies that would originally be // assigned to the pre-Qt 5.15 'autogen' target. These dependencies will // serve as a list of order-only dependencies for the custom command, // without forcing the custom command to re-execute. // // The dependency tree would then look like // '_autogen_timestamp_deps (order-only)' <- '/timestamp' file <- // '_autogen' target. const auto timestampTargetName = cmStrCat(this->GenTarget->GetName(), "_autogen_timestamp_deps"); // Add additional autogen target dependencies to // '_autogen_timestamp_deps'. for (const cmTarget* t : this->AutogenTarget.DependTargets) { std::string depname = t->GetName(); if (t->IsImported()) { auto const ttype = t->GetType(); if (ttype == cmStateEnums::TargetType::STATIC_LIBRARY || ttype == cmStateEnums::TargetType::SHARED_LIBRARY || ttype == cmStateEnums::TargetType::UNKNOWN_LIBRARY) { depname = cmStrCat("$GetName(), ">"); } } dependencies.emplace_back(std::move(depname)); } auto cc = cm::make_unique(); cc->SetWorkingDirectory(this->Dir.Work.c_str()); cc->SetDepends(dependencies); cc->SetEscapeOldStyle(false); cmTarget* timestampTarget = this->LocalGen->AddUtilityCommand( timestampTargetName, true, std::move(cc)); this->LocalGen->AddGeneratorTarget( cm::make_unique(timestampTarget, this->LocalGen)); // Set FOLDER property on the timestamp target, so it appears in the // appropriate folder in an IDE or in the file api. if (!this->TargetsFolder.empty()) { timestampTarget->SetProperty("FOLDER", this->TargetsFolder); } // Make '/timestamp' file depend on '_autogen_timestamp_deps' and on the // moc and uic executables (whichever are enabled). dependencies.clear(); dependencies.push_back(timestampTargetName); AddAutogenExecutableToDependencies(this->Moc, dependencies); AddAutogenExecutableToDependencies(this->Uic, dependencies); // Create the custom command that outputs the timestamp file. const char timestampFileName[] = "timestamp"; const std::string outputFile = cmStrCat(this->Dir.Build, "/", timestampFileName); this->AutogenTarget.DepFile = cmStrCat(this->Dir.Build, "/deps"); this->AutogenTarget.DepFileRuleName = cmStrCat(this->Dir.RelativeBuild, "/", timestampFileName); commandLines.push_back(cmMakeCommandLine( { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile })); this->AddGeneratedSource(outputFile, this->Moc); cc = cm::make_unique(); cc->SetOutputs(outputFile); cc->SetByproducts(timestampByproducts); cc->SetDepends(dependencies); cc->SetCommandLines(commandLines); cc->SetComment(autogenComment.c_str()); cc->SetWorkingDirectory(this->Dir.Work.c_str()); cc->SetEscapeOldStyle(false); cc->SetDepfile(this->AutogenTarget.DepFile); cc->SetStdPipesUTF8(stdPipesUTF8); this->LocalGen->AddCustomCommandToOutput(std::move(cc)); // Alter variables for the autogen target which now merely wraps the // custom command dependencies.clear(); dependencies.emplace_back(outputFile); commandLines.clear(); autogenComment.clear(); } // Create autogen target auto cc = cm::make_unique(); cc->SetWorkingDirectory(this->Dir.Work.c_str()); cc->SetByproducts(autogenByproducts); cc->SetDepends(dependencies); cc->SetCommandLines(commandLines); cc->SetEscapeOldStyle(false); cc->SetComment(autogenComment.c_str()); cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand( this->AutogenTarget.Name, true, std::move(cc)); // Create autogen generator target this->LocalGen->AddGeneratorTarget( cm::make_unique(autogenTarget, this->LocalGen)); // Forward origin utilities to autogen target if (this->AutogenTarget.DependOrigin) { for (BT> const& depName : this->GenTarget->GetUtilities()) { autogenTarget->AddUtility(depName.Value.first, false, this->Makefile); } } if (!useDepfile) { // Add additional autogen target dependencies to autogen target for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) { autogenTarget->AddUtility(depTarget->GetName(), false, this->Makefile); } } // Set FOLDER property in autogen target if (!this->TargetsFolder.empty()) { autogenTarget->SetProperty("FOLDER", this->TargetsFolder); } // Add autogen target to the origin target dependencies this->GenTarget->Target->AddUtility(this->AutogenTarget.Name, false, this->Makefile); // Add autogen target to the global autogen target dependencies if (this->AutogenTarget.GlobalTarget) { this->GlobalInitializer->AddToGlobalAutoGen(this->LocalGen, this->AutogenTarget.Name); } } return true; } bool cmQtAutoGenInitializer::InitRccTargets() { for (Qrc const& qrc : this->Rcc.Qrcs) { // Register info file as generated by CMake this->Makefile->AddCMakeOutputFile(qrc.InfoFile); // Register file at target { cmSourceFile* sf = this->AddGeneratedSource(qrc.OutputFile, this->Rcc); sf->SetProperty("SKIP_UNITY_BUILD_INCLUSION", "On"); } std::vector ccOutput; ccOutput.push_back(qrc.OutputFile); std::vector ccDepends; // Add the .qrc and info file to the custom command dependencies ccDepends.push_back(qrc.QrcFile); ccDepends.push_back(qrc.InfoFile); cmCustomCommandLines commandLines; if (this->MultiConfig) { // Build for all configurations for (std::string const& config : this->ConfigsList) { commandLines.push_back( cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", "cmake_autorcc", qrc.InfoFile, config })); } } else { commandLines.push_back( cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", "cmake_autorcc", qrc.InfoFile, "$" })); } std::string const ccComment = cmStrCat("Automatic RCC for ", FileProjectRelativePath(this->Makefile, qrc.QrcFile)); auto cc = cm::make_unique(); cc->SetWorkingDirectory(this->Dir.Work.c_str()); cc->SetCommandLines(commandLines); cc->SetComment(ccComment.c_str()); cc->SetStdPipesUTF8(true); if (qrc.Generated || this->Rcc.GlobalTarget) { // Create custom rcc target std::string ccName; { ccName = cmStrCat(this->GenTarget->GetName(), "_arcc_", qrc.QrcName); if (!qrc.Unique) { ccName += cmStrCat('_', qrc.QrcPathChecksum); } cc->SetByproducts(ccOutput); cc->SetDepends(ccDepends); cc->SetEscapeOldStyle(false); cmTarget* autoRccTarget = this->LocalGen->AddUtilityCommand(ccName, true, std::move(cc)); // Create autogen generator target this->LocalGen->AddGeneratorTarget( cm::make_unique(autoRccTarget, this->LocalGen)); // Set FOLDER property in autogen target if (!this->TargetsFolder.empty()) { autoRccTarget->SetProperty("FOLDER", this->TargetsFolder); } if (!this->Rcc.ExecutableTargetName.empty()) { autoRccTarget->AddUtility(this->Rcc.ExecutableTargetName, false, this->Makefile); } } // Add autogen target to the origin target dependencies this->GenTarget->Target->AddUtility(ccName, false, this->Makefile); // Add autogen target to the global autogen target dependencies if (this->Rcc.GlobalTarget) { this->GlobalInitializer->AddToGlobalAutoRcc(this->LocalGen, ccName); } } else { // Create custom rcc command { // Add the resource files to the dependencies for (std::string const& fileName : qrc.Resources) { // Add resource file to the custom command dependencies ccDepends.push_back(fileName); } if (!this->Rcc.ExecutableTargetName.empty()) { ccDepends.push_back(this->Rcc.ExecutableTargetName); } cc->SetOutputs(ccOutput); cc->SetDepends(ccDepends); this->LocalGen->AddCustomCommandToOutput(std::move(cc)); } // Reconfigure when .qrc file changes this->Makefile->AddCMakeDependFile(qrc.QrcFile); } } return true; } bool cmQtAutoGenInitializer::SetupCustomTargets() { // Create info directory on demand if (!cmSystemTools::MakeDirectory(this->Dir.Info)) { cmSystemTools::Error(cmStrCat("AutoGen: Could not create directory: ", Quoted(this->Dir.Info))); return false; } // Generate autogen target info file if (this->MocOrUicEnabled()) { // Write autogen target info files if (!this->SetupWriteAutogenInfo()) { return false; } } // Write AUTORCC info files return !this->Rcc.Enabled || this->SetupWriteRccInfo(); } bool cmQtAutoGenInitializer::SetupWriteAutogenInfo() { // Utility lambdas auto MfDef = [this](std::string const& key) { return this->Makefile->GetSafeDefinition(key); }; // Filtered headers and sources std::set moc_skip; std::set uic_skip; std::vector headers; std::vector sources; // Filter headers { headers.reserve(this->AutogenTarget.Headers.size()); for (auto const& pair : this->AutogenTarget.Headers) { MUFile const* const muf = pair.second.get(); if (muf->SkipMoc) { moc_skip.insert(muf->FullPath); } if (muf->SkipUic) { uic_skip.insert(muf->FullPath); } if (muf->Generated && !this->CMP0071Accept) { continue; } if (muf->MocIt || muf->UicIt) { headers.emplace_back(muf); } } std::sort(headers.begin(), headers.end(), [](MUFile const* a, MUFile const* b) { return (a->FullPath < b->FullPath); }); } // Filter sources { sources.reserve(this->AutogenTarget.Sources.size()); for (auto const& pair : this->AutogenTarget.Sources) { MUFile const* const muf = pair.second.get(); if (muf->Generated && !this->CMP0071Accept) { continue; } if (muf->SkipMoc) { moc_skip.insert(muf->FullPath); } if (muf->SkipUic) { uic_skip.insert(muf->FullPath); } if (muf->MocIt || muf->UicIt) { sources.emplace_back(muf); } } std::sort(sources.begin(), sources.end(), [](MUFile const* a, MUFile const* b) { return (a->FullPath < b->FullPath); }); } // Info writer InfoWriter info; // General info.SetBool("MULTI_CONFIG", this->MultiConfig); info.SetUInt("PARALLEL", this->AutogenTarget.Parallel); #ifdef _WIN32 info.SetUInt("AUTOGEN_COMMAND_LINE_LENGTH_MAX", this->AutogenTarget.MaxCommandLineLength); #endif info.SetUInt("VERBOSITY", this->Verbosity); // Directories info.Set("CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR")); info.Set("CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR")); info.Set("CMAKE_CURRENT_SOURCE_DIR", MfDef("CMAKE_CURRENT_SOURCE_DIR")); info.Set("CMAKE_CURRENT_BINARY_DIR", MfDef("CMAKE_CURRENT_BINARY_DIR")); info.Set("BUILD_DIR", this->Dir.Build); info.SetConfig("INCLUDE_DIR", this->Dir.Include); info.SetUInt("QT_VERSION_MAJOR", this->QtVersion.Major); info.SetUInt("QT_VERSION_MINOR", this->QtVersion.Minor); info.Set("QT_MOC_EXECUTABLE", this->Moc.Executable); info.Set("QT_UIC_EXECUTABLE", this->Uic.Executable); info.Set("CMAKE_EXECUTABLE", cmSystemTools::GetCMakeCommand()); info.SetConfig("SETTINGS_FILE", this->AutogenTarget.SettingsFile); info.SetConfig("PARSE_CACHE_FILE", this->AutogenTarget.ParseCacheFile); info.Set("DEP_FILE", this->AutogenTarget.DepFile); info.Set("DEP_FILE_RULE_NAME", this->AutogenTarget.DepFileRuleName); info.SetArray("CMAKE_LIST_FILES", this->Makefile->GetListFiles()); info.SetArray("HEADER_EXTENSIONS", this->Makefile->GetCMakeInstance()->GetHeaderExtensions()); auto cfgArray = [this](std::vector const& configs) -> Json::Value { Json::Value value; if (!configs.empty()) { value = Json::arrayValue; for (size_t ci : configs) { value.append(this->ConfigsList[ci]); } } return value; }; info.SetArrayArray("HEADERS", headers, [this, &cfgArray](Json::Value& jval, MUFile const* muf) { jval.resize(4u); jval[0u] = muf->FullPath; jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u'); jval[2u] = this->GetMocBuildPath(*muf); jval[3u] = cfgArray(muf->Configs); }); info.SetArrayArray( "SOURCES", sources, [&cfgArray](Json::Value& jval, MUFile const* muf) { jval.resize(3u); jval[0u] = muf->FullPath; jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u'); jval[2u] = cfgArray(muf->Configs); }); // Write moc settings if (this->Moc.Enabled) { info.SetArray("MOC_SKIP", moc_skip); info.SetConfigArray("MOC_DEFINITIONS", this->Moc.Defines); info.SetConfigArray("MOC_INCLUDES", this->Moc.Includes); info.SetArray("MOC_OPTIONS", this->Moc.Options); info.SetBool("MOC_RELAXED_MODE", this->Moc.RelaxedMode); info.SetBool("MOC_PATH_PREFIX", this->Moc.PathPrefix); cmGeneratorExpressionDAGChecker dagChecker( this->GenTarget, "AUTOMOC_MACRO_NAMES", nullptr, nullptr); EvaluatedTargetPropertyEntries InterfaceAutoMocMacroNamesEntries; if (this->MultiConfig) { for (auto const& cfg : this->ConfigsList) { if (!cfg.empty()) { AddInterfaceEntries(this->GenTarget, cfg, "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX", &dagChecker, InterfaceAutoMocMacroNamesEntries, IncludeRuntimeInterface::Yes); } } } else { AddInterfaceEntries(this->GenTarget, this->ConfigDefault, "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX", &dagChecker, InterfaceAutoMocMacroNamesEntries, IncludeRuntimeInterface::Yes); } for (auto const& entry : InterfaceAutoMocMacroNamesEntries.Entries) { this->Moc.MacroNames.insert(this->Moc.MacroNames.end(), entry.Values.begin(), entry.Values.end()); } this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames), this->Moc.MacroNames.end()); info.SetArray("MOC_MACRO_NAMES", this->Moc.MacroNames); info.SetArrayArray( "MOC_DEPEND_FILTERS", this->Moc.DependFilters, [](Json::Value& jval, std::pair const& pair) { jval.resize(2u); jval[0u] = pair.first; jval[1u] = pair.second; }); info.SetConfig("MOC_COMPILATION_FILE", this->Moc.CompilationFile); info.SetConfig("MOC_PREDEFS_FILE", this->Moc.PredefsFile); cmStandardLevelResolver const resolver{ this->Makefile }; auto const CompileOptionFlag = resolver.GetCompileOptionDef(this->GenTarget, "CXX", ""); auto const CompileOptionValue = this->GenTarget->Makefile->GetSafeDefinition(CompileOptionFlag); if (!CompileOptionValue.empty()) { if (this->Moc.PredefsCmd.size() >= 3) { this->Moc.PredefsCmd.insert(this->Moc.PredefsCmd.begin() + 1, CompileOptionValue); } } info.SetArray("MOC_PREDEFS_CMD", this->Moc.PredefsCmd); } // Write uic settings if (this->Uic.Enabled) { // Add skipped .ui files uic_skip.insert(this->Uic.SkipUi.begin(), this->Uic.SkipUi.end()); info.SetArray("UIC_SKIP", uic_skip); info.SetArrayArray("UIC_UI_FILES", this->Uic.UiFilesWithOptions, [](Json::Value& jval, UicT::UiFileT const& uiFile) { jval.resize(2u); jval[0u] = uiFile.first; InfoWriter::MakeStringArray(jval[1u], uiFile.second); }); info.SetConfigArray("UIC_OPTIONS", this->Uic.Options); info.SetArray("UIC_SEARCH_PATHS", this->Uic.SearchPaths); } info.Save(this->AutogenTarget.InfoFile); return true; } bool cmQtAutoGenInitializer::SetupWriteRccInfo() { for (Qrc const& qrc : this->Rcc.Qrcs) { // Utility lambdas auto MfDef = [this](std::string const& key) { return this->Makefile->GetSafeDefinition(key); }; InfoWriter info; // General info.SetBool("MULTI_CONFIG", this->MultiConfig); info.SetUInt("VERBOSITY", this->Verbosity); info.Set("GENERATOR", this->GlobalGen->GetName()); // Files info.Set("LOCK_FILE", qrc.LockFile); info.SetConfig("SETTINGS_FILE", qrc.SettingsFile); // Directories info.Set("CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR")); info.Set("CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR")); info.Set("CMAKE_CURRENT_SOURCE_DIR", MfDef("CMAKE_CURRENT_SOURCE_DIR")); info.Set("CMAKE_CURRENT_BINARY_DIR", MfDef("CMAKE_CURRENT_BINARY_DIR")); info.Set("BUILD_DIR", this->Dir.Build); info.SetConfig("INCLUDE_DIR", this->Dir.Include); // rcc executable info.Set("RCC_EXECUTABLE", this->Rcc.Executable); info.SetArray("RCC_LIST_OPTIONS", this->Rcc.ExecutableFeatures->ListOptions); // qrc file info.Set("SOURCE", qrc.QrcFile); info.Set("OUTPUT_CHECKSUM", qrc.QrcPathChecksum); info.Set("OUTPUT_NAME", cmSystemTools::GetFilenameName(qrc.OutputFile)); info.SetArray("OPTIONS", qrc.Options); info.SetArray("INPUTS", qrc.Resources); info.Save(qrc.InfoFile); } return true; } cmSourceFile* cmQtAutoGenInitializer::RegisterGeneratedSource( std::string const& filename) { cmSourceFile* gFile = this->Makefile->GetOrCreateSource(filename, true); gFile->MarkAsGenerated(); gFile->SetProperty("SKIP_AUTOGEN", "1"); gFile->SetProperty("SKIP_LINTING", "ON"); return gFile; } cmSourceFile* cmQtAutoGenInitializer::AddGeneratedSource( std::string const& filename, GenVarsT const& genVars, bool prepend) { // Register source at makefile cmSourceFile* gFile = this->RegisterGeneratedSource(filename); // Add source file to target this->GenTarget->AddSource(filename, prepend); // Add source file to source group this->AddToSourceGroup(filename, genVars.GenNameUpper); return gFile; } void cmQtAutoGenInitializer::AddGeneratedSource(ConfigString const& filename, GenVarsT const& genVars, bool prepend) { // XXX(xcode-per-cfg-src): Drop the Xcode-specific part of the condition // when the Xcode generator supports per-config sources. if (!this->MultiConfig || this->GlobalGen->IsXcode()) { cmSourceFile* sf = this->AddGeneratedSource(filename.Default, genVars, prepend); handleSkipPch(sf); return; } for (auto const& cfg : this->ConfigsList) { std::string const& filenameCfg = filename.Config.at(cfg); // Register source at makefile cmSourceFile* sf = this->RegisterGeneratedSource(filenameCfg); handleSkipPch(sf); // Add source file to target for this configuration. this->GenTarget->AddSource( cmStrCat("$<$:"_s, filenameCfg, ">"_s), prepend); // Add source file to source group this->AddToSourceGroup(filenameCfg, genVars.GenNameUpper); } } void cmQtAutoGenInitializer::AddToSourceGroup(std::string const& fileName, cm::string_view genNameUpper) { cmSourceGroup* sourceGroup = nullptr; // Acquire source group { std::string property; std::string groupName; { // Prefer generator specific source group name std::initializer_list const props{ cmStrCat(genNameUpper, "_SOURCE_GROUP"), "AUTOGEN_SOURCE_GROUP" }; for (std::string const& prop : props) { cmValue propName = this->Makefile->GetState()->GetGlobalProperty(prop); if (cmNonempty(propName)) { groupName = *propName; property = prop; break; } } } // Generate a source group on demand if (!groupName.empty()) { sourceGroup = this->Makefile->GetOrCreateSourceGroup(groupName); if (!sourceGroup) { cmSystemTools::Error( cmStrCat(genNameUpper, " error in ", property, ": Could not find or create the source group ", cmQtAutoGen::Quoted(groupName))); } } } if (sourceGroup) { sourceGroup->AddGroupFile(fileName); } } void cmQtAutoGenInitializer::AddCleanFile(std::string const& fileName) { this->GenTarget->Target->AppendProperty("ADDITIONAL_CLEAN_FILES", fileName); } void cmQtAutoGenInitializer::ConfigFileNames(ConfigString& configString, cm::string_view prefix, cm::string_view suffix) { configString.Default = cmStrCat(prefix, suffix); if (this->MultiConfig) { for (auto const& cfg : this->ConfigsList) { configString.Config[cfg] = cmStrCat(prefix, '_', cfg, suffix); } } } void cmQtAutoGenInitializer::ConfigFileNamesAndGenex( ConfigString& configString, std::string& genex, cm::string_view const prefix, cm::string_view const suffix) { this->ConfigFileNames(configString, prefix, suffix); if (this->MultiConfig) { genex = cmStrCat(prefix, "_$"_s, suffix); } else { genex = configString.Default; } } void cmQtAutoGenInitializer::ConfigFileClean(ConfigString& configString) { this->AddCleanFile(configString.Default); if (this->MultiConfig) { for (auto const& pair : configString.Config) { this->AddCleanFile(pair.second); } } } static cmQtAutoGen::IntegerVersion parseMocVersion(std::string str) { cmQtAutoGen::IntegerVersion result; static const std::string prelude = "moc "; size_t const pos = str.find(prelude); if (pos == std::string::npos) { return result; } str.erase(0, prelude.size() + pos); std::istringstream iss(str); std::string major; std::string minor; if (!std::getline(iss, major, '.') || !std::getline(iss, minor, '.')) { return result; } result.Major = static_cast(std::stoi(major)); result.Minor = static_cast(std::stoi(minor)); return result; } static cmQtAutoGen::IntegerVersion GetMocVersion( const std::string& mocExecutablePath) { std::string capturedStdOut; int exitCode; if (!cmSystemTools::RunSingleCommand({ mocExecutablePath, "--version" }, &capturedStdOut, nullptr, &exitCode, nullptr, cmSystemTools::OUTPUT_NONE)) { return {}; } if (exitCode != 0) { return {}; } return parseMocVersion(capturedStdOut); } static std::string FindMocExecutableFromMocTarget(cmMakefile const* makefile, unsigned int qtMajorVersion) { std::string result; const std::string mocTargetName = "Qt" + std::to_string(qtMajorVersion) + "::moc"; cmTarget const* mocTarget = makefile->FindTargetToUse(mocTargetName); if (mocTarget) { result = mocTarget->GetSafeProperty("IMPORTED_LOCATION"); } return result; } std::pair cmQtAutoGenInitializer::GetQtVersion(cmGeneratorTarget const* target, std::string mocExecutable) { // Converts a char ptr to an unsigned int value auto toUInt = [](const char* const input) -> unsigned int { unsigned long tmp = 0; if (input && cmStrToULong(input, &tmp)) { return static_cast(tmp); } return 0u; }; auto toUInt2 = [](cmValue input) -> unsigned int { unsigned long tmp = 0; if (input && cmStrToULong(*input, &tmp)) { return static_cast(tmp); } return 0u; }; // Initialize return value to a default std::pair res( IntegerVersion(), toUInt(target->GetLinkInterfaceDependentStringProperty("QT_MAJOR_VERSION", ""))); // Acquire known Qt versions std::vector knownQtVersions; { // Qt version variable prefixes static std::initializer_list< std::pair> const keys{ { "Qt6Core_VERSION_MAJOR", "Qt6Core_VERSION_MINOR" }, { "Qt5Core_VERSION_MAJOR", "Qt5Core_VERSION_MINOR" }, { "QT_VERSION_MAJOR", "QT_VERSION_MINOR" }, }; knownQtVersions.reserve(keys.size() * 2); // Adds a version to the result (nullptr safe) auto addVersion = [&knownQtVersions, &toUInt2](cmValue major, cmValue minor) { cmQtAutoGen::IntegerVersion ver(toUInt2(major), toUInt2(minor)); if (ver.Major != 0) { knownQtVersions.emplace_back(ver); } }; // Read versions from variables for (auto const& keyPair : keys) { addVersion(target->Makefile->GetDefinition(std::string(keyPair.first)), target->Makefile->GetDefinition(std::string(keyPair.second))); } // Read versions from directory properties for (auto const& keyPair : keys) { addVersion(target->Makefile->GetProperty(std::string(keyPair.first)), target->Makefile->GetProperty(std::string(keyPair.second))); } } // Evaluate known Qt versions if (!knownQtVersions.empty()) { if (res.second == 0) { // No specific version was requested by the target: // Use highest known Qt version. res.first = knownQtVersions.at(0); } else { // Pick a version from the known versions: for (auto const& it : knownQtVersions) { if (it.Major == res.second) { res.first = it; break; } } } } if (res.first.Major == 0) { // We could not get the version number from variables or directory // properties. This might happen if the find_package call for Qt is wrapped // in a function. Try to find the moc executable path from the available // targets and call "moc --version" to get the Qt version. if (mocExecutable.empty()) { mocExecutable = FindMocExecutableFromMocTarget(target->Makefile, res.second); } if (!mocExecutable.empty()) { res.first = GetMocVersion(mocExecutable); } } return res; } std::string cmQtAutoGenInitializer::GetMocBuildPath(MUFile const& muf) { std::string res; if (!muf.MocIt) { return res; } std::string basePath = cmStrCat(this->PathCheckSum.getPart(muf.FullPath), "/moc_", FileNameWithoutLastExtension(muf.FullPath)); res = cmStrCat(basePath, ".cpp"); if (this->Moc.EmittedBuildPaths.emplace(res).second) { return res; } // File name already emitted. // Try appending the header suffix to the base path. basePath = cmStrCat(basePath, '_', muf.SF->GetExtension()); res = cmStrCat(basePath, ".cpp"); if (this->Moc.EmittedBuildPaths.emplace(res).second) { return res; } // File name with header extension already emitted. // Try adding a number to the base path. constexpr std::size_t number_begin = 2; constexpr std::size_t number_end = 256; for (std::size_t ii = number_begin; ii != number_end; ++ii) { res = cmStrCat(basePath, '_', ii, ".cpp"); if (this->Moc.EmittedBuildPaths.emplace(res).second) { return res; } } // Output file name conflict (unlikely, but still...) cmSystemTools::Error( cmStrCat("moc output file name conflict for ", muf.FullPath)); return res; } bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars, const std::string& executable, bool ignoreMissingTarget) const { auto print_err = [this, &genVars](std::string const& err) { cmSystemTools::Error(cmStrCat(genVars.GenNameUpper, " for target ", this->GenTarget->GetName(), ": ", err)); }; // Custom executable { std::string const prop = cmStrCat(genVars.GenNameUpper, "_EXECUTABLE"); std::string const& val = this->GenTarget->Target->GetSafeProperty(prop); if (!val.empty()) { // Evaluate generator expression { cmListFileBacktrace lfbt = this->Makefile->GetBacktrace(); cmGeneratorExpression ge(*this->Makefile->GetCMakeInstance(), lfbt); std::unique_ptr cge = ge.Parse(val); genVars.Executable = cge->Evaluate(this->LocalGen, ""); } if (genVars.Executable.empty() && !ignoreMissingTarget) { print_err(prop + " evaluates to an empty value"); return false; } // Create empty compiler features. genVars.ExecutableFeatures = std::make_shared(); return true; } } // Find executable target { // Find executable target name cm::string_view prefix; if (this->QtVersion.Major == 4) { prefix = "Qt4::"; } else if (this->QtVersion.Major == 5) { prefix = "Qt5::"; } else if (this->QtVersion.Major == 6) { prefix = "Qt6::"; } std::string const targetName = cmStrCat(prefix, executable); // Find target cmGeneratorTarget* genTarget = this->LocalGen->FindGeneratorTargetToUse(targetName); if (genTarget) { genVars.ExecutableTargetName = targetName; genVars.ExecutableTarget = genTarget; if (genTarget->IsImported()) { genVars.Executable = genTarget->ImportedGetLocation(""); } else { genVars.Executable = genTarget->GetLocation(""); } } else { if (ignoreMissingTarget) { // Create empty compiler features. genVars.ExecutableFeatures = std::make_shared(); return true; } print_err(cmStrCat("Could not find ", executable, " executable target ", targetName)); return false; } } // Get executable features { std::string err; genVars.ExecutableFeatures = this->GlobalInitializer->GetCompilerFeatures( executable, genVars.Executable, err); if (!genVars.ExecutableFeatures) { print_err(err); return false; } } return true; } void cmQtAutoGenInitializer::handleSkipPch(cmSourceFile* sf) { bool skipPch = true; for (auto const& pair : this->AutogenTarget.Sources) { if (!pair.first->GetIsGenerated() && !pair.first->GetProperty("SKIP_PRECOMPILE_HEADERS")) { skipPch = false; } } if (skipPch) { sf->SetProperty("SKIP_PRECOMPILE_HEADERS", "ON"); } }