|
|
|
/* 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;
|
|
|
|
|
|
|
|
#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<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;
|
|
|
|
}
|