rcc: Support Python as output format

Start with  rcc -g python|python2 $name.qrc.

[ChangeLog][rcc] Added support for Python as output format.

Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Fixes: PYSIDE-855
Change-Id: I97a642c3721d6d95b7cd0972d21abb0b2752fd4f
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
This commit is contained in:
hjk 2019-03-01 12:55:13 +01:00 committed by Friedemann Kleint
parent de1e15af44
commit f91aae6397
5 changed files with 306 additions and 26 deletions

View File

@ -155,6 +155,11 @@ int runRcc(int argc, char *argv[])
QCommandLineOption binaryOption(QStringLiteral("binary"), QStringLiteral("Output a binary file for use as a dynamic resource."));
parser.addOption(binaryOption);
QCommandLineOption generatorOption(QStringList{QStringLiteral("g"), QStringLiteral("generator")});
generatorOption.setDescription(QStringLiteral("Select generator."));
generatorOption.setValueName(QStringLiteral("cpp|python|python2"));
parser.addOption(generatorOption);
QCommandLineOption passOption(QStringLiteral("pass"), QStringLiteral("Pass number for big resources"), QStringLiteral("number"));
parser.addOption(passOption);
@ -220,6 +225,18 @@ int runRcc(int argc, char *argv[])
library.setCompressThreshold(parser.value(thresholdOption).toInt());
if (parser.isSet(binaryOption))
library.setFormat(RCCResourceLibrary::Binary);
if (parser.isSet(generatorOption)) {
auto value = parser.value(generatorOption);
if (value == QLatin1String("cpp"))
library.setFormat(RCCResourceLibrary::C_Code);
else if (value == QLatin1String("python"))
library.setFormat(RCCResourceLibrary::Python3_Code);
else if (value == QLatin1String("python2"))
library.setFormat(RCCResourceLibrary::Python2_Code);
else
errorMsg = QLatin1String("Invalid generator: ") + value;
}
if (parser.isSet(passOption)) {
if (parser.value(passOption) == QLatin1String("1"))
library.setFormat(RCCResourceLibrary::Pass1);
@ -280,6 +297,8 @@ int runRcc(int argc, char *argv[])
switch (library.format()) {
case RCCResourceLibrary::C_Code:
case RCCResourceLibrary::Pass1:
case RCCResourceLibrary::Python3_Code:
case RCCResourceLibrary::Python2_Code:
mode = QIODevice::WriteOnly | QIODevice::Text;
break;
case RCCResourceLibrary::Pass2:

View File

@ -68,6 +68,17 @@ enum {
#define writeString(s) write(s, sizeof(s))
static const char pythonHeader1[] =
R"(# Created by: object code
# Created by: The Resource Compiler for Qt version )";
static const char pythonHeader2[] = R"(
# WARNING! All changes made in this file will be lost!
from PySide2 import QtCore
)";
void RCCResourceLibrary::write(const char *str, int len)
{
--len; // trailing \0 on string literals...
@ -176,6 +187,8 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
{
const bool text = lib.m_format == RCCResourceLibrary::C_Code;
const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
|| lib.m_format == RCCResourceLibrary::Python2_Code;
//some info
if (text || pass1) {
if (m_language != QLocale::C) {
@ -222,6 +235,8 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
}
if (text || pass1)
lib.writeChar('\n');
else if (python)
lib.writeString("\\\n");
if (lib.formatVersion() >= 2) {
// last modified time stamp
@ -236,6 +251,8 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
lib.writeNumber8(lastmod);
if (text || pass1)
lib.writeChar('\n');
else if (python)
lib.writeString("\\\n");
}
}
@ -246,6 +263,8 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2;
const bool binary = lib.m_format == RCCResourceLibrary::Binary;
const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
|| lib.m_format == RCCResourceLibrary::Python2_Code;
//capture the offset
m_dataOffset = offset;
@ -343,20 +362,24 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
}
// write the length
if (text || binary || pass2)
if (text || binary || pass2 || python)
lib.writeNumber4(data.size());
if (text || pass1)
lib.writeString("\n ");
else if (python)
lib.writeString("\\\n");
offset += 4;
// write the payload
const char *p = data.constData();
if (text) {
if (text || python) {
for (int i = data.size(), j = 0; --i >= 0; --j) {
lib.writeHex(*p++);
if (j == 0) {
lib.writeString("\n ");
if (text)
lib.writeString("\n ");
else
lib.writeString("\\\n");
j = 16;
}
}
@ -368,6 +391,9 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
// done
if (text || pass1)
lib.writeString("\n ");
else if (python)
lib.writeString("\\\n");
return offset;
}
@ -375,6 +401,8 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
{
const bool text = lib.m_format == RCCResourceLibrary::C_Code;
const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
|| lib.m_format == RCCResourceLibrary::Python2_Code;
// capture the offset
m_nameOffset = offset;
@ -390,12 +418,16 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
lib.writeNumber2(m_name.length());
if (text || pass1)
lib.writeString("\n ");
else if (python)
lib.writeString("\\\n");
offset += 2;
// write the hash
lib.writeNumber4(qt_hash(m_name));
if (text || pass1)
lib.writeString("\n ");
else if (python)
lib.writeString("\\\n");
offset += 4;
// write the m_name
@ -404,12 +436,17 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
lib.writeNumber2(unicode[i].unicode());
if ((text || pass1) && i % 16 == 0)
lib.writeString("\n ");
else if (python && i % 16 == 0)
lib.writeString("\\\n");
}
offset += m_name.length()*2;
// done
if (text || pass1)
lib.writeString("\n ");
else if (python)
lib.writeString("\\\n");
return offset;
}
@ -959,18 +996,37 @@ void RCCResourceLibrary::writeDecimal(int value)
write(buf, n + 1); // write() takes a size including terminating NUL
}
static const char hexDigits[] = "0123456789abcdef";
inline void RCCResourceLibrary::write2HexDigits(quint8 number)
{
writeChar(hexDigits[number >> 4]);
writeChar(hexDigits[number & 0xf]);
}
void RCCResourceLibrary::writeHex(quint8 tmp)
{
const char digits[] = "0123456789abcdef";
writeChar('0');
writeChar('x');
if (tmp < 16) {
writeChar(digits[tmp]);
} else {
writeChar(digits[tmp >> 4]);
writeChar(digits[tmp & 0xf]);
switch (m_format) {
case RCCResourceLibrary::Python3_Code:
case RCCResourceLibrary::Python2_Code:
if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') {
writeChar(char(tmp));
} else {
writeChar('\\');
writeChar('x');
write2HexDigits(tmp);
}
break;
default:
writeChar('0');
writeChar('x');
if (tmp < 16)
writeChar(hexDigits[tmp]);
else
write2HexDigits(tmp);
writeChar(',');
break;
}
writeChar(',');
}
void RCCResourceLibrary::writeNumber2(quint16 number)
@ -1038,7 +1094,9 @@ void RCCResourceLibrary::writeNumber8(quint64 number)
bool RCCResourceLibrary::writeHeader()
{
if (m_format == C_Code || m_format == Pass1) {
switch (m_format) {
case C_Code:
case Pass1:
writeString("/****************************************************************************\n");
writeString("** Resource object code\n");
writeString("**\n");
@ -1047,7 +1105,17 @@ bool RCCResourceLibrary::writeHeader()
writeString("\n**\n");
writeString("** WARNING! All changes made in this file will be lost!\n");
writeString( "*****************************************************************************/\n\n");
} else if (m_format == Binary) {
break;
case Python3_Code:
case Python2_Code:
writeString("# Resource object code (Python ");
writeChar(m_format == Python3_Code ? '3' : '2');
writeString(")\n");
writeString(pythonHeader1);
writeByteArray(QT_VERSION_STR);
writeString(pythonHeader2);
break;
case Binary:
writeString("qres");
writeNumber4(0);
writeNumber4(0);
@ -1055,6 +1123,9 @@ bool RCCResourceLibrary::writeHeader()
writeNumber4(0);
if (m_formatVersion >= 3)
writeNumber4(m_overallFlags);
break;
default:
break;
}
return true;
}
@ -1062,10 +1133,21 @@ bool RCCResourceLibrary::writeHeader()
bool RCCResourceLibrary::writeDataBlobs()
{
Q_ASSERT(m_errorDevice);
if (m_format == C_Code) {
switch (m_format) {
case C_Code:
writeString("static const unsigned char qt_resource_data[] = {\n");
} else if (m_format == Binary) {
break;
case Python3_Code:
writeString("qt_resource_data = b\"\\\n");
break;
case Python2_Code:
writeString("qt_resource_data = \"\\\n");
break;
case Binary:
m_dataOffset = m_out.size();
break;
default:
break;
}
if (!m_root)
@ -1091,24 +1173,46 @@ bool RCCResourceLibrary::writeDataBlobs()
}
}
}
if (m_format == C_Code)
switch (m_format) {
case C_Code:
writeString("\n};\n\n");
else if (m_format == Pass1) {
break;
case Python3_Code:
case Python2_Code:
writeString("\"\n\n");
break;
case Pass1:
if (offset < 8)
offset = 8;
writeString("\nstatic const unsigned char qt_resource_data[");
writeByteArray(QByteArray::number(offset));
writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n");
break;
default:
break;
}
return true;
}
bool RCCResourceLibrary::writeDataNames()
{
if (m_format == C_Code || m_format == Pass1)
switch (m_format) {
case C_Code:
case Pass1:
writeString("static const unsigned char qt_resource_name[] = {\n");
else if (m_format == Binary)
break;
case Python3_Code:
writeString("qt_resource_name = b\"\\\n");
break;
case Python2_Code:
writeString("qt_resource_name = \"\\\n");
break;
case Binary:
m_namesOffset = m_out.size();
break;
default:
break;
}
QHash<QString, int> names;
QStack<RCCFileInfo*> pending;
@ -1133,8 +1237,18 @@ bool RCCResourceLibrary::writeDataNames()
}
}
}
if (m_format == C_Code || m_format == Pass1)
switch (m_format) {
case C_Code:
case Pass1:
writeString("\n};\n\n");
break;
case Python3_Code:
case Python2_Code:
writeString("\"\n\n");
break;
default:
break;
}
return true;
}
@ -1149,10 +1263,24 @@ struct qt_rcc_compare_hash
bool RCCResourceLibrary::writeDataStructure()
{
if (m_format == C_Code || m_format == Pass1)
switch (m_format) {
case C_Code:
case Pass1:
writeString("static const unsigned char qt_resource_struct[] = {\n");
else if (m_format == Binary)
break;
case Python3_Code:
writeString("qt_resource_struct = b\"\\\n");
break;
case Python2_Code:
writeString("qt_resource_struct = \"\\\n");
break;
case Binary:
m_treeOffset = m_out.size();
break;
default:
break;
}
QStack<RCCFileInfo*> pending;
if (!m_root)
@ -1196,8 +1324,18 @@ bool RCCResourceLibrary::writeDataStructure()
pending.push(child);
}
}
if (m_format == C_Code || m_format == Pass1)
switch (m_format) {
case C_Code:
case Pass1:
writeString("\n};\n\n");
break;
case Python3_Code:
case Python2_Code:
writeString("\"\n\n");
break;
default:
break;
}
return true;
}
@ -1387,6 +1525,19 @@ bool RCCResourceLibrary::writeInitializer()
p[i++] = (m_overallFlags >> 8) & 0xff;
p[i++] = (m_overallFlags >> 0) & 0xff;
}
} else if (m_format == Python3_Code || m_format == Python2_Code) {
writeString(R"(def qInitResources():
QtCore.qRegisterResourceData(0x)");
write2HexDigits(m_formatVersion);
writeString(R"(, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(0x)");
write2HexDigits(m_formatVersion);
writeString(R"(, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
)");
}
return true;
}

View File

@ -58,7 +58,7 @@ public:
bool readFiles(bool listMode, QIODevice &errorDevice);
enum Format { Binary, C_Code, Pass1, Pass2 };
enum Format { Binary, C_Code, Pass1, Pass2, Python3_Code, Python2_Code };
void setFormat(Format f) { m_format = f; }
Format format() const { return m_format; }
@ -136,6 +136,7 @@ private:
void writeAddNamespaceFunction(const QByteArray &name);
void writeDecimal(int value);
void writeHex(quint8 number);
void write2HexDigits(quint8 number);
void writeNumber2(quint16 number);
void writeNumber4(quint32 number);
void writeNumber8(quint64 number);

View File

@ -0,0 +1,68 @@
# Resource object code (Python 3)
# Created by: object code
# Created by: The Resource Compiler for Qt version 5.14.0
# WARNING! All changes made in this file will be lost!
from PySide2 import QtCore
qt_resource_data = b"\
\x00\x00\x00\x02\
0\
1\
\x00\x00\x00#\
0\
123456789 012345\
6789 0123456789 \
12\
\x00\x00\x00\x01\
@\
\
\x00\x00\x00\x00\
\
"
qt_resource_name = b"\
\x00\x04\
\x00\x06\xa8\xa1\
\x00d\
\x00a\x00t\x00a\
\x00\x0a\
\x04\x08\x0a\xb4\
\x00d\
\x00a\x00t\x00a\x00-\x002\x00.\x00t\x00x\x00t\
\x00\x0b\
\x00\xb5Ot\
\x00d\
\x00a\x00t\x00a\x00-\x003\x005\x00.\x00t\x00x\x00t\
\x00\x0a\
\x04\x11\x0a\xb4\
\x00d\
\x00a\x00t\x00a\x00-\x001\x00.\x00t\x00x\x00t\
\x00\x0a\
\x04\x0e\x0a\xb4\
\x00d\
\x00a\x00t\x00a\x00-\x000\x00.\x00t\x00x\x00t\
"
qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x06\
IGNORE: (time stamp)
\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
IGNORE: (time stamp)
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x002\
IGNORE: (time stamp)
\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00-\
IGNORE: (time stamp)
"
def qInitResources():
QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()

View File

@ -89,6 +89,8 @@ private slots:
void readback_data();
void readback();
void python();
void cleanupTestCase();
private:
@ -107,6 +109,12 @@ void tst_rcc::initTestCase()
QVERIFY(!m_dataPath.isEmpty());
}
static inline bool isPythonComment(const QString &line)
{
return line.startsWith(QLatin1Char('#'));
}
static QString doCompare(const QStringList &actual, const QStringList &expected,
const QString &timeStampPath)
{
@ -116,10 +124,13 @@ static QString doCompare(const QStringList &actual, const QStringList &expected,
}
QByteArray ba;
const bool isPython = isPythonComment(expected.constFirst());
for (int i = 0, n = expected.size(); i != n; ++i) {
QString expectedLine = expected.at(i);
if (expectedLine.startsWith("IGNORE:"))
continue;
if (isPython && isPythonComment(expectedLine) && isPythonComment(actual.at(i)))
continue;
if (expectedLine.startsWith("TIMESTAMP:")) {
const QString relativePath = expectedLine.mid(strlen("TIMESTAMP:"));
const QFileInfo fi(timeStampPath + QLatin1Char('/') + relativePath);
@ -405,6 +416,36 @@ void tst_rcc::readback()
QCOMPARE(resourceData, fileSystemData);
}
void tst_rcc::python()
{
const QString path = m_dataPath + QLatin1String("/sizes");
const QString testFileRoot = path + QLatin1String("/size-2-0-35-1");
const QString qrcFile = testFileRoot + QLatin1String(".qrc");
const QString expectedFile = testFileRoot + QLatin1String("_python.expected");
const QString actualFile = testFileRoot + QLatin1String(".rcc");
QProcess process;
process.setWorkingDirectory(path);
process.start(m_rcc, { "-g", "python", "-o", actualFile, qrcFile});
QVERIFY2(process.waitForStarted(), msgProcessStartFailed(process).constData());
if (!process.waitForFinished()) {
process.kill();
QFAIL(msgProcessTimeout(process).constData());
}
QVERIFY2(process.exitStatus() == QProcess::NormalExit,
msgProcessCrashed(process).constData());
QVERIFY2(process.exitCode() == 0,
msgProcessFailed(process).constData());
const auto actualLines = readLinesFromFile(actualFile, QString::KeepEmptyParts);
QVERIFY(!actualLines.isEmpty());
const auto expectedLines = readLinesFromFile(expectedFile, QString::KeepEmptyParts);
QVERIFY(!expectedLines.isEmpty());
const QString diff = doCompare(actualLines, expectedLines, path);
if (!diff.isEmpty())
QFAIL(qPrintable(diff));
}
void tst_rcc::cleanupTestCase()
{
QDir dataDir(m_dataPath + QLatin1String("/binary"));