| /* 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 <algorithm> |
| #include <array> |
| #include <initializer_list> |
| #include <sstream> |
| #include <utility> |
| |
| #include <cmext/algorithm> |
| |
| #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<std::string>& baseOpts, |
| std::vector<std::string> const& newOpts, |
| std::initializer_list<cm::string_view> valueOpts, |
| bool isQt5OrLater) |
| { |
| if (newOpts.empty()) { |
| return; |
| } |
| if (baseOpts.empty()) { |
| baseOpts = newOpts; |
| return; |
| } |
| |
| std::vector<std::string> 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; |
| |
| 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<cm::string_view, 3> 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<std::pair<const char*, const char*>> 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<std::string> 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<std::string>& baseOpts, |
| std::vector<std::string> const& newOpts, |
| bool isQt5OrLater) |
| { |
| static std::initializer_list<cm::string_view> const valueOpts = { |
| "tr", "translate", "postfix", "generator", |
| "include", // Since Qt 5.3 |
| "g" |
| }; |
| MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater); |
| } |
| |
| void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts, |
| std::vector<std::string> const& newOpts, |
| bool isQt5OrLater) |
| { |
| static std::initializer_list<cm::string_view> const valueOpts = { |
| "name", "root", "compress", "threshold" |
| }; |
| MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater); |
| } |
| |
| static void RccListParseContent(std::string const& content, |
| std::vector<std::string>& files) |
| { |
| cmsys::RegularExpression fileMatchRegex("(<file[^<]+)"); |
| cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)"); |
| |
| 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<std::string>& 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<std::string> listOptions) |
| : RccExcutable_(std::move(rccExecutable)) |
| , ListOptions_(std::move(listOptions)) |
| { |
| } |
| |
| bool cmQtAutoGen::RccLister::list(std::string const& qrcFile, |
| std::vector<std::string>& 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<std::string> 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; |
| } |