/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGenerators.h" #include #include #include #include #include #include #include #include #include #include #include "cmAlgorithms.h" #include "cmFilePathChecksum.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmOutputConverter.h" #include "cmStateDirectory.h" #include "cmStateSnapshot.h" #include "cmSystemTools.h" #include "cm_auto_ptr.hxx" #include "cmake.h" #if defined(__APPLE__) #include #endif // -- Static variables static const char* MocOldSettingsKey = "AM_MOC_OLD_SETTINGS"; static const char* UicOldSettingsKey = "AM_UIC_OLD_SETTINGS"; static const char* RccOldSettingsKey = "AM_RCC_OLD_SETTINGS"; // -- Static functions static std::string GetConfigDefinition(cmMakefile* makefile, const std::string& key, const std::string& config) { std::string keyConf = key; if (!config.empty()) { keyConf += "_"; keyConf += config; } const char* valueConf = makefile->GetDefinition(keyConf); if (valueConf != CM_NULLPTR) { return valueConf; } return makefile->GetSafeDefinition(key); } static std::string OldSettingsFile(const std::string& targetDirectory) { std::string filename(cmSystemTools::CollapseFullPath(targetDirectory)); cmSystemTools::ConvertToUnixSlashes(filename); filename += "/AutogenOldSettings.cmake"; return filename; } static std::string FindMatchingHeader( const std::string& absPath, const std::string& mocSubDir, const std::string& basename, const std::vector& headerExtensions) { std::string header; for (std::vector::const_iterator ext = headerExtensions.begin(); ext != headerExtensions.end(); ++ext) { std::string sourceFilePath = absPath + basename + "." + (*ext); if (cmsys::SystemTools::FileExists(sourceFilePath.c_str())) { header = sourceFilePath; break; } // Try subdirectory instead if (!mocSubDir.empty()) { sourceFilePath = mocSubDir + basename + "." + (*ext); if (cmsys::SystemTools::FileExists(sourceFilePath.c_str())) { header = sourceFilePath; break; } } } return header; } static std::string ExtractSubDir(const std::string& absPath, const std::string& currentMoc) { std::string subDir; if (currentMoc.find_first_of('/') != std::string::npos) { subDir = absPath + cmsys::SystemTools::GetFilenamePath(currentMoc) + '/'; } return subDir; } static bool FileNameIsUnique(const std::string& filePath, const std::map& fileMap) { size_t count(0); const std::string fileName = cmsys::SystemTools::GetFilenameName(filePath); for (std::map::const_iterator si = fileMap.begin(); si != fileMap.end(); ++si) { if (cmsys::SystemTools::GetFilenameName(si->first) == fileName) { ++count; if (count > 1) { return false; } } } return true; } static std::string ReadAll(const std::string& filename) { cmsys::ifstream file(filename.c_str()); std::ostringstream stream; stream << file.rdbuf(); file.close(); return stream.str(); } /** * @brief Tests if buildFile doesn't exist or is older than sourceFile * @return True if buildFile doesn't exist or is older than sourceFile */ static bool FileAbsentOrOlder(const std::string& buildFile, const std::string& sourceFile) { int result = 0; bool success = cmsys::SystemTools::FileTimeCompare(buildFile, sourceFile, &result); return (!success || (result <= 0)); } static bool ListContains(const std::vector& list, const std::string& entry) { return (std::find(list.begin(), list.end(), entry) != list.end()); } static std::string JoinOptions(const std::map& opts) { std::string result; for (std::map::const_iterator it = opts.begin(); it != opts.end(); ++it) { if (it != opts.begin()) { result += "%%%"; } result += it->first; result += "==="; result += it->second; } return result; } static std::string JoinExts(const std::vector& lst) { std::string result; if (!lst.empty()) { const std::string separator = ","; for (std::vector::const_iterator it = lst.begin(); it != lst.end(); ++it) { if (it != lst.begin()) { result += separator; } result += '.'; result += *it; } } return result; } static void UicMergeOptions(std::vector& opts, const std::vector& fileOpts, bool isQt5) { static const char* valueOptions[] = { "tr", "translate", "postfix", "generator", "include", // Since Qt 5.3 "g" }; std::vector extraOpts; for (std::vector::const_iterator it = fileOpts.begin(); it != fileOpts.end(); ++it) { std::vector::iterator existingIt = std::find(opts.begin(), opts.end(), *it); if (existingIt != opts.end()) { const char* o = it->c_str(); if (*o == '-') { ++o; } if (isQt5 && *o == '-') { ++o; } if (std::find_if(cmArrayBegin(valueOptions), cmArrayEnd(valueOptions), cmStrCmp(*it)) != cmArrayEnd(valueOptions)) { assert(existingIt + 1 != opts.end()); *(existingIt + 1) = *(it + 1); ++it; } } else { extraOpts.push_back(*it); } } opts.insert(opts.end(), extraOpts.begin(), extraOpts.end()); } // -- Class methods cmQtAutoGenerators::cmQtAutoGenerators() : Verbose(cmsys::SystemTools::HasEnv("VERBOSE")) , ColorOutput(true) , RunMocFailed(false) , RunUicFailed(false) , RunRccFailed(false) , GenerateMocAll(false) , GenerateUicAll(false) , GenerateRccAll(false) { std::string colorEnv; cmsys::SystemTools::GetEnv("COLOR", colorEnv); if (!colorEnv.empty()) { if (cmSystemTools::IsOn(colorEnv.c_str())) { this->ColorOutput = true; } else { this->ColorOutput = false; } } // Precompile regular expressions this->RegExpQObject.compile("[\n][ \t]*Q_OBJECT[^a-zA-Z0-9_]"); this->RegExpQGadget.compile("[\n][ \t]*Q_GADGET[^a-zA-Z0-9_]"); this->RegExpMocInclude.compile( "[\n][ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]"); this->RegExpUicInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+" "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]"); } bool cmQtAutoGenerators::Run(const std::string& targetDirectory, const std::string& config) { cmake cm; cm.SetHomeOutputDirectory(targetDirectory); cm.SetHomeDirectory(targetDirectory); cm.GetCurrentSnapshot().SetDefaultDefinitions(); cmGlobalGenerator gg(&cm); cmStateSnapshot snapshot = cm.GetCurrentSnapshot(); snapshot.GetDirectory().SetCurrentBinary(targetDirectory); snapshot.GetDirectory().SetCurrentSource(targetDirectory); CM_AUTO_PTR mf(new cmMakefile(&gg, snapshot)); gg.SetCurrentMakefile(mf.get()); if (!this->ReadAutogenInfoFile(mf.get(), targetDirectory, config)) { return false; } // Read old settings this->OldSettingsReadFile(mf.get(), targetDirectory); // Init and run this->Init(); if (this->QtMajorVersion == "4" || this->QtMajorVersion == "5") { if (!this->RunAutogen(mf.get())) { return false; } } // Write latest settings if (!this->OldSettingsWriteFile(targetDirectory)) { return false; } return true; } bool cmQtAutoGenerators::ReadAutogenInfoFile( cmMakefile* makefile, const std::string& targetDirectory, const std::string& config) { std::string filename(cmSystemTools::CollapseFullPath(targetDirectory)); cmSystemTools::ConvertToUnixSlashes(filename); filename += "/AutogenInfo.cmake"; if (!makefile->ReadListFile(filename.c_str())) { std::ostringstream err; err << "AutoGen: error processing file: " << filename << std::endl; this->LogError(err.str()); return false; } // - Target names this->OriginTargetName = makefile->GetSafeDefinition("AM_ORIGIN_TARGET_NAME"); this->AutogenTargetName = makefile->GetSafeDefinition("AM_TARGET_NAME"); // - Directories this->ProjectSourceDir = makefile->GetSafeDefinition("AM_CMAKE_SOURCE_DIR"); this->ProjectBinaryDir = makefile->GetSafeDefinition("AM_CMAKE_BINARY_DIR"); this->CurrentSourceDir = makefile->GetSafeDefinition("AM_CMAKE_CURRENT_SOURCE_DIR"); this->CurrentBinaryDir = makefile->GetSafeDefinition("AM_CMAKE_CURRENT_BINARY_DIR"); // - Qt environment this->QtMajorVersion = makefile->GetSafeDefinition("AM_QT_VERSION_MAJOR"); if (this->QtMajorVersion == "") { this->QtMajorVersion = makefile->GetSafeDefinition("AM_Qt5Core_VERSION_MAJOR"); } this->MocExecutable = makefile->GetSafeDefinition("AM_QT_MOC_EXECUTABLE"); this->UicExecutable = makefile->GetSafeDefinition("AM_QT_UIC_EXECUTABLE"); this->RccExecutable = makefile->GetSafeDefinition("AM_QT_RCC_EXECUTABLE"); // - File Lists cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition("AM_SOURCES"), this->Sources); cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition("AM_HEADERS"), this->Headers); // - Moc cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition("AM_SKIP_MOC"), this->SkipMoc); this->MocCompileDefinitionsStr = GetConfigDefinition(makefile, "AM_MOC_COMPILE_DEFINITIONS", config); this->MocIncludesStr = GetConfigDefinition(makefile, "AM_MOC_INCLUDES", config); this->MocOptionsStr = makefile->GetSafeDefinition("AM_MOC_OPTIONS"); // - Uic cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition("AM_SKIP_UIC"), this->SkipUic); cmSystemTools::ExpandListArgument( GetConfigDefinition(makefile, "AM_UIC_TARGET_OPTIONS", config), this->UicTargetOptions); { std::vector uicFilesVec; std::vector uicOptionsVec; cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_UIC_OPTIONS_FILES"), uicFilesVec); cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_UIC_OPTIONS_OPTIONS"), uicOptionsVec); if (uicFilesVec.size() != uicOptionsVec.size()) { std::ostringstream err; err << "AutoGen: Error: Uic files/options lists size missmatch in: " << filename << std::endl; this->LogError(err.str()); return false; } for (std::vector::iterator fileIt = uicFilesVec.begin(), optionIt = uicOptionsVec.begin(); fileIt != uicFilesVec.end(); ++fileIt, ++optionIt) { cmSystemTools::ReplaceString(*optionIt, "@list_sep@", ";"); this->UicOptions[*fileIt] = *optionIt; } } // - Rcc cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_RCC_SOURCES"), this->RccSources); { std::vector rccFilesVec; std::vector rccOptionsVec; cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_RCC_OPTIONS_FILES"), rccFilesVec); cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_RCC_OPTIONS_OPTIONS"), rccOptionsVec); if (rccFilesVec.size() != rccOptionsVec.size()) { std::ostringstream err; err << "AutoGen: Error: RCC files/options lists size missmatch in: " << filename << std::endl; this->LogError(err.str()); return false; } for (std::vector::iterator fileIt = rccFilesVec.begin(), optionIt = rccOptionsVec.begin(); fileIt != rccFilesVec.end(); ++fileIt, ++optionIt) { cmSystemTools::ReplaceString(*optionIt, "@list_sep@", ";"); this->RccOptions[*fileIt] = *optionIt; } } { std::vector rccInputLists; cmSystemTools::ExpandListArgument( makefile->GetSafeDefinition("AM_RCC_INPUTS"), rccInputLists); // qrc files in the end of the list may have been empty if (rccInputLists.size() < this->RccSources.size()) { rccInputLists.resize(this->RccSources.size()); } if (this->RccSources.size() != rccInputLists.size()) { std::ostringstream err; err << "AutoGen: Error: RCC sources/inputs lists size missmatch in: " << filename << std::endl; this->LogError(err.str()); return false; } for (std::vector::iterator fileIt = this->RccSources.begin(), inputIt = rccInputLists.begin(); fileIt != this->RccSources.end(); ++fileIt, ++inputIt) { cmSystemTools::ReplaceString(*inputIt, "@list_sep@", ";"); std::vector rccInputFiles; cmSystemTools::ExpandListArgument(*inputIt, rccInputFiles); this->RccInputs[*fileIt] = rccInputFiles; } } // - Flags this->IncludeProjectDirsBefore = makefile->IsOn("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"); this->MocRelaxedMode = makefile->IsOn("AM_MOC_RELAXED_MODE"); return true; } std::string cmQtAutoGenerators::MocSettingsStringCompose() { std::string res; res += this->MocCompileDefinitionsStr; res += " ~~~ "; res += this->MocIncludesStr; res += " ~~~ "; res += this->MocOptionsStr; res += " ~~~ "; res += this->IncludeProjectDirsBefore ? "TRUE" : "FALSE"; res += " ~~~ "; return res; } std::string cmQtAutoGenerators::UicSettingsStringCompose() { std::string res; res += cmJoin(this->UicTargetOptions, "@osep@"); res += " ~~~ "; res += JoinOptions(this->UicOptions); res += " ~~~ "; return res; } std::string cmQtAutoGenerators::RccSettingsStringCompose() { std::string res; res += JoinOptions(this->RccOptions); res += " ~~~ "; return res; } void cmQtAutoGenerators::OldSettingsReadFile( cmMakefile* makefile, const std::string& targetDirectory) { if (!this->MocExecutable.empty() || !this->UicExecutable.empty() || !this->RccExecutable.empty()) { // Compose current settings strings this->MocSettingsString = this->MocSettingsStringCompose(); this->UicSettingsString = this->UicSettingsStringCompose(); this->RccSettingsString = this->RccSettingsStringCompose(); // Read old settings const std::string filename = OldSettingsFile(targetDirectory); if (makefile->ReadListFile(filename.c_str())) { if (!this->MocExecutable.empty()) { const std::string sol = makefile->GetSafeDefinition(MocOldSettingsKey); if (sol != this->MocSettingsString) { this->GenerateMocAll = true; } } if (!this->UicExecutable.empty()) { const std::string sol = makefile->GetSafeDefinition(UicOldSettingsKey); if (sol != this->UicSettingsString) { this->GenerateUicAll = true; } } if (!this->RccExecutable.empty()) { const std::string sol = makefile->GetSafeDefinition(RccOldSettingsKey); if (sol != this->RccSettingsString) { this->GenerateRccAll = true; } } // In case any setting changed remove the old settings file. // This triggers a full rebuild on the next run if the current // build is aborted before writing the current settings in the end. if (this->GenerateMocAll || this->GenerateUicAll || this->GenerateRccAll) { cmSystemTools::RemoveFile(filename); } } else { // If the file could not be read re-generate everythiung. this->GenerateMocAll = true; this->GenerateUicAll = true; this->GenerateRccAll = true; } } } bool cmQtAutoGenerators::OldSettingsWriteFile( const std::string& targetDirectory) { bool success = true; // Only write if any setting changed if (this->GenerateMocAll || this->GenerateUicAll || this->GenerateRccAll) { const std::string filename = OldSettingsFile(targetDirectory); cmsys::ofstream outfile; outfile.open(filename.c_str(), std::ios::trunc); if (outfile) { if (!this->MocExecutable.empty()) { outfile << "set(" << MocOldSettingsKey << " " << cmOutputConverter::EscapeForCMake(this->MocSettingsString) << ")\n"; } if (!this->UicExecutable.empty()) { outfile << "set(" << UicOldSettingsKey << " " << cmOutputConverter::EscapeForCMake(this->UicSettingsString) << ")\n"; } if (!this->RccExecutable.empty()) { outfile << "set(" << RccOldSettingsKey << " " << cmOutputConverter::EscapeForCMake(this->RccSettingsString) << ")\n"; } success = outfile.good(); outfile.close(); } else { success = false; // Remove old settings file to trigger full rebuild on next run cmSystemTools::RemoveFile(filename); { std::ostringstream err; err << "AutoGen: Error: Writing old settings file failed: " << filename << std::endl; this->LogError(err.str()); } } } return success; } void cmQtAutoGenerators::Init() { this->AutogenBuildSubDir = this->AutogenTargetName; this->AutogenBuildSubDir += "/"; this->OutMocCppFilenameRel = this->AutogenBuildSubDir; this->OutMocCppFilenameRel += "moc_compilation.cpp"; this->OutMocCppFilenameAbs = this->CurrentBinaryDir + this->OutMocCppFilenameRel; // Init file path checksum generator fpathCheckSum.setupParentDirs(this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir, this->ProjectBinaryDir); std::vector cdefList; cmSystemTools::ExpandListArgument(this->MocCompileDefinitionsStr, cdefList); for (std::vector::const_iterator it = cdefList.begin(); it != cdefList.end(); ++it) { this->MocDefinitions.push_back("-D" + (*it)); } cmSystemTools::ExpandListArgument(this->MocOptionsStr, this->MocOptions); std::vector incPaths; cmSystemTools::ExpandListArgument(this->MocIncludesStr, incPaths); std::set frameworkPaths; for (std::vector::const_iterator it = incPaths.begin(); it != incPaths.end(); ++it) { const std::string& path = *it; this->MocIncludes.push_back("-I" + path); if (cmHasLiteralSuffix(path, ".framework/Headers")) { // Go up twice to get to the framework root std::vector pathComponents; cmsys::SystemTools::SplitPath(path, pathComponents); std::string frameworkPath = cmsys::SystemTools::JoinPath( pathComponents.begin(), pathComponents.end() - 2); frameworkPaths.insert(frameworkPath); } } for (std::set::const_iterator it = frameworkPaths.begin(); it != frameworkPaths.end(); ++it) { this->MocIncludes.push_back("-F"); this->MocIncludes.push_back(*it); } if (this->IncludeProjectDirsBefore) { const std::string binDir = "-I" + this->ProjectBinaryDir; const std::string srcDir = "-I" + this->ProjectSourceDir; std::list sortedMocIncludes; std::list::iterator it = this->MocIncludes.begin(); while (it != this->MocIncludes.end()) { if (cmsys::SystemTools::StringStartsWith(*it, binDir.c_str())) { sortedMocIncludes.push_back(*it); it = this->MocIncludes.erase(it); } else { ++it; } } it = this->MocIncludes.begin(); while (it != this->MocIncludes.end()) { if (cmsys::SystemTools::StringStartsWith(*it, srcDir.c_str())) { sortedMocIncludes.push_back(*it); it = this->MocIncludes.erase(it); } else { ++it; } } sortedMocIncludes.insert(sortedMocIncludes.end(), this->MocIncludes.begin(), this->MocIncludes.end()); this->MocIncludes = sortedMocIncludes; } } bool cmQtAutoGenerators::RunAutogen(cmMakefile* makefile) { // the program goes through all .cpp files to see which moc files are // included. It is not really interesting how the moc file is named, but // what file the moc is created from. Once a moc is included the same moc // may not be included in the moc_compilation.cpp file anymore. OTOH if // there's a header containing Q_OBJECT where no corresponding moc file // is included anywhere a moc_.cpp file is created and included in // the moc_compilation.cpp file. // key = moc source filepath, value = moc output filepath std::map includedMocs; std::map notIncludedMocs; std::map > includedUis; // collects all headers which may need to be mocced std::set headerFilesMoc; std::set headerFilesUic; // Parse sources { const std::vector& headerExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions(); for (std::vector::const_iterator it = this->Sources.begin(); it != this->Sources.end(); ++it) { const std::string& absFilename = *it; // Parse source file for MOC/UIC if (!this->ParseSourceFile(absFilename, headerExtensions, includedMocs, includedUis, this->MocRelaxedMode)) { return false; } // Find additional headers this->SearchHeadersForSourceFile(absFilename, headerExtensions, headerFilesMoc, headerFilesUic); } } // Parse headers for (std::vector::const_iterator it = this->Headers.begin(); it != this->Headers.end(); ++it) { const std::string& headerName = *it; if (!this->MocSkipTest(headerName)) { headerFilesMoc.insert(headerName); } if (!this->UicSkipTest(headerName)) { headerFilesUic.insert(headerName); } } this->ParseHeaders(headerFilesMoc, headerFilesUic, includedMocs, notIncludedMocs, includedUis); // Generate files if (!this->MocGenerateAll(includedMocs, notIncludedMocs)) { return false; } if (!this->UicGenerateAll(includedUis)) { return false; } if (!this->QrcGenerateAll()) { return false; } return true; } /** * @brief Tests if the C++ content requires moc processing * @return True if moc is required */ bool cmQtAutoGenerators::MocRequired(const std::string& text, std::string& macroName) { // Run a simple check before an expensive regular expression check if (strstr(text.c_str(), "Q_OBJECT") != CM_NULLPTR) { if (this->RegExpQObject.find(text)) { macroName = "Q_OBJECT"; return true; } } if (strstr(text.c_str(), "Q_GADGET") != CM_NULLPTR) { if (this->RegExpQGadget.find(text)) { macroName = "Q_GADGET"; return true; } } return false; } /** * @brief Tests if the file should be ignored for moc scanning * @return True if the file should be ignored */ bool cmQtAutoGenerators::MocSkipTest(const std::string& absFilename) { // Test if moc scanning is enabled if (!this->MocExecutable.empty()) { // Test if the file name is on the skip list if (!ListContains(this->SkipMoc, absFilename)) { return false; } } return true; } /** * @brief Tests if the file name is in the skip list */ bool cmQtAutoGenerators::UicSkipTest(const std::string& absFilename) { // Test if uic scanning is enabled if (!this->UicExecutable.empty()) { // Test if the file name is on the skip list if (!ListContains(this->SkipUic, absFilename)) { return false; } } return true; } /** * @return True on success */ bool cmQtAutoGenerators::ParseSourceFile( const std::string& absFilename, const std::vector& headerExtensions, std::map& includedMocs, std::map >& includedUis, bool relaxed) { bool success = true; const std::string contentsString = ReadAll(absFilename); if (contentsString.empty()) { std::ostringstream err; err << "AutoGen: Warning: " << absFilename << "\n" << "The file is empty\n"; this->LogWarning(err.str()); } else { // Parse source contents for MOC if (success && !this->MocSkipTest(absFilename)) { success = this->ParseContentForMoc( absFilename, contentsString, headerExtensions, includedMocs, relaxed); } // Parse source contents for UIC if (success && !this->UicSkipTest(absFilename)) { this->ParseContentForUic(absFilename, contentsString, includedUis); } } return success; } void cmQtAutoGenerators::ParseContentForUic( const std::string& absFilename, const std::string& contentsString, std::map >& includedUis) { // Process if (this->Verbose) { std::ostringstream err; err << "AutoUic: Checking " << absFilename << "\n"; this->LogInfo(err.str()); } const std::string realName = cmsys::SystemTools::GetRealPath(absFilename); const char* contentChars = contentsString.c_str(); if (strstr(contentChars, "ui_") != CM_NULLPTR) { while (this->RegExpUicInclude.find(contentChars)) { const std::string currentUi = this->RegExpUicInclude.match(1); const std::string basename = cmsys::SystemTools::GetFilenameWithoutLastExtension(currentUi); // basename should be the part of the ui filename used for // finding the correct header, so we need to remove the ui_ part includedUis[realName].push_back(basename.substr(3)); contentChars += this->RegExpUicInclude.end(); } } } /** * @return True on success */ bool cmQtAutoGenerators::ParseContentForMoc( const std::string& absFilename, const std::string& contentsString, const std::vector& headerExtensions, std::map& includedMocs, bool relaxed) { // Process if (this->Verbose) { std::ostringstream err; err << "AutoMoc: Checking " << absFilename << "\n"; this->LogInfo(err.str()); } const std::string scannedFileAbsPath = cmsys::SystemTools::GetFilenamePath( cmsys::SystemTools::GetRealPath(absFilename)) + '/'; const std::string scannedFileBasename = cmsys::SystemTools::GetFilenameWithoutLastExtension(absFilename); std::string macroName; const bool requiresMoc = this->MocRequired(contentsString, macroName); bool ownDotMocIncluded = false; bool ownMocUnderscoreIncluded = false; std::string ownMocUnderscoreFile; std::string ownMocHeaderFile; // first a simple string check for "moc" is *much* faster than the regexp, // and if the string search already fails, we don't have to try the // expensive regexp const char* contentChars = contentsString.c_str(); if (strstr(contentChars, "moc") != CM_NULLPTR) { // Iterate over all included moc files while (this->RegExpMocInclude.find(contentChars)) { const std::string currentMoc = this->RegExpMocInclude.match(1); // Basename of the current moc include std::string basename = cmsys::SystemTools::GetFilenameWithoutLastExtension(currentMoc); // If the moc include is of the moc_foo.cpp style we expect // the Q_OBJECT class declaration in a header file. // If the moc include is of the foo.moc style we need to look for // a Q_OBJECT macro in the current source file, if it contains the // macro we generate the moc file from the source file. if (cmHasLiteralPrefix(basename, "moc_")) { // Include: moc_FOO.cxx // basename should be the part of the moc filename used for // finding the correct header, so we need to remove the moc_ part basename = basename.substr(4); const std::string mocSubDir = ExtractSubDir(scannedFileAbsPath, currentMoc); const std::string headerToMoc = FindMatchingHeader( scannedFileAbsPath, mocSubDir, basename, headerExtensions); if (!headerToMoc.empty()) { includedMocs[headerToMoc] = currentMoc; if (relaxed && (basename == scannedFileBasename)) { ownMocUnderscoreIncluded = true; ownMocUnderscoreFile = currentMoc; ownMocHeaderFile = headerToMoc; } } else { std::ostringstream err; err << "AutoMoc: Error: " << absFilename << "\n" << "The file includes the moc file \"" << currentMoc << "\", but could not find header \"" << basename << '{' << JoinExts(headerExtensions) << "}\" "; if (mocSubDir.empty()) { err << "in " << scannedFileAbsPath << "\n"; } else { err << "neither in " << scannedFileAbsPath << " nor in " << mocSubDir << "\n"; } this->LogError(err.str()); return false; } } else { // Include: FOO.moc std::string fileToMoc; if (relaxed) { // Mode: Relaxed if (!requiresMoc || basename != scannedFileBasename) { const std::string mocSubDir = ExtractSubDir(scannedFileAbsPath, currentMoc); const std::string headerToMoc = FindMatchingHeader( scannedFileAbsPath, mocSubDir, basename, headerExtensions); if (!headerToMoc.empty()) { // This is for KDE4 compatibility: fileToMoc = headerToMoc; if (!requiresMoc && basename == scannedFileBasename) { std::ostringstream err; err << "AutoMoc: Warning: " << absFilename << "\n" << "The file includes the moc file \"" << currentMoc << "\", but does not contain a " << macroName << " macro. Running moc on " << "\"" << headerToMoc << "\" ! Include \"moc_" << basename << ".cpp\" for a compatibility with " "strict mode (see CMAKE_AUTOMOC_RELAXED_MODE).\n"; this->LogWarning(err.str()); } else { std::ostringstream err; err << "AutoMoc: Warning: " << absFilename << "\n" << "The file includes the moc file \"" << currentMoc << "\" instead of \"moc_" << basename << ".cpp\". Running moc on " << "\"" << headerToMoc << "\" ! Include \"moc_" << basename << ".cpp\" for compatibility with " "strict mode (see CMAKE_AUTOMOC_RELAXED_MODE).\n"; this->LogWarning(err.str()); } } else { std::ostringstream err; err << "AutoMoc: Error: " << absFilename << "\n" << "The file includes the moc file \"" << currentMoc << "\", which seems to be the moc file from a different " "source file. CMake also could not find a matching " "header.\n"; this->LogError(err.str()); return false; } } else { // Include self fileToMoc = absFilename; ownDotMocIncluded = true; } } else { // Mode: Strict if (basename == scannedFileBasename) { // Include self fileToMoc = absFilename; ownDotMocIncluded = true; } else { // Don't allow FOO.moc include other than self in strict mode std::ostringstream err; err << "AutoMoc: Error: " << absFilename << "\n" << "The file includes the moc file \"" << currentMoc << "\", which seems to be the moc file from a different " "source file. This is not supported. Include \"" << scannedFileBasename << ".moc\" to run moc on this source file.\n"; this->LogError(err.str()); return false; } } if (!fileToMoc.empty()) { includedMocs[fileToMoc] = currentMoc; } } // Forward content pointer contentChars += this->RegExpMocInclude.end(); } } // In this case, check whether the scanned file itself contains a Q_OBJECT. // If this is the case, the moc_foo.cpp should probably be generated from // foo.cpp instead of foo.h, because otherwise it won't build. // But warn, since this is not how it is supposed to be used. if (requiresMoc && !ownDotMocIncluded) { if (relaxed && ownMocUnderscoreIncluded) { // This is for KDE4 compatibility: std::ostringstream err; err << "AutoMoc: Warning: " << absFilename << "\n" << "The file contains a " << macroName << " macro, but does not include " << "\"" << scannedFileBasename << ".moc\", but instead includes " << "\"" << ownMocUnderscoreFile << "\". Running moc on " << "\"" << absFilename << "\" ! Better include \"" << scannedFileBasename << ".moc\" for compatibility with " "strict mode (see CMAKE_AUTOMOC_RELAXED_MODE).\n"; this->LogWarning(err.str()); // Use scanned source file instead of scanned header file as moc source includedMocs[absFilename] = ownMocUnderscoreFile; includedMocs.erase(ownMocHeaderFile); } else { // Otherwise always error out since it will not compile: std::ostringstream err; err << "AutoMoc: Error: " << absFilename << "\n" << "The file contains a " << macroName << " macro, but does not include " << "\"" << scannedFileBasename << ".moc\" !\n"; this->LogError(err.str()); return false; } } return true; } void cmQtAutoGenerators::SearchHeadersForSourceFile( const std::string& absFilename, const std::vector& headerExtensions, std::set& absHeadersMoc, std::set& absHeadersUic) { // search for header files and private header files we may need to moc: std::string basepath = cmsys::SystemTools::GetFilenamePath( cmsys::SystemTools::GetRealPath(absFilename)); basepath += '/'; basepath += cmsys::SystemTools::GetFilenameWithoutLastExtension(absFilename); // Search for regular header for (std::vector::const_iterator ext = headerExtensions.begin(); ext != headerExtensions.end(); ++ext) { const std::string headerName = basepath + "." + (*ext); if (cmsys::SystemTools::FileExists(headerName.c_str())) { // Moc headers if (!this->MocSkipTest(absFilename) && !this->MocSkipTest(headerName)) { absHeadersMoc.insert(headerName); } // Uic headers if (!this->UicSkipTest(absFilename) && !this->UicSkipTest(headerName)) { absHeadersUic.insert(headerName); } break; } } // Search for private header for (std::vector::const_iterator ext = headerExtensions.begin(); ext != headerExtensions.end(); ++ext) { const std::string headerName = basepath + "_p." + (*ext); if (cmsys::SystemTools::FileExists(headerName.c_str())) { // Moc headers if (!this->MocSkipTest(absFilename) && !this->MocSkipTest(headerName)) { absHeadersMoc.insert(headerName); } // Uic headers if (!this->UicSkipTest(absFilename) && !this->UicSkipTest(headerName)) { absHeadersUic.insert(headerName); } break; } } } void cmQtAutoGenerators::ParseHeaders( const std::set& absHeadersMoc, const std::set& absHeadersUic, const std::map& includedMocs, std::map& notIncludedMocs, std::map >& includedUis) { // Merged header files list to read files only once std::set headerFiles; headerFiles.insert(absHeadersMoc.begin(), absHeadersMoc.end()); headerFiles.insert(absHeadersUic.begin(), absHeadersUic.end()); for (std::set::const_iterator hIt = headerFiles.begin(); hIt != headerFiles.end(); ++hIt) { const std::string& headerName = *hIt; const std::string contents = ReadAll(headerName); // Parse header content for MOC if ((absHeadersMoc.find(headerName) != absHeadersMoc.end()) && (includedMocs.find(headerName) == includedMocs.end())) { // Process if (this->Verbose) { std::ostringstream err; err << "AutoMoc: Checking " << headerName << "\n"; this->LogInfo(err.str()); } std::string macroName; if (this->MocRequired(contents, macroName)) { notIncludedMocs[headerName] = fpathCheckSum.getPart(headerName) + "/moc_" + cmsys::SystemTools::GetFilenameWithoutLastExtension(headerName) + ".cpp"; } } // Parse header content for UIC if (absHeadersUic.find(headerName) != absHeadersUic.end()) { this->ParseContentForUic(headerName, contents, includedUis); } } } bool cmQtAutoGenerators::MocGenerateAll( const std::map& includedMocs, const std::map& notIncludedMocs) { if (this->MocExecutable.empty()) { return true; } // look for name collisions { std::multimap collisions; // Test merged map of included and notIncluded std::map mergedMocs(includedMocs); mergedMocs.insert(notIncludedMocs.begin(), notIncludedMocs.end()); if (this->NameCollisionTest(mergedMocs, collisions)) { std::ostringstream err; err << "AutoMoc: Error: " "The same moc file will be generated " "from different sources." << std::endl << "To avoid this error either" << std::endl << "- rename the source files or" << std::endl << "- do not include the (moc_NAME.cpp|NAME.moc) file" << std::endl; this->LogErrorNameCollision(err.str(), collisions); return false; } } // generate moc files that are included by source files. { const std::string subDirPrefix = "include/"; for (std::map::const_iterator it = includedMocs.begin(); it != includedMocs.end(); ++it) { if (!this->MocGenerateFile(it->first, it->second, subDirPrefix)) { if (this->RunMocFailed) { return false; } } } } // generate moc files that are _not_ included by source files. bool automocCppChanged = false; { const std::string subDirPrefix; for (std::map::const_iterator it = notIncludedMocs.begin(); it != notIncludedMocs.end(); ++it) { if (this->MocGenerateFile(it->first, it->second, subDirPrefix)) { automocCppChanged = true; } else { if (this->RunMocFailed) { return false; } } } } // Compose moc_compilation.cpp content std::string automocSource; { std::ostringstream outStream; outStream << "/* This file is autogenerated, do not edit*/\n"; if (notIncludedMocs.empty()) { // Dummy content outStream << "enum some_compilers { need_more_than_nothing };\n"; } else { // Valid content for (std::map::const_iterator it = notIncludedMocs.begin(); it != notIncludedMocs.end(); ++it) { outStream << "#include \"" << it->second << "\"\n"; } } outStream.flush(); automocSource = outStream.str(); } // Check if we even need to update moc_compilation.cpp if (!automocCppChanged) { // compare contents of the moc_compilation.cpp file const std::string oldContents = ReadAll(this->OutMocCppFilenameAbs); if (oldContents == automocSource) { // nothing changed: don't touch the moc_compilation.cpp file if (this->Verbose) { std::ostringstream err; err << "AutoMoc: " << this->OutMocCppFilenameRel << " still up to date" << std::endl; this->LogInfo(err.str()); } return true; } } // Actually write moc_compilation.cpp { std::string msg = "Generating MOC compilation "; msg += this->OutMocCppFilenameRel; this->LogBold(msg); } // Make sure the parent directory exists bool success = this->MakeParentDirectory(this->OutMocCppFilenameAbs); if (success) { cmsys::ofstream outfile; outfile.open(this->OutMocCppFilenameAbs.c_str(), std::ios::trunc); if (!outfile) { success = false; std::ostringstream err; err << "AutoMoc: error opening " << this->OutMocCppFilenameAbs << "\n"; this->LogError(err.str()); } else { outfile << automocSource; // Check for write errors if (!outfile.good()) { success = false; std::ostringstream err; err << "AutoMoc: error writing " << this->OutMocCppFilenameAbs << "\n"; this->LogError(err.str()); } } } return success; } /** * @return True if a moc file was created. False may indicate an error. */ bool cmQtAutoGenerators::MocGenerateFile(const std::string& sourceFile, const std::string& mocFileName, const std::string& subDirPrefix) { const std::string mocFileRel = this->AutogenBuildSubDir + subDirPrefix + mocFileName; const std::string mocFileAbs = this->CurrentBinaryDir + mocFileRel; bool generateMoc = this->GenerateMocAll; // Test if the source file is newer that the build file if (!generateMoc) { generateMoc = FileAbsentOrOlder(mocFileAbs, sourceFile); } if (generateMoc) { // Log this->LogBold("Generating MOC source " + mocFileRel); // Make sure the parent directory exists if (!this->MakeParentDirectory(mocFileAbs)) { this->RunMocFailed = true; return false; } std::vector command; command.push_back(this->MocExecutable); command.insert(command.end(), this->MocIncludes.begin(), this->MocIncludes.end()); command.insert(command.end(), this->MocDefinitions.begin(), this->MocDefinitions.end()); command.insert(command.end(), this->MocOptions.begin(), this->MocOptions.end()); #ifdef _WIN32 command.push_back("-DWIN32"); #endif command.push_back("-o"); command.push_back(mocFileAbs); command.push_back(sourceFile); if (this->Verbose) { this->LogCommand(command); } std::string output; int retVal = 0; bool result = cmSystemTools::RunSingleCommand(command, &output, &output, &retVal); if (!result || retVal) { { std::ostringstream err; err << "AutoMoc: Error: moc process for " << mocFileRel << " failed:\n" << output << std::endl; this->LogError(err.str()); } cmSystemTools::RemoveFile(mocFileAbs); this->RunMocFailed = true; return false; } return true; } return false; } bool cmQtAutoGenerators::UicGenerateAll( const std::map >& includedUis) { if (this->UicExecutable.empty()) { return true; } // single map with input / output names std::map > uiGenMap; std::map testMap; for (std::map >::const_iterator it = includedUis.begin(); it != includedUis.end(); ++it) { // source file path std::string sourcePath = cmsys::SystemTools::GetFilenamePath(it->first); sourcePath += '/'; // insert new map for source file an use new reference uiGenMap[it->first] = std::map(); std::map& sourceMap = uiGenMap[it->first]; for (std::vector::const_iterator sit = it->second.begin(); sit != it->second.end(); ++sit) { const std::string& uiFileName = *sit; const std::string uiInputFile = sourcePath + uiFileName + ".ui"; const std::string uiOutputFile = "ui_" + uiFileName + ".h"; sourceMap[uiInputFile] = uiOutputFile; testMap[uiInputFile] = uiOutputFile; } } // look for name collisions { std::multimap collisions; if (this->NameCollisionTest(testMap, collisions)) { std::ostringstream err; err << "AutoUic: Error: The same ui_NAME.h file will be generated " "from different sources." << std::endl << "To avoid this error rename the source files." << std::endl; this->LogErrorNameCollision(err.str(), collisions); return false; } } testMap.clear(); // generate ui files for (std::map >::const_iterator it = uiGenMap.begin(); it != uiGenMap.end(); ++it) { for (std::map::const_iterator sit = it->second.begin(); sit != it->second.end(); ++sit) { if (!this->UicGenerateFile(it->first, sit->first, sit->second)) { if (this->RunUicFailed) { return false; } } } } return true; } /** * @return True if a uic file was created. False may indicate an error. */ bool cmQtAutoGenerators::UicGenerateFile(const std::string& realName, const std::string& uiInputFile, const std::string& uiOutputFile) { const std::string uicFileRel = this->AutogenBuildSubDir + "include/" + uiOutputFile; const std::string uicFileAbs = this->CurrentBinaryDir + uicFileRel; bool generateUic = this->GenerateUicAll; // Test if the source file is newer that the build file if (!generateUic) { generateUic = FileAbsentOrOlder(uicFileAbs, uiInputFile); } if (generateUic) { // Log this->LogBold("Generating UIC header " + uicFileRel); // Make sure the parent directory exists if (!this->MakeParentDirectory(uicFileAbs)) { this->RunUicFailed = true; return false; } std::vector command; command.push_back(this->UicExecutable); std::vector opts = this->UicTargetOptions; std::map::const_iterator optionIt = this->UicOptions.find(uiInputFile); if (optionIt != this->UicOptions.end()) { std::vector fileOpts; cmSystemTools::ExpandListArgument(optionIt->second, fileOpts); UicMergeOptions(opts, fileOpts, this->QtMajorVersion == "5"); } command.insert(command.end(), opts.begin(), opts.end()); command.push_back("-o"); command.push_back(uicFileAbs); command.push_back(uiInputFile); if (this->Verbose) { this->LogCommand(command); } std::string output; int retVal = 0; bool result = cmSystemTools::RunSingleCommand(command, &output, &output, &retVal); if (!result || retVal) { { std::ostringstream err; err << "AutoUic: Error: uic process for " << uicFileRel << " needed by\n \"" << realName << "\"\nfailed:\n" << output << std::endl; this->LogError(err.str()); } cmSystemTools::RemoveFile(uicFileAbs); this->RunUicFailed = true; return false; } return true; } return false; } bool cmQtAutoGenerators::QrcGenerateAll() { if (this->RccExecutable.empty()) { return true; } // generate single map with input / output names std::map qrcGenMap; for (std::vector::const_iterator si = this->RccSources.begin(); si != this->RccSources.end(); ++si) { const std::string ext = cmsys::SystemTools::GetFilenameLastExtension(*si); if (ext == ".qrc") { qrcGenMap[*si] = this->AutogenBuildSubDir + fpathCheckSum.getPart(*si) + "/qrc_" + cmsys::SystemTools::GetFilenameWithoutLastExtension(*si) + ".cpp"; } } // look for name collisions { std::multimap collisions; if (this->NameCollisionTest(qrcGenMap, collisions)) { std::ostringstream err; err << "AutoRcc: Error: The same qrc_NAME.cpp file" " will be generated from different sources." << std::endl << "To avoid this error rename the source .qrc files." << std::endl; this->LogErrorNameCollision(err.str(), collisions); return false; } } // generate qrc files for (std::map::const_iterator si = qrcGenMap.begin(); si != qrcGenMap.end(); ++si) { bool unique = FileNameIsUnique(si->first, qrcGenMap); if (!this->QrcGenerateFile(si->first, si->second, unique)) { if (this->RunRccFailed) { return false; } } } return true; } /** * @return True if a rcc file was created. False may indicate an error. */ bool cmQtAutoGenerators::QrcGenerateFile(const std::string& qrcInputFile, const std::string& qrcOutputFile, bool unique_n) { std::string symbolName = cmsys::SystemTools::GetFilenameWithoutLastExtension(qrcInputFile); if (!unique_n) { symbolName += "_"; symbolName += fpathCheckSum.getPart(qrcInputFile); } // Replace '-' with '_'. The former is valid for // file names but not for symbol names. std::replace(symbolName.begin(), symbolName.end(), '-', '_'); const std::string qrcBuildFile = this->CurrentBinaryDir + qrcOutputFile; bool generateQrc = this->GenerateRccAll; // Test if the resources list file is newer than build file if (!generateQrc) { generateQrc = FileAbsentOrOlder(qrcBuildFile, qrcInputFile); } // Test if any resource file is newer than the build file if (!generateQrc) { const std::vector& files = this->RccInputs[qrcInputFile]; for (std::vector::const_iterator it = files.begin(); it != files.end(); ++it) { if (FileAbsentOrOlder(qrcBuildFile, *it)) { generateQrc = true; break; } } } if (generateQrc) { { std::string msg = "Generating RCC source "; msg += qrcOutputFile; this->LogBold(msg); } // Make sure the parent directory exists if (!this->MakeParentDirectory(qrcOutputFile)) { this->RunRccFailed = true; return false; } std::vector command; command.push_back(this->RccExecutable); { std::map::const_iterator optionIt = this->RccOptions.find(qrcInputFile); if (optionIt != this->RccOptions.end()) { cmSystemTools::ExpandListArgument(optionIt->second, command); } } command.push_back("-name"); command.push_back(symbolName); command.push_back("-o"); command.push_back(qrcBuildFile); command.push_back(qrcInputFile); if (this->Verbose) { this->LogCommand(command); } std::string output; int retVal = 0; bool result = cmSystemTools::RunSingleCommand(command, &output, &output, &retVal); if (!result || retVal) { { std::ostringstream err; err << "AutoRcc: Error: rcc process for " << qrcOutputFile << " failed:\n" << output << std::endl; this->LogError(err.str()); } cmSystemTools::RemoveFile(qrcBuildFile); this->RunRccFailed = true; return false; } return true; } return false; } void cmQtAutoGenerators::LogErrorNameCollision( const std::string& message, const std::multimap& collisions) { typedef std::multimap::const_iterator Iter; std::ostringstream err; // Add message err << message; // Append collision list for (Iter it = collisions.begin(); it != collisions.end(); ++it) { err << it->first << " : " << it->second << std::endl; } this->LogError(err.str()); } void cmQtAutoGenerators::LogBold(const std::string& message) { cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundBlue | cmsysTerminal_Color_ForegroundBold, message.c_str(), true, this->ColorOutput); } void cmQtAutoGenerators::LogInfo(const std::string& message) { std::cout << message.c_str(); } void cmQtAutoGenerators::LogWarning(const std::string& message) { std::ostringstream ostr; ostr << message << "\n"; std::cout << message.c_str(); } void cmQtAutoGenerators::LogError(const std::string& message) { std::ostringstream ostr; ostr << message << "\n\n"; std::cerr << ostr.str(); } void cmQtAutoGenerators::LogCommand(const std::vector& command) { std::ostringstream sbuf; for (std::vector::const_iterator cmdIt = command.begin(); cmdIt != command.end(); ++cmdIt) { if (cmdIt != command.begin()) { sbuf << " "; } sbuf << *cmdIt; } if (!sbuf.str().empty()) { sbuf << std::endl; this->LogInfo(sbuf.str()); } } /** * @brief Collects name collisions as output/input pairs * @return True if there were collisions */ bool cmQtAutoGenerators::NameCollisionTest( const std::map& genFiles, std::multimap& collisions) { typedef std::map::const_iterator Iter; typedef std::map::value_type VType; for (Iter ait = genFiles.begin(); ait != genFiles.end(); ++ait) { bool first_match(true); for (Iter bit = (++Iter(ait)); bit != genFiles.end(); ++bit) { if (ait->second == bit->second) { if (first_match) { if (collisions.find(ait->second) != collisions.end()) { // We already know of this collision from before break; } collisions.insert(VType(ait->second, ait->first)); first_match = false; } collisions.insert(VType(bit->second, bit->first)); } } } return !collisions.empty(); } /** * @brief Generates the parent directory of the given file on demand * @return True on success */ bool cmQtAutoGenerators::MakeParentDirectory(const std::string& filename) { bool success = true; const std::string dirName = cmSystemTools::GetFilenamePath(filename); if (!dirName.empty()) { success = cmsys::SystemTools::MakeDirectory(dirName); if (!success) { std::ostringstream err; err << "AutoGen: Directory creation failed: " << dirName << std::endl; this->LogError(err.str()); } } return success; }