diff options
author | Tobias Hunger <tobias.hunger@qt.io> | 2019-04-16 16:32:08 +0200 |
---|---|---|
committer | Tobias Hunger <tobias.hunger@qt.io> | 2019-04-16 16:32:08 +0200 |
commit | 6630937e63ae5797487b86743a7733c8ae5cc42c (patch) | |
tree | 3d53dacf6430f9099e1fb20835881205de674961 /src/tools/rcc | |
parent | 37ed6dae00640f9cc980ffda05347c12a7eb5d7e (diff) | |
parent | c7af193d2e49e9f10b86262e63d8d13abf72b5cf (diff) |
Merge commit 'dev' into 'wip/cmake-merge'
Change-Id: I176c40d031be26a1dd1cf08843e448a660598783
Diffstat (limited to 'src/tools/rcc')
-rw-r--r-- | src/tools/rcc/main.cpp | 56 | ||||
-rw-r--r-- | src/tools/rcc/rcc.cpp | 461 | ||||
-rw-r--r-- | src/tools/rcc/rcc.h | 29 | ||||
-rw-r--r-- | src/tools/rcc/rcc.pro | 11 |
4 files changed, 493 insertions, 64 deletions
diff --git a/src/tools/rcc/main.cpp b/src/tools/rcc/main.cpp index 12f986b1e2..0eb6766b5a 100644 --- a/src/tools/rcc/main.cpp +++ b/src/tools/rcc/main.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. @@ -127,10 +128,25 @@ int runRcc(int argc, char *argv[]) QCommandLineOption rootOption(QStringLiteral("root"), QStringLiteral("Prefix resource access path with root path."), QStringLiteral("path")); parser.addOption(rootOption); +#if QT_CONFIG(zstd) && !defined(QT_NO_COMPRESS) +# define ALGOS "[zstd], zlib, none" +#elif QT_CONFIG(zstd) +# define ALGOS "[zstd], none" +#elif !defined(QT_NO_COMPRESS) +# define ALGOS "[zlib], none" +#else +# define ALGOS "[none]" +#endif + const QString &algoDescription = + QStringLiteral("Compress input files using algorithm <algo> (" ALGOS ")."); + QCommandLineOption compressionAlgoOption(QStringLiteral("compress-algo"), algoDescription, QStringLiteral("algo")); + parser.addOption(compressionAlgoOption); +#undef ALGOS + QCommandLineOption compressOption(QStringLiteral("compress"), QStringLiteral("Compress input files by <level>."), QStringLiteral("level")); parser.addOption(compressOption); - QCommandLineOption nocompressOption(QStringLiteral("no-compress"), QStringLiteral("Disable all compression.")); + QCommandLineOption nocompressOption(QStringLiteral("no-compress"), QStringLiteral("Disable all compression. Same as --compress-algo=none.")); parser.addOption(nocompressOption); QCommandLineOption thresholdOption(QStringLiteral("threshold"), QStringLiteral("Threshold to consider compressing files."), QStringLiteral("level")); @@ -139,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); @@ -169,13 +190,13 @@ int runRcc(int argc, char *argv[]) QString errorMsg; - quint8 formatVersion = 2; + quint8 formatVersion = 3; if (parser.isSet(formatVersionOption)) { bool ok = false; formatVersion = parser.value(formatVersionOption).toUInt(&ok); if (!ok) { errorMsg = QLatin1String("Invalid format version specified"); - } else if (formatVersion != 1 && formatVersion != 2) { + } else if (formatVersion < 1 || formatVersion > 3) { errorMsg = QLatin1String("Unsupported format version specified"); } } @@ -189,14 +210,33 @@ int runRcc(int argc, char *argv[]) || library.resourceRoot().at(0) != QLatin1Char('/')) errorMsg = QLatin1String("Root must start with a /"); } - if (parser.isSet(compressOption)) - library.setCompressLevel(parser.value(compressOption).toInt()); + + if (parser.isSet(compressionAlgoOption)) + library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg)); + if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd) + errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher"); if (parser.isSet(nocompressOption)) - library.setCompressLevel(-2); + library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); + if (parser.isSet(compressOption) && errorMsg.isEmpty()) { + int level = library.parseCompressionLevel(library.compressionAlgorithm(), parser.value(compressOption), &errorMsg); + library.setCompressLevel(level); + } if (parser.isSet(thresholdOption)) 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); @@ -257,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: diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp index 1a7cab01df..bb4e6e3615 100644 --- a/src/tools/rcc/rcc.cpp +++ b/src/tools/rcc/rcc.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. @@ -41,6 +42,10 @@ #include <algorithm> +#if QT_CONFIG(zstd) +# include <zstd.h> +#endif + // Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) QT_BEGIN_NAMESPACE @@ -48,9 +53,18 @@ QT_BEGIN_NAMESPACE enum { CONSTANT_USENAMESPACE = 1, CONSTANT_COMPRESSLEVEL_DEFAULT = -1, + CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea + CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 }; +#if QT_CONFIG(zstd) +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd +#elif !defined(QT_NO_COMPRESS) +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib +#else +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None +#endif #define writeString(s) write(s, sizeof(s)) @@ -88,15 +102,18 @@ class RCCFileInfo public: enum Flags { + // must match qresource.cpp NoFlags = 0x00, Compressed = 0x01, - Directory = 0x02 + Directory = 0x02, + CompressedZstd = 0x04 }; RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), QLocale::Language language = QLocale::C, QLocale::Country country = QLocale::AnyCountry, uint flags = NoFlags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); ~RCCFileInfo(); @@ -115,6 +132,7 @@ public: QFileInfo m_fileInfo; RCCFileInfo *m_parent; QHash<QString, RCCFileInfo*> m_children; + RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; int m_compressLevel; int m_compressThreshold; @@ -125,7 +143,7 @@ public: RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, QLocale::Country country, uint flags, - int compressLevel, int compressThreshold) + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold) { m_name = name; m_fileInfo = fileInfo; @@ -136,6 +154,7 @@ RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, m_nameOffset = 0; m_dataOffset = 0; m_childOffset = 0; + m_compressAlgo = compressAlgo; m_compressLevel = compressLevel; m_compressThreshold = compressThreshold; } @@ -157,6 +176,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) { @@ -203,6 +224,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 @@ -211,9 +234,14 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); if (sourceDate != 0) lastmod = sourceDate; + static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); + if (sourceDate2 != 0) + lastmod = sourceDate2; lib.writeNumber8(lastmod); if (text || pass1) lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); } } @@ -224,6 +252,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; @@ -236,19 +266,82 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, } QByteArray data = file.readAll(); -#ifndef QT_NO_COMPRESS // Check if compression is useful for this file - if (m_compressLevel != 0 && data.size() != 0) { - QByteArray compressed = - qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); - - int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); - if (compressRatio >= m_compressThreshold) { - data = compressed; - m_flags |= Compressed; + if (data.size() != 0) { +#if QT_CONFIG(zstd) + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; + m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd) { + if (lib.m_zstdCCtx == nullptr) + lib.m_zstdCCtx = ZSTD_createCCtx(); + qsizetype size = data.size(); + size = ZSTD_COMPRESSBOUND(size); + + int compressLevel = m_compressLevel; + if (compressLevel < 0) + compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; + + QByteArray compressed(size, Qt::Uninitialized); + char *dst = const_cast<char *>(compressed.constData()); + size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + compressLevel); + if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { + // compressing is worth it + if (m_compressLevel < 0) { + // heuristic compression, so recompress + n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + CONSTANT_ZSTDCOMPRESSLEVEL_STORE); + } + if (ZSTD_isError(n)) { + QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") + .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); + lib.m_errorDevice->write(msg.toUtf8()); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(n); + lib.m_errorDevice->write(msg.toUtf8()); + } + + lib.m_overallFlags |= CompressedZstd; + m_flags |= CompressedZstd; + data = std::move(compressed); + data.truncate(n); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif +#ifndef QT_NO_COMPRESS + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; + m_compressLevel = 9; + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { + QByteArray compressed = + qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); + + int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); + if (compressRatio >= m_compressThreshold) { + if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(compressed.size()); + lib.m_errorDevice->write(msg.toUtf8()); + } + data = compressed; + lib.m_overallFlags |= Compressed; + m_flags |= Compressed; + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } } - } #endif // QT_NO_COMPRESS + } // some info if (text || pass1) { @@ -258,20 +351,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; } } @@ -283,6 +380,9 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, // done if (text || pass1) lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + return offset; } @@ -290,6 +390,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; @@ -305,12 +407,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 @@ -319,12 +425,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; } @@ -343,7 +454,8 @@ RCCResourceLibrary::Strings::Strings() : ATTRIBUTE_PREFIX(QLatin1String("prefix")), ATTRIBUTE_ALIAS(QLatin1String("alias")), ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), - ATTRIBUTE_COMPRESS(QLatin1String("compress")) + ATTRIBUTE_COMPRESS(QLatin1String("compress")), + ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) { } @@ -351,22 +463,30 @@ RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) : m_root(0), m_format(C_Code), m_verbose(false), + m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), m_treeOffset(0), m_namesOffset(0), m_dataOffset(0), + m_overallFlags(0), m_useNameSpace(CONSTANT_USENAMESPACE), m_errorDevice(0), m_outDevice(0), m_formatVersion(formatVersion) { m_out.reserve(30 * 1000 * 1000); +#if QT_CONFIG(zstd) + m_zstdCCtx = nullptr; +#endif } RCCResourceLibrary::~RCCResourceLibrary() { delete m_root; +#if QT_CONFIG(zstd) + ZSTD_freeCCtx(m_zstdCCtx); +#endif } enum RCCXmlTag { @@ -391,6 +511,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, QLocale::Language language = QLocale::c().language(); QLocale::Country country = QLocale::c().country(); QString alias; + auto compressAlgo = m_compressionAlgo; int compressLevel = m_compressLevel; int compressThreshold = m_compressThreshold; @@ -444,17 +565,27 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); + compressAlgo = m_compressionAlgo; compressLevel = m_compressLevel; - if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) - compressLevel = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString().toInt(); - compressThreshold = m_compressThreshold; + + QString errorString; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) + compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); + if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { + QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); + compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); + } + + // Special case for -no-compress + if (m_compressLevel == -2) + compressAlgo = CompressionAlgorithm::None; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); - // Special case for -no-compress. Overrides all other settings. - if (m_compressLevel == -2) - compressLevel = 0; + if (!errorString.isEmpty()) + reader.raiseError(errorString); } } else { reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString())); @@ -520,6 +651,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, language, country, child.isDir() ? RCCFileInfo::Directory : RCCFileInfo::NoFlags, + compressAlgo, compressLevel, compressThreshold) ); @@ -535,6 +667,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, language, country, RCCFileInfo::NoFlags, + compressAlgo, compressLevel, compressThreshold) ); @@ -729,6 +862,55 @@ RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap( return rc; } +RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) +{ + if (value == QLatin1String("best")) + return CompressionAlgorithm::Best; + if (value == QLatin1String("zlib")) { +#ifdef QT_NO_COMPRESS + *errorMsg = QLatin1String("zlib support not compiled in"); +#else + return CompressionAlgorithm::Zlib; +#endif + } else if (value == QLatin1String("zstd")) { +#if QT_CONFIG(zstd) + return CompressionAlgorithm::Zstd; +#else + *errorMsg = QLatin1String("Zstandard support not compiled in"); +#endif + } else if (value != QLatin1String("none")) { + *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); + } + + return CompressionAlgorithm::None; +} + +int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) +{ + bool ok; + int c = level.toInt(&ok); + if (ok) { + switch (algo) { + case CompressionAlgorithm::None: + case CompressionAlgorithm::Best: + return 0; + case CompressionAlgorithm::Zlib: + if (c >= 1 && c <= 9) + return c; + break; + case CompressionAlgorithm::Zstd: +#if QT_CONFIG(zstd) + if (c >= 0 && c <= ZSTD_maxCLevel()) + return c; +#endif + break; + } + } + + *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); + return 0; +} + bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) { m_errorDevice = &errorDevice; @@ -795,18 +977,45 @@ bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIO return true; } +void RCCResourceLibrary::writeDecimal(int value) +{ + Q_ASSERT(m_format != RCCResourceLibrary::Binary); + char buf[std::numeric_limits<int>::digits10 + 2]; + int n = snprintf(buf, sizeof(buf), "%d", 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) @@ -874,7 +1083,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"); @@ -883,12 +1094,30 @@ 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("# Created by: object code\n"); + writeString("# Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n"); + writeString("# WARNING! All changes made in this file will be lost!\n\n"); + writeString("from PySide2 import QtCore\n\n"); + break; + case Binary: writeString("qres"); writeNumber4(0); writeNumber4(0); writeNumber4(0); writeNumber4(0); + if (m_formatVersion >= 3) + writeNumber4(m_overallFlags); + break; + default: + break; } return true; } @@ -896,10 +1125,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) @@ -925,24 +1165,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; @@ -967,8 +1229,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; } @@ -983,10 +1255,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) @@ -1030,8 +1316,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; } @@ -1091,10 +1387,35 @@ bool RCCResourceLibrary::writeInitializer() if (m_root) { writeString("bool qRegisterResourceData" "(int, const unsigned char *, " - "const unsigned char *, const unsigned char *);\n\n"); + "const unsigned char *, const unsigned char *);\n"); writeString("bool qUnregisterResourceData" "(int, const unsigned char *, " "const unsigned char *, const unsigned char *);\n\n"); + + if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { + // use variable relocations with ELF and Mach-O + writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("static inline unsigned char qResourceFeatureZlib()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZlib;\n" + " return qt_resourceFeatureZlib;\n" + "}\n"); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("static inline unsigned char qResourceFeatureZstd()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZstd;\n" + " return qt_resourceFeatureZstd;\n" + "}\n"); + } + writeString("#else\n"); + if (m_overallFlags & RCCFileInfo::Compressed) + writeString("unsigned char qResourceFeatureZlib();\n"); + if (m_overallFlags & RCCFileInfo::CompressedZstd) + writeString("unsigned char qResourceFeatureZstd();\n"); + writeString("#endif\n\n"); + } } if (m_useNameSpace) @@ -1113,12 +1434,12 @@ bool RCCResourceLibrary::writeInitializer() writeString("()\n{\n"); if (m_root) { - writeString(" "); + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); writeAddNamespaceFunction("qRegisterResourceData"); - writeString("\n ("); - writeHex(m_formatVersion); - writeString(" qt_resource_struct, " - "qt_resource_name, qt_resource_data);\n"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); } writeString(" return 1;\n"); writeString("}\n\n"); @@ -1136,11 +1457,24 @@ bool RCCResourceLibrary::writeInitializer() writeMangleNamespaceFunction(cleanResources); writeString("()\n{\n"); if (m_root) { - writeString(" "); + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + + // ODR-use certain symbols from QtCore if we require optional features + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZlib()"); + writeString(";\n "); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZstd()"); + writeString(";\n "); + } + writeAddNamespaceFunction("qUnregisterResourceData"); - writeString("\n ("); - writeHex(m_formatVersion); - writeString(" qt_resource_struct, " + writeString("\n (version, qt_resource_struct, " "qt_resource_name, qt_resource_data);\n"); } writeString(" return 1;\n"); @@ -1176,6 +1510,23 @@ bool RCCResourceLibrary::writeInitializer() p[i++] = (m_namesOffset >> 16) & 0xff; p[i++] = (m_namesOffset >> 8) & 0xff; p[i++] = (m_namesOffset >> 0) & 0xff; + + if (m_formatVersion >= 3) { + p[i++] = (m_overallFlags >> 24) & 0xff; + p[i++] = (m_overallFlags >> 16) & 0xff; + p[i++] = (m_overallFlags >> 8) & 0xff; + p[i++] = (m_overallFlags >> 0) & 0xff; + } + } else if (m_format == Python3_Code || m_format == Python2_Code) { + writeString("def qInitResources():\n"); + writeString(" QtCore.qRegisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("def qCleanupResources():\n"); + writeString(" QtCore.qUnregisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("qInitResources()\n"); } return true; } diff --git a/src/tools/rcc/rcc.h b/src/tools/rcc/rcc.h index 36984cf38a..b301355e4f 100644 --- a/src/tools/rcc/rcc.h +++ b/src/tools/rcc/rcc.h @@ -1,6 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. @@ -35,6 +36,8 @@ #include <qhash.h> #include <qstring.h> +typedef struct ZSTD_CCtx_s ZSTD_CCtx; + QT_BEGIN_NAMESPACE class RCCFileInfo; @@ -55,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; } @@ -77,6 +80,19 @@ public: void setOutputName(const QString &name) { m_outputName = name; } QString outputName() const { return m_outputName; } + enum class CompressionAlgorithm { + Zlib, + Zstd, + + Best = 99, + None = -1 + }; + + static CompressionAlgorithm parseCompressionAlgorithm(QStringView algo, QString *errorMsg); + void setCompressionAlgorithm(CompressionAlgorithm algo) { m_compressionAlgo = algo; } + CompressionAlgorithm compressionAlgorithm() const { return m_compressionAlgo; } + + static int parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg); void setCompressLevel(int c) { m_compressLevel = c; } int compressLevel() const { return m_compressLevel; } @@ -104,6 +120,7 @@ private: const QString ATTRIBUTE_ALIAS; const QString ATTRIBUTE_THRESHOLD; const QString ATTRIBUTE_COMPRESS; + const QString ATTRIBUTE_COMPRESSALGO; }; friend class RCCFileInfo; void reset(); @@ -117,7 +134,9 @@ private: bool writeInitializer(); void writeMangleNamespaceFunction(const QByteArray &name); 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); @@ -125,6 +144,10 @@ private: void writeByteArray(const QByteArray &); void write(const char *, int len); +#if QT_CONFIG(zstd) + ZSTD_CCtx *m_zstdCCtx; +#endif + const Strings m_strings; RCCFileInfo *m_root; QStringList m_fileNames; @@ -133,11 +156,13 @@ private: QString m_outputName; Format m_format; bool m_verbose; + CompressionAlgorithm m_compressionAlgo; int m_compressLevel; int m_compressThreshold; int m_treeOffset; int m_namesOffset; int m_dataOffset; + quint32 m_overallFlags; bool m_useNameSpace; QStringList m_failedResources; QIODevice *m_errorDevice; diff --git a/src/tools/rcc/rcc.pro b/src/tools/rcc/rcc.pro index 208ec54a73..ae55b5d8af 100644 --- a/src/tools/rcc/rcc.pro +++ b/src/tools/rcc/rcc.pro @@ -8,3 +8,14 @@ SOURCES += main.cpp QMAKE_TARGET_DESCRIPTION = "Qt Resource Compiler" load(qt_tool) + +# RCC is a bootstrapped tool, so qglobal.h #includes qconfig-bootstrapped.h +# and that has a #define saying zstd isn't present (for qresource.cpp, which is +# part of the bootstrap lib). So we inform the presence of the feature in the +# command-line. +qtConfig(zstd):!cross_compile { + DEFINES += QT_FEATURE_zstd=1 + QMAKE_USE_PRIVATE += zstd +} else { + DEFINES += QT_FEATURE_zstd=-1 +} |