/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGen.h" #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include "cmDuration.h" #include "cmProcessOutput.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" // - Static functions /// @brief Merges newOpts into baseOpts /// @arg valueOpts list of options that accept a value static void MergeOptions(std::vector& baseOpts, std::vector const& newOpts, std::initializer_list valueOpts, bool isQt5OrLater) { if (newOpts.empty()) { return; } if (baseOpts.empty()) { baseOpts = newOpts; return; } std::vector extraOpts; for (auto fit = newOpts.begin(), fitEnd = newOpts.end(); fit != fitEnd; ++fit) { std::string const& newOpt = *fit; auto existIt = std::find(baseOpts.begin(), baseOpts.end(), newOpt); if (existIt != baseOpts.end()) { if (newOpt.size() >= 2) { // Acquire the option name std::string optName; { auto oit = newOpt.begin(); if (*oit == '-') { ++oit; if (isQt5OrLater && (*oit == '-')) { ++oit; } optName.assign(oit, newOpt.end()); } } // Test if this is a value option and change the existing value if (!optName.empty() && cm::contains(valueOpts, optName)) { const auto existItNext(existIt + 1); const auto fitNext(fit + 1); if ((existItNext != baseOpts.end()) && (fitNext != fitEnd)) { *existItNext = *fitNext; ++fit; } } } } else { extraOpts.push_back(newOpt); } } // Append options cm::append(baseOpts, extraOpts); } // - Class definitions unsigned int const cmQtAutoGen::ParallelMax = 64; #ifdef _WIN32 // Actually 32767 (see // https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) but we // allow for a small margin size_t const cmQtAutoGen::CommandLineLengthMax = 32000; #endif cm::string_view cmQtAutoGen::GeneratorName(GenT genType) { switch (genType) { case GenT::GEN: return "AutoGen"; case GenT::MOC: return "AutoMoc"; case GenT::UIC: return "AutoUic"; case GenT::RCC: return "AutoRcc"; } return "AutoGen"; } cm::string_view cmQtAutoGen::GeneratorNameUpper(GenT genType) { switch (genType) { case GenT::GEN: return "AUTOGEN"; case GenT::MOC: return "AUTOMOC"; case GenT::UIC: return "AUTOUIC"; case GenT::RCC: return "AUTORCC"; } return "AUTOGEN"; } std::string cmQtAutoGen::Tools(bool moc, bool uic, bool rcc) { std::array lst; decltype(lst)::size_type num = 0; if (moc) { lst.at(num++) = "AUTOMOC"; } if (uic) { lst.at(num++) = "AUTOUIC"; } if (rcc) { lst.at(num++) = "AUTORCC"; } switch (num) { case 1: return std::string(lst[0]); case 2: return cmStrCat(lst[0], " and ", lst[1]); case 3: return cmStrCat(lst[0], ", ", lst[1], " and ", lst[2]); default: break; } return std::string(); } std::string cmQtAutoGen::Quoted(cm::string_view text) { static std::initializer_list> const replacements = { { "\\", "\\\\" }, { "\"", "\\\"" }, { "\a", "\\a" }, { "\b", "\\b" }, { "\f", "\\f" }, { "\n", "\\n" }, { "\r", "\\r" }, { "\t", "\\t" }, { "\v", "\\v" } }; std::string res(text); for (auto const& pair : replacements) { cmSystemTools::ReplaceString(res, pair.first, pair.second); } return cmStrCat('"', res, '"'); } std::string cmQtAutoGen::QuotedCommand(std::vector const& command) { std::string res; for (std::string const& item : command) { if (!res.empty()) { res.push_back(' '); } std::string const cesc = cmQtAutoGen::Quoted(item); if (item.empty() || (cesc.size() > (item.size() + 2)) || (cesc.find(' ') != std::string::npos)) { res += cesc; } else { res += item; } } return res; } std::string cmQtAutoGen::FileNameWithoutLastExtension(cm::string_view filename) { auto slashPos = filename.rfind('/'); if (slashPos != cm::string_view::npos) { filename.remove_prefix(slashPos + 1); } auto dotPos = filename.rfind('.'); return std::string(filename.substr(0, dotPos)); } std::string cmQtAutoGen::ParentDir(cm::string_view filename) { auto slashPos = filename.rfind('/'); if (slashPos == cm::string_view::npos) { return std::string(); } return std::string(filename.substr(0, slashPos)); } std::string cmQtAutoGen::SubDirPrefix(cm::string_view filename) { auto slashPos = filename.rfind('/'); if (slashPos == cm::string_view::npos) { return std::string(); } return std::string(filename.substr(0, slashPos + 1)); } std::string cmQtAutoGen::AppendFilenameSuffix(cm::string_view filename, cm::string_view suffix) { auto dotPos = filename.rfind('.'); if (dotPos == cm::string_view::npos) { return cmStrCat(filename, suffix); } return cmStrCat(filename.substr(0, dotPos), suffix, filename.substr(dotPos, filename.size() - dotPos)); } void cmQtAutoGen::UicMergeOptions(std::vector& baseOpts, std::vector const& newOpts, bool isQt5OrLater) { static std::initializer_list const valueOpts = { "tr", "translate", "postfix", "generator", "include", // Since Qt 5.3 "g" }; MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater); } void cmQtAutoGen::RccMergeOptions(std::vector& baseOpts, std::vector const& newOpts, bool isQt5OrLater) { static std::initializer_list const valueOpts = { "name", "root", "compress", "threshold" }; MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater); } static void RccListParseContent(std::string const& content, std::vector& files) { cmsys::RegularExpression fileMatchRegex("(]*>)"); const char* contentChars = content.c_str(); while (fileMatchRegex.find(contentChars)) { std::string const qrcEntry = fileMatchRegex.match(1); contentChars += qrcEntry.size(); { fileReplaceRegex.find(qrcEntry); std::string const tag = fileReplaceRegex.match(1); files.push_back(qrcEntry.substr(tag.size())); } } } static bool RccListParseOutput(std::string const& rccStdOut, std::string const& rccStdErr, std::vector& files, std::string& error) { // Lambda to strip CR characters auto StripCR = [](std::string& line) { std::string::size_type cr = line.find('\r'); if (cr != std::string::npos) { line = line.substr(0, cr); } }; // Parse rcc std output { std::istringstream ostr(rccStdOut); std::string oline; while (std::getline(ostr, oline)) { StripCR(oline); if (!oline.empty()) { files.push_back(oline); } } } // Parse rcc error output { std::istringstream estr(rccStdErr); std::string eline; while (std::getline(estr, eline)) { StripCR(eline); if (cmHasLiteralPrefix(eline, "RCC: Error in")) { static std::string const searchString = "Cannot find file '"; std::string::size_type pos = eline.find(searchString); if (pos == std::string::npos) { error = cmStrCat("rcc lists unparsable output:\n", cmQtAutoGen::Quoted(eline), '\n'); return false; } pos += searchString.length(); std::string::size_type sz = eline.size() - pos - 1; files.push_back(eline.substr(pos, sz)); } } } return true; } cmQtAutoGen::RccLister::RccLister() = default; cmQtAutoGen::RccLister::RccLister(std::string rccExecutable, std::vector listOptions) : RccExcutable_(std::move(rccExecutable)) , ListOptions_(std::move(listOptions)) { } bool cmQtAutoGen::RccLister::list(std::string const& qrcFile, std::vector& files, std::string& error, bool verbose) const { error.clear(); if (!cmSystemTools::FileExists(qrcFile, true)) { error = cmStrCat("The resource file ", Quoted(qrcFile), " does not exist."); return false; } // Run rcc list command in the directory of the qrc file with the pathless // qrc file name argument. This way rcc prints relative paths. // This avoids issues on Windows when the qrc file is in a path that // contains non-ASCII characters. std::string const fileDir = cmSystemTools::GetFilenamePath(qrcFile); if (!this->RccExcutable_.empty() && cmSystemTools::FileExists(this->RccExcutable_, true) && !this->ListOptions_.empty()) { bool result = false; int retVal = 0; std::string rccStdOut; std::string rccStdErr; { std::vector cmd; cmd.emplace_back(this->RccExcutable_); cm::append(cmd, this->ListOptions_); cmd.emplace_back(cmSystemTools::GetFilenameName(qrcFile)); // Log command if (verbose) { cmSystemTools::Stdout( cmStrCat("Running command:\n", QuotedCommand(cmd), '\n')); } result = cmSystemTools::RunSingleCommand( cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(), cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto); } if (!result || retVal) { error = cmStrCat("The rcc list process failed for ", Quoted(qrcFile), '\n'); if (!rccStdOut.empty()) { error += cmStrCat(rccStdOut, '\n'); } if (!rccStdErr.empty()) { error += cmStrCat(rccStdErr, '\n'); } return false; } if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) { return false; } } else { // We can't use rcc for the file listing. // Read the qrc file content into string and parse it. { std::string qrcContents; { cmsys::ifstream ifs(qrcFile.c_str()); if (ifs) { std::ostringstream osst; osst << ifs.rdbuf(); qrcContents = osst.str(); } else { error = cmStrCat("The resource file ", Quoted(qrcFile), " is not readable\n"); return false; } } // Parse string content RccListParseContent(qrcContents, files); } } // Convert relative paths to absolute paths for (std::string& entry : files) { entry = cmSystemTools::CollapseFullPath(entry, fileDir); } return true; }