/* 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 "cmAlgorithms.h" #include "cmDuration.h" #include "cmProcessOutput.h" #include "cmSystemTools.h" #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include #include #include #include // - Static functions /// @brief Merges newOpts into baseOpts /// @arg valueOpts list of options that accept a value void MergeOptions(std::vector& baseOpts, std::vector const& newOpts, std::vector const& valueOpts, bool isQt5) { typedef std::vector::iterator Iter; typedef std::vector::const_iterator CIter; if (newOpts.empty()) { return; } if (baseOpts.empty()) { baseOpts = newOpts; return; } std::vector extraOpts; for (CIter fit = newOpts.begin(), fitEnd = newOpts.end(); fit != fitEnd; ++fit) { std::string const& newOpt = *fit; Iter 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 (isQt5 && (*oit == '-')) { ++oit; } optName.assign(oit, newOpt.end()); } } // Test if this is a value option and change the existing value if (!optName.empty() && (std::find(valueOpts.begin(), valueOpts.end(), optName) != valueOpts.end())) { const Iter existItNext(existIt + 1); const CIter fitNext(fit + 1); if ((existItNext != baseOpts.end()) && (fitNext != fitEnd)) { *existItNext = *fitNext; ++fit; } } } } else { extraOpts.push_back(newOpt); } } // Append options baseOpts.insert(baseOpts.end(), extraOpts.begin(), extraOpts.end()); } // - Class definitions unsigned int const cmQtAutoGen::ParallelMax = 64; std::string const cmQtAutoGen::ListSep = "<<>>"; std::string const& cmQtAutoGen::GeneratorName(GenT genType) { static const std::string AutoGen("AutoGen"); static const std::string AutoMoc("AutoMoc"); static const std::string AutoUic("AutoUic"); static const std::string AutoRcc("AutoRcc"); 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 const& cmQtAutoGen::GeneratorNameUpper(GenT genType) { static const std::string AUTOGEN("AUTOGEN"); static const std::string AUTOMOC("AUTOMOC"); static const std::string AUTOUIC("AUTOUIC"); static const std::string AUTORCC("AUTORCC"); 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::string res; std::vector lst; if (moc) { lst.emplace_back("AUTOMOC"); } if (uic) { lst.emplace_back("AUTOUIC"); } if (rcc) { lst.emplace_back("AUTORCC"); } switch (lst.size()) { case 1: res += lst.at(0); break; case 2: res += lst.at(0); res += " and "; res += lst.at(1); break; case 3: res += lst.at(0); res += ", "; res += lst.at(1); res += " and "; res += lst.at(2); break; default: break; } return res; } std::string cmQtAutoGen::Quoted(std::string const& text) { const std::array, 9> replaces = { { { "\\", "\\\\" }, { "\"", "\\\"" }, { "\a", "\\a" }, { "\b", "\\b" }, { "\f", "\\f" }, { "\n", "\\n" }, { "\r", "\\r" }, { "\t", "\\t" }, { "\v", "\\v" } } }; std::string res = text; for (auto const& pair : replaces) { cmSystemTools::ReplaceString(res, pair.first, pair.second); } res = '"' + res; res += '"'; return res; } std::string cmQtAutoGen::QuotedCommand(std::vector const& command) { std::string res; for (std::string const& item : command) { if (!res.empty()) { res.push_back(' '); } std::string const cesc = cmQtAutoGen::Quoted(item); if (item.empty() || (cesc.size() > (item.size() + 2)) || (cesc.find(' ') != std::string::npos)) { res += cesc; } else { res += item; } } return res; } std::string cmQtAutoGen::SubDirPrefix(std::string const& filename) { std::string::size_type slash_pos = filename.rfind('/'); if (slash_pos == std::string::npos) { return std::string(); } return filename.substr(0, slash_pos + 1); } std::string cmQtAutoGen::AppendFilenameSuffix(std::string const& filename, std::string const& suffix) { std::string res; auto pos = filename.rfind('.'); if (pos != std::string::npos) { const auto it_dot = filename.begin() + pos; res.assign(filename.begin(), it_dot); res.append(suffix); res.append(it_dot, filename.end()); } else { res = filename; res.append(suffix); } return res; } void cmQtAutoGen::UicMergeOptions(std::vector& baseOpts, std::vector const& newOpts, bool isQt5) { static std::vector const valueOpts = { "tr", "translate", "postfix", "generator", "include", // Since Qt 5.3 "g" }; MergeOptions(baseOpts, newOpts, valueOpts, isQt5); } void cmQtAutoGen::RccMergeOptions(std::vector& baseOpts, std::vector const& newOpts, bool isQt5) { static std::vector const valueOpts = { "name", "root", "compress", "threshold" }; MergeOptions(baseOpts, newOpts, valueOpts, isQt5); } 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 = "rcc lists unparsable output:\n"; error += cmQtAutoGen::Quoted(eline); error += "\n"; return false; } pos += searchString.length(); std::string::size_type sz = eline.size() - pos - 1; files.push_back(eline.substr(pos, sz)); } } } return true; } 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 = "The resource file "; error += Quoted(qrcFile); error += " 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_); cmd.insert(cmd.end(), this->ListOptions_.begin(), this->ListOptions_.end()); cmd.emplace_back(cmSystemTools::GetFilenameName(qrcFile)); // Log command if (verbose) { std::string msg = "Running command:\n"; msg += QuotedCommand(cmd); msg += '\n'; cmSystemTools::Stdout(msg); } result = cmSystemTools::RunSingleCommand( cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(), cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto); } if (!result || retVal) { error = "The rcc list process failed for "; error += Quoted(qrcFile); error += "\n"; if (!rccStdOut.empty()) { error += rccStdOut; error += "\n"; } if (!rccStdErr.empty()) { error += rccStdErr; error += "\n"; } return false; } if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) { return false; } } else { // We can't use rcc for the file listing. // Read the qrc file content into string and parse it. { std::string qrcContents; { cmsys::ifstream ifs(qrcFile.c_str()); if (ifs) { std::ostringstream osst; osst << ifs.rdbuf(); qrcContents = osst.str(); } else { error = "The resource file "; error += Quoted(qrcFile); error += " 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; }