1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Copyright (C) 2018 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the tools applications of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | #include "rcc.h" |
31 | |
32 | #include <qbytearray.h> |
33 | #include <qdatetime.h> |
34 | #include <qdebug.h> |
35 | #include <qdir.h> |
36 | #include <qdiriterator.h> |
37 | #include <qfile.h> |
38 | #include <qiodevice.h> |
39 | #include <qlocale.h> |
40 | #include <qstack.h> |
41 | #include <qxmlstream.h> |
42 | |
43 | #include <algorithm> |
44 | |
45 | #if QT_CONFIG(zstd) |
46 | # include <zstd.h> |
47 | #endif |
48 | |
49 | // Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | enum { |
54 | CONSTANT_USENAMESPACE = 1, |
55 | CONSTANT_COMPRESSLEVEL_DEFAULT = -1, |
56 | CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea |
57 | CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data |
58 | CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 |
59 | }; |
60 | |
61 | #if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0) |
62 | # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd |
63 | #elif !defined(QT_NO_COMPRESS) |
64 | # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib |
65 | #else |
66 | # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None |
67 | #endif |
68 | |
69 | void RCCResourceLibrary::write(const char *str, int len) |
70 | { |
71 | int n = m_out.size(); |
72 | m_out.resize(n + len); |
73 | memcpy(m_out.data() + n, str, len); |
74 | } |
75 | |
76 | void RCCResourceLibrary::writeByteArray(const QByteArray &other) |
77 | { |
78 | if (m_format == Pass2) { |
79 | m_outDevice->write(other); |
80 | } else { |
81 | m_out.append(other); |
82 | } |
83 | } |
84 | |
85 | static inline QString msgOpenReadFailed(const QString &fname, const QString &why) |
86 | { |
87 | return QString::fromLatin1("Unable to open %1 for reading: %2\n" ).arg(fname, why); |
88 | } |
89 | |
90 | |
91 | /////////////////////////////////////////////////////////// |
92 | // |
93 | // RCCFileInfo |
94 | // |
95 | /////////////////////////////////////////////////////////// |
96 | |
97 | class RCCFileInfo |
98 | { |
99 | public: |
100 | enum Flags |
101 | { |
102 | // must match qresource.cpp |
103 | NoFlags = 0x00, |
104 | Compressed = 0x01, |
105 | Directory = 0x02, |
106 | CompressedZstd = 0x04 |
107 | }; |
108 | |
109 | RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), |
110 | QLocale::Language language = QLocale::C, |
111 | QLocale::Country country = QLocale::AnyCountry, |
112 | uint flags = NoFlags, |
113 | RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, |
114 | int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, |
115 | int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT, |
116 | bool noZstd = false); |
117 | ~RCCFileInfo(); |
118 | |
119 | QString resourceName() const; |
120 | |
121 | public: |
122 | qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); |
123 | qint64 writeDataName(RCCResourceLibrary &, qint64 offset); |
124 | void writeDataInfo(RCCResourceLibrary &lib); |
125 | |
126 | int m_flags; |
127 | QString m_name; |
128 | QLocale::Language m_language; |
129 | QLocale::Country m_country; |
130 | QFileInfo m_fileInfo; |
131 | RCCFileInfo *m_parent; |
132 | QMultiHash<QString, RCCFileInfo *> m_children; |
133 | RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; |
134 | int m_compressLevel; |
135 | int m_compressThreshold; |
136 | |
137 | qint64 m_nameOffset; |
138 | qint64 m_dataOffset; |
139 | qint64 m_childOffset; |
140 | bool m_noZstd; |
141 | }; |
142 | |
143 | RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, |
144 | QLocale::Language language, QLocale::Country country, uint flags, |
145 | RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold, |
146 | bool noZstd) |
147 | { |
148 | m_name = name; |
149 | m_fileInfo = fileInfo; |
150 | m_language = language; |
151 | m_country = country; |
152 | m_flags = flags; |
153 | m_parent = nullptr; |
154 | m_nameOffset = 0; |
155 | m_dataOffset = 0; |
156 | m_childOffset = 0; |
157 | m_compressAlgo = compressAlgo; |
158 | m_compressLevel = compressLevel; |
159 | m_compressThreshold = compressThreshold; |
160 | m_noZstd = noZstd; |
161 | } |
162 | |
163 | RCCFileInfo::~RCCFileInfo() |
164 | { |
165 | qDeleteAll(m_children); |
166 | } |
167 | |
168 | QString RCCFileInfo::resourceName() const |
169 | { |
170 | QString resource = m_name; |
171 | for (RCCFileInfo *p = m_parent; p; p = p->m_parent) |
172 | resource = resource.prepend(p->m_name + QLatin1Char('/')); |
173 | return QLatin1Char(':') + resource; |
174 | } |
175 | |
176 | void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) |
177 | { |
178 | const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
179 | const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
180 | const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
181 | || lib.m_format == RCCResourceLibrary::Python2_Code; |
182 | //some info |
183 | if (text || pass1) { |
184 | if (m_language != QLocale::C) { |
185 | lib.writeString(" // " ); |
186 | lib.writeByteArray(resourceName().toLocal8Bit()); |
187 | lib.writeString(" [" ); |
188 | lib.writeByteArray(QByteArray::number(m_country)); |
189 | lib.writeString("::" ); |
190 | lib.writeByteArray(QByteArray::number(m_language)); |
191 | lib.writeString("[\n " ); |
192 | } else { |
193 | lib.writeString(" // " ); |
194 | lib.writeByteArray(resourceName().toLocal8Bit()); |
195 | lib.writeString("\n " ); |
196 | } |
197 | } |
198 | |
199 | //pointer data |
200 | if (m_flags & RCCFileInfo::Directory) { |
201 | // name offset |
202 | lib.writeNumber4(m_nameOffset); |
203 | |
204 | // flags |
205 | lib.writeNumber2(m_flags); |
206 | |
207 | // child count |
208 | lib.writeNumber4(m_children.size()); |
209 | |
210 | // first child offset |
211 | lib.writeNumber4(m_childOffset); |
212 | } else { |
213 | // name offset |
214 | lib.writeNumber4(m_nameOffset); |
215 | |
216 | // flags |
217 | lib.writeNumber2(m_flags); |
218 | |
219 | // locale |
220 | lib.writeNumber2(m_country); |
221 | lib.writeNumber2(m_language); |
222 | |
223 | //data offset |
224 | lib.writeNumber4(m_dataOffset); |
225 | } |
226 | if (text || pass1) |
227 | lib.writeChar('\n'); |
228 | else if (python) |
229 | lib.writeString("\\\n" ); |
230 | |
231 | if (lib.formatVersion() >= 2) { |
232 | // last modified time stamp |
233 | const QDateTime lastModified = m_fileInfo.lastModified(); |
234 | quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); |
235 | static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE" ).toULongLong(); |
236 | if (sourceDate != 0) |
237 | lastmod = sourceDate; |
238 | static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH" ).toULongLong(); |
239 | if (sourceDate2 != 0) |
240 | lastmod = sourceDate2; |
241 | lib.writeNumber8(lastmod); |
242 | if (text || pass1) |
243 | lib.writeChar('\n'); |
244 | else if (python) |
245 | lib.writeString("\\\n" ); |
246 | } |
247 | } |
248 | |
249 | qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, |
250 | QString *errorMessage) |
251 | { |
252 | const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
253 | const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
254 | const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; |
255 | const bool binary = lib.m_format == RCCResourceLibrary::Binary; |
256 | const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
257 | || lib.m_format == RCCResourceLibrary::Python2_Code; |
258 | |
259 | //capture the offset |
260 | m_dataOffset = offset; |
261 | |
262 | //find the data to be written |
263 | QFile file(m_fileInfo.absoluteFilePath()); |
264 | if (!file.open(QFile::ReadOnly)) { |
265 | *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); |
266 | return 0; |
267 | } |
268 | QByteArray data = file.readAll(); |
269 | |
270 | // Check if compression is useful for this file |
271 | if (data.size() != 0) { |
272 | #if QT_CONFIG(zstd) |
273 | if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) { |
274 | m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; |
275 | m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental |
276 | } |
277 | if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) { |
278 | if (lib.m_zstdCCtx == nullptr) |
279 | lib.m_zstdCCtx = ZSTD_createCCtx(); |
280 | qsizetype size = data.size(); |
281 | size = ZSTD_COMPRESSBOUND(size); |
282 | |
283 | int compressLevel = m_compressLevel; |
284 | if (compressLevel < 0) |
285 | compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; |
286 | |
287 | QByteArray compressed(size, Qt::Uninitialized); |
288 | char *dst = const_cast<char *>(compressed.constData()); |
289 | size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, |
290 | data.constData(), data.size(), |
291 | compressLevel); |
292 | if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { |
293 | // compressing is worth it |
294 | if (m_compressLevel < 0) { |
295 | // heuristic compression, so recompress |
296 | n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, |
297 | data.constData(), data.size(), |
298 | CONSTANT_ZSTDCOMPRESSLEVEL_STORE); |
299 | } |
300 | if (ZSTD_isError(n)) { |
301 | QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n" ) |
302 | .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); |
303 | lib.m_errorDevice->write(msg.toUtf8()); |
304 | } else if (lib.verbose()) { |
305 | QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n" ) |
306 | .arg(m_name).arg(data.size()).arg(n); |
307 | lib.m_errorDevice->write(msg.toUtf8()); |
308 | } |
309 | |
310 | lib.m_overallFlags |= CompressedZstd; |
311 | m_flags |= CompressedZstd; |
312 | data = std::move(compressed); |
313 | data.truncate(n); |
314 | } else if (lib.verbose()) { |
315 | QString msg = QString::fromLatin1("%1: note: not compressed\n" ).arg(m_name); |
316 | lib.m_errorDevice->write(msg.toUtf8()); |
317 | } |
318 | } |
319 | #endif |
320 | #ifndef QT_NO_COMPRESS |
321 | if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { |
322 | m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; |
323 | m_compressLevel = 9; |
324 | } |
325 | if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { |
326 | QByteArray compressed = |
327 | qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); |
328 | |
329 | int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); |
330 | if (compressRatio >= m_compressThreshold) { |
331 | if (lib.verbose()) { |
332 | QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n" ) |
333 | .arg(m_name).arg(data.size()).arg(compressed.size()); |
334 | lib.m_errorDevice->write(msg.toUtf8()); |
335 | } |
336 | data = compressed; |
337 | lib.m_overallFlags |= Compressed; |
338 | m_flags |= Compressed; |
339 | } else if (lib.verbose()) { |
340 | QString msg = QString::fromLatin1("%1: note: not compressed\n" ).arg(m_name); |
341 | lib.m_errorDevice->write(msg.toUtf8()); |
342 | } |
343 | } |
344 | #endif // QT_NO_COMPRESS |
345 | } |
346 | |
347 | // some info |
348 | if (text || pass1) { |
349 | lib.writeString(" // " ); |
350 | lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); |
351 | lib.writeString("\n " ); |
352 | } |
353 | |
354 | // write the length |
355 | if (text || binary || pass2 || python) |
356 | lib.writeNumber4(data.size()); |
357 | if (text || pass1) |
358 | lib.writeString("\n " ); |
359 | else if (python) |
360 | lib.writeString("\\\n" ); |
361 | offset += 4; |
362 | |
363 | // write the payload |
364 | const char *p = data.constData(); |
365 | if (text || python) { |
366 | for (int i = data.size(), j = 0; --i >= 0; --j) { |
367 | lib.writeHex(*p++); |
368 | if (j == 0) { |
369 | if (text) |
370 | lib.writeString("\n " ); |
371 | else |
372 | lib.writeString("\\\n" ); |
373 | j = 16; |
374 | } |
375 | } |
376 | } else if (binary || pass2) { |
377 | lib.writeByteArray(data); |
378 | } |
379 | offset += data.size(); |
380 | |
381 | // done |
382 | if (text || pass1) |
383 | lib.writeString("\n " ); |
384 | else if (python) |
385 | lib.writeString("\\\n" ); |
386 | |
387 | return offset; |
388 | } |
389 | |
390 | qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) |
391 | { |
392 | const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
393 | const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
394 | const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
395 | || lib.m_format == RCCResourceLibrary::Python2_Code; |
396 | |
397 | // capture the offset |
398 | m_nameOffset = offset; |
399 | |
400 | // some info |
401 | if (text || pass1) { |
402 | lib.writeString(" // " ); |
403 | lib.writeByteArray(m_name.toLocal8Bit()); |
404 | lib.writeString("\n " ); |
405 | } |
406 | |
407 | // write the length |
408 | lib.writeNumber2(m_name.length()); |
409 | if (text || pass1) |
410 | lib.writeString("\n " ); |
411 | else if (python) |
412 | lib.writeString("\\\n" ); |
413 | offset += 2; |
414 | |
415 | // write the hash |
416 | lib.writeNumber4(qt_hash(m_name)); |
417 | if (text || pass1) |
418 | lib.writeString("\n " ); |
419 | else if (python) |
420 | lib.writeString("\\\n" ); |
421 | offset += 4; |
422 | |
423 | // write the m_name |
424 | const QChar *unicode = m_name.unicode(); |
425 | for (int i = 0; i < m_name.length(); ++i) { |
426 | lib.writeNumber2(unicode[i].unicode()); |
427 | if ((text || pass1) && i % 16 == 0) |
428 | lib.writeString("\n " ); |
429 | else if (python && i % 16 == 0) |
430 | lib.writeString("\\\n" ); |
431 | } |
432 | offset += m_name.length()*2; |
433 | |
434 | // done |
435 | if (text || pass1) |
436 | lib.writeString("\n " ); |
437 | else if (python) |
438 | lib.writeString("\\\n" ); |
439 | |
440 | return offset; |
441 | } |
442 | |
443 | |
444 | /////////////////////////////////////////////////////////// |
445 | // |
446 | // RCCResourceLibrary |
447 | // |
448 | /////////////////////////////////////////////////////////// |
449 | |
450 | RCCResourceLibrary::Strings::Strings() : |
451 | TAG_RCC(QLatin1String("RCC" )), |
452 | TAG_RESOURCE(QLatin1String("qresource" )), |
453 | TAG_FILE(QLatin1String("file" )), |
454 | ATTRIBUTE_LANG(QLatin1String("lang" )), |
455 | ATTRIBUTE_PREFIX(QLatin1String("prefix" )), |
456 | ATTRIBUTE_ALIAS(QLatin1String("alias" )), |
457 | ATTRIBUTE_THRESHOLD(QLatin1String("threshold" )), |
458 | ATTRIBUTE_COMPRESS(QLatin1String("compress" )), |
459 | ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm" )) |
460 | { |
461 | } |
462 | |
463 | RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) |
464 | : m_root(nullptr), |
465 | m_format(C_Code), |
466 | m_verbose(false), |
467 | m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), |
468 | m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), |
469 | m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), |
470 | m_treeOffset(0), |
471 | m_namesOffset(0), |
472 | m_dataOffset(0), |
473 | m_overallFlags(0), |
474 | m_useNameSpace(CONSTANT_USENAMESPACE), |
475 | m_errorDevice(nullptr), |
476 | m_outDevice(nullptr), |
477 | m_formatVersion(formatVersion), |
478 | m_noZstd(false) |
479 | { |
480 | m_out.reserve(30 * 1000 * 1000); |
481 | #if QT_CONFIG(zstd) |
482 | m_zstdCCtx = nullptr; |
483 | #endif |
484 | } |
485 | |
486 | RCCResourceLibrary::~RCCResourceLibrary() |
487 | { |
488 | delete m_root; |
489 | #if QT_CONFIG(zstd) |
490 | ZSTD_freeCCtx(m_zstdCCtx); |
491 | #endif |
492 | } |
493 | |
494 | enum RCCXmlTag { |
495 | RccTag, |
496 | ResourceTag, |
497 | FileTag |
498 | }; |
499 | Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); |
500 | |
501 | bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, |
502 | const QString &fname, QString currentPath, bool listMode) |
503 | { |
504 | Q_ASSERT(m_errorDevice); |
505 | const QChar slash = QLatin1Char('/'); |
506 | if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) |
507 | currentPath += slash; |
508 | |
509 | QXmlStreamReader reader(inputDevice); |
510 | QStack<RCCXmlTag> tokens; |
511 | |
512 | QString prefix; |
513 | QLocale::Language language = QLocale::c().language(); |
514 | QLocale::Country country = QLocale::c().country(); |
515 | QString alias; |
516 | auto compressAlgo = m_compressionAlgo; |
517 | int compressLevel = m_compressLevel; |
518 | int compressThreshold = m_compressThreshold; |
519 | |
520 | while (!reader.atEnd()) { |
521 | QXmlStreamReader::TokenType t = reader.readNext(); |
522 | switch (t) { |
523 | case QXmlStreamReader::StartElement: |
524 | if (reader.name() == m_strings.TAG_RCC) { |
525 | if (!tokens.isEmpty()) |
526 | reader.raiseError(QLatin1String("expected <RCC> tag" )); |
527 | else |
528 | tokens.push(RccTag); |
529 | } else if (reader.name() == m_strings.TAG_RESOURCE) { |
530 | if (tokens.isEmpty() || tokens.top() != RccTag) { |
531 | reader.raiseError(QLatin1String("unexpected <RESOURCE> tag" )); |
532 | } else { |
533 | tokens.push(ResourceTag); |
534 | |
535 | QXmlStreamAttributes attributes = reader.attributes(); |
536 | language = QLocale::c().language(); |
537 | country = QLocale::c().country(); |
538 | |
539 | if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { |
540 | QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); |
541 | QLocale lang = QLocale(attribute); |
542 | language = lang.language(); |
543 | if (2 == attribute.length()) { |
544 | // Language only |
545 | country = QLocale::AnyCountry; |
546 | } else { |
547 | country = lang.country(); |
548 | } |
549 | } |
550 | |
551 | prefix.clear(); |
552 | if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) |
553 | prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); |
554 | if (!prefix.startsWith(slash)) |
555 | prefix.prepend(slash); |
556 | if (!prefix.endsWith(slash)) |
557 | prefix += slash; |
558 | } |
559 | } else if (reader.name() == m_strings.TAG_FILE) { |
560 | if (tokens.isEmpty() || tokens.top() != ResourceTag) { |
561 | reader.raiseError(QLatin1String("unexpected <FILE> tag" )); |
562 | } else { |
563 | tokens.push(FileTag); |
564 | |
565 | QXmlStreamAttributes attributes = reader.attributes(); |
566 | alias.clear(); |
567 | if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) |
568 | alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); |
569 | |
570 | compressAlgo = m_compressionAlgo; |
571 | compressLevel = m_compressLevel; |
572 | compressThreshold = m_compressThreshold; |
573 | |
574 | QString errorString; |
575 | if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) |
576 | compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); |
577 | if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { |
578 | QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); |
579 | compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); |
580 | } |
581 | |
582 | // Special case for -no-compress |
583 | if (m_compressLevel == -2) |
584 | compressAlgo = CompressionAlgorithm::None; |
585 | |
586 | if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) |
587 | compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); |
588 | |
589 | if (!errorString.isEmpty()) |
590 | reader.raiseError(errorString); |
591 | } |
592 | } else { |
593 | reader.raiseError(QString(QLatin1String("unexpected tag: %1" )).arg(reader.name().toString())); |
594 | } |
595 | break; |
596 | |
597 | case QXmlStreamReader::EndElement: |
598 | if (reader.name() == m_strings.TAG_RCC) { |
599 | if (!tokens.isEmpty() && tokens.top() == RccTag) |
600 | tokens.pop(); |
601 | else |
602 | reader.raiseError(QLatin1String("unexpected closing tag" )); |
603 | } else if (reader.name() == m_strings.TAG_RESOURCE) { |
604 | if (!tokens.isEmpty() && tokens.top() == ResourceTag) |
605 | tokens.pop(); |
606 | else |
607 | reader.raiseError(QLatin1String("unexpected closing tag" )); |
608 | } else if (reader.name() == m_strings.TAG_FILE) { |
609 | if (!tokens.isEmpty() && tokens.top() == FileTag) |
610 | tokens.pop(); |
611 | else |
612 | reader.raiseError(QLatin1String("unexpected closing tag" )); |
613 | } |
614 | break; |
615 | |
616 | case QXmlStreamReader::Characters: |
617 | if (reader.isWhitespace()) |
618 | break; |
619 | if (tokens.isEmpty() || tokens.top() != FileTag) { |
620 | reader.raiseError(QLatin1String("unexpected text" )); |
621 | } else { |
622 | QString fileName = reader.text().toString(); |
623 | if (fileName.isEmpty()) { |
624 | const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n" ).arg(fname); |
625 | m_errorDevice->write(msg.toUtf8()); |
626 | } |
627 | |
628 | if (alias.isNull()) |
629 | alias = fileName; |
630 | |
631 | alias = QDir::cleanPath(alias); |
632 | while (alias.startsWith(QLatin1String("../" ))) |
633 | alias.remove(0, 3); |
634 | alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; |
635 | |
636 | QString absFileName = fileName; |
637 | if (QDir::isRelativePath(absFileName)) |
638 | absFileName.prepend(currentPath); |
639 | QFileInfo file(absFileName); |
640 | if (file.isDir()) { |
641 | QDir dir(file.filePath()); |
642 | if (!alias.endsWith(slash)) |
643 | alias += slash; |
644 | |
645 | QStringList filePaths; |
646 | QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); |
647 | while (it.hasNext()) { |
648 | it.next(); |
649 | if (it.fileName() == QLatin1String("." ) |
650 | || it.fileName() == QLatin1String(".." )) |
651 | continue; |
652 | filePaths.append(it.filePath()); |
653 | } |
654 | |
655 | // make rcc output deterministic |
656 | std::sort(filePaths.begin(), filePaths.end()); |
657 | |
658 | for (const QString &filePath : filePaths) { |
659 | QFileInfo child(filePath); |
660 | const bool arc = |
661 | addFile(alias + child.fileName(), |
662 | RCCFileInfo(child.fileName(), child, language, country, |
663 | child.isDir() ? RCCFileInfo::Directory |
664 | : RCCFileInfo::NoFlags, |
665 | compressAlgo, compressLevel, compressThreshold, |
666 | m_noZstd)); |
667 | if (!arc) |
668 | m_failedResources.push_back(child.fileName()); |
669 | } |
670 | } else if (listMode || file.isFile()) { |
671 | const bool arc = |
672 | addFile(alias, |
673 | RCCFileInfo(alias.section(slash, -1), |
674 | file, |
675 | language, |
676 | country, |
677 | RCCFileInfo::NoFlags, |
678 | compressAlgo, |
679 | compressLevel, |
680 | compressThreshold, |
681 | m_noZstd) |
682 | ); |
683 | if (!arc) |
684 | m_failedResources.push_back(absFileName); |
685 | } else if (file.exists()) { |
686 | m_failedResources.push_back(absFileName); |
687 | const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n" ) |
688 | .arg(fname, fileName); |
689 | m_errorDevice->write(msg.toUtf8()); |
690 | return false; |
691 | } else { |
692 | m_failedResources.push_back(absFileName); |
693 | const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n" ) |
694 | .arg(fname, fileName); |
695 | m_errorDevice->write(msg.toUtf8()); |
696 | return false; |
697 | } |
698 | } |
699 | break; |
700 | |
701 | default: |
702 | break; |
703 | } |
704 | } |
705 | |
706 | if (reader.hasError()) { |
707 | int errorLine = reader.lineNumber(); |
708 | int errorColumn = reader.columnNumber(); |
709 | QString errorMessage = reader.errorString(); |
710 | QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n" ).arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); |
711 | m_errorDevice->write(msg.toUtf8()); |
712 | return false; |
713 | } |
714 | |
715 | if (m_root == nullptr) { |
716 | const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n" ).arg(fname); |
717 | m_errorDevice->write(msg.toUtf8()); |
718 | if (!listMode && m_format == Binary) { |
719 | // create dummy entry, otherwise loading with QResource will crash |
720 | m_root = new RCCFileInfo(QString(), QFileInfo(), |
721 | QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
722 | } |
723 | } |
724 | |
725 | return true; |
726 | } |
727 | |
728 | bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) |
729 | { |
730 | Q_ASSERT(m_errorDevice); |
731 | if (file.m_fileInfo.size() > 0xffffffff) { |
732 | const QString msg = QString::fromLatin1("File too big: %1\n" ).arg(file.m_fileInfo.absoluteFilePath()); |
733 | m_errorDevice->write(msg.toUtf8()); |
734 | return false; |
735 | } |
736 | if (!m_root) |
737 | m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
738 | |
739 | RCCFileInfo *parent = m_root; |
740 | const QStringList nodes = alias.split(QLatin1Char('/')); |
741 | for (int i = 1; i < nodes.size()-1; ++i) { |
742 | const QString node = nodes.at(i); |
743 | if (node.isEmpty()) |
744 | continue; |
745 | if (!parent->m_children.contains(node)) { |
746 | RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
747 | s->m_parent = parent; |
748 | parent->m_children.insert(node, s); |
749 | parent = s; |
750 | } else { |
751 | parent = *parent->m_children.constFind(node); |
752 | } |
753 | } |
754 | |
755 | const QString filename = nodes.at(nodes.size()-1); |
756 | RCCFileInfo *s = new RCCFileInfo(file); |
757 | s->m_parent = parent; |
758 | auto cbegin = parent->m_children.constFind(filename); |
759 | auto cend = parent->m_children.constEnd(); |
760 | for (auto it = cbegin; it != cend; ++it) { |
761 | if (it.key() == filename && it.value()->m_language == s->m_language && |
762 | it.value()->m_country == s->m_country) { |
763 | for (const QString &name : qAsConst(m_fileNames)) { |
764 | qWarning("%s: Warning: potential duplicate alias detected: '%s'" , |
765 | qPrintable(name), qPrintable(filename)); |
766 | } |
767 | break; |
768 | } |
769 | } |
770 | parent->m_children.insert(filename, s); |
771 | return true; |
772 | } |
773 | |
774 | void RCCResourceLibrary::reset() |
775 | { |
776 | if (m_root) { |
777 | delete m_root; |
778 | m_root = nullptr; |
779 | } |
780 | m_errorDevice = nullptr; |
781 | m_failedResources.clear(); |
782 | } |
783 | |
784 | |
785 | bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) |
786 | { |
787 | reset(); |
788 | m_errorDevice = &errorDevice; |
789 | //read in data |
790 | if (m_verbose) { |
791 | const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n" ) |
792 | .arg(m_fileNames.size()).arg(static_cast<int>(listMode)); |
793 | m_errorDevice->write(msg.toUtf8()); |
794 | } |
795 | for (int i = 0; i < m_fileNames.size(); ++i) { |
796 | QFile fileIn; |
797 | QString fname = m_fileNames.at(i); |
798 | QString pwd; |
799 | if (fname == QLatin1String("-" )) { |
800 | fname = QLatin1String("(stdin)" ); |
801 | pwd = QDir::currentPath(); |
802 | fileIn.setFileName(fname); |
803 | if (!fileIn.open(stdin, QIODevice::ReadOnly)) { |
804 | m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); |
805 | return false; |
806 | } |
807 | } else { |
808 | pwd = QFileInfo(fname).path(); |
809 | fileIn.setFileName(fname); |
810 | if (!fileIn.open(QIODevice::ReadOnly)) { |
811 | m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); |
812 | return false; |
813 | } |
814 | } |
815 | if (m_verbose) { |
816 | const QString msg = QString::fromLatin1("Interpreting %1\n" ).arg(fname); |
817 | m_errorDevice->write(msg.toUtf8()); |
818 | } |
819 | |
820 | if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) |
821 | return false; |
822 | } |
823 | return true; |
824 | } |
825 | |
826 | QStringList RCCResourceLibrary::dataFiles() const |
827 | { |
828 | QStringList ret; |
829 | QStack<RCCFileInfo*> pending; |
830 | |
831 | if (!m_root) |
832 | return ret; |
833 | pending.push(m_root); |
834 | while (!pending.isEmpty()) { |
835 | RCCFileInfo *file = pending.pop(); |
836 | for (auto it = file->m_children.begin(); |
837 | it != file->m_children.end(); ++it) { |
838 | RCCFileInfo *child = it.value(); |
839 | if (child->m_flags & RCCFileInfo::Directory) |
840 | pending.push(child); |
841 | else |
842 | ret.append(child->m_fileInfo.filePath()); |
843 | } |
844 | } |
845 | return ret; |
846 | } |
847 | |
848 | // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion |
849 | static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) |
850 | { |
851 | const QChar slash = QLatin1Char('/'); |
852 | const auto cend = m_root->m_children.constEnd(); |
853 | for (auto it = m_root->m_children.constBegin(); it != cend; ++it) { |
854 | const RCCFileInfo *child = it.value(); |
855 | const QString childName = path + slash + child->m_name; |
856 | if (child->m_flags & RCCFileInfo::Directory) { |
857 | resourceDataFileMapRecursion(child, childName, m); |
858 | } else { |
859 | m.insert(childName, child->m_fileInfo.filePath()); |
860 | } |
861 | } |
862 | } |
863 | |
864 | RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const |
865 | { |
866 | ResourceDataFileMap rc; |
867 | if (m_root) |
868 | resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); |
869 | return rc; |
870 | } |
871 | |
872 | RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) |
873 | { |
874 | if (value == QLatin1String("best" )) |
875 | return CompressionAlgorithm::Best; |
876 | if (value == QLatin1String("zlib" )) { |
877 | #ifdef QT_NO_COMPRESS |
878 | *errorMsg = QLatin1String("zlib support not compiled in" ); |
879 | #else |
880 | return CompressionAlgorithm::Zlib; |
881 | #endif |
882 | } else if (value == QLatin1String("zstd" )) { |
883 | #if QT_CONFIG(zstd) |
884 | return CompressionAlgorithm::Zstd; |
885 | #else |
886 | *errorMsg = QLatin1String("Zstandard support not compiled in" ); |
887 | #endif |
888 | } else if (value != QLatin1String("none" )) { |
889 | *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'" ).arg(value); |
890 | } |
891 | |
892 | return CompressionAlgorithm::None; |
893 | } |
894 | |
895 | int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) |
896 | { |
897 | bool ok; |
898 | int c = level.toInt(&ok); |
899 | if (ok) { |
900 | switch (algo) { |
901 | case CompressionAlgorithm::None: |
902 | case CompressionAlgorithm::Best: |
903 | return 0; |
904 | case CompressionAlgorithm::Zlib: |
905 | if (c >= 1 && c <= 9) |
906 | return c; |
907 | break; |
908 | case CompressionAlgorithm::Zstd: |
909 | #if QT_CONFIG(zstd) |
910 | if (c >= 0 && c <= ZSTD_maxCLevel()) |
911 | return c; |
912 | #endif |
913 | break; |
914 | } |
915 | } |
916 | |
917 | *errorMsg = QString::fromLatin1("invalid compression level '%1'" ).arg(level); |
918 | return 0; |
919 | } |
920 | |
921 | bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) |
922 | { |
923 | m_errorDevice = &errorDevice; |
924 | |
925 | if (m_format == Pass2) { |
926 | const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; |
927 | bool foundSignature = false; |
928 | |
929 | while (true) { |
930 | char c; |
931 | for (int i = 0; i < 8; ) { |
932 | if (!tempDevice.getChar(&c)) { |
933 | if (foundSignature) |
934 | return true; |
935 | m_errorDevice->write("No data signature found\n" ); |
936 | return false; |
937 | } |
938 | if (c == pattern[i]) { |
939 | ++i; |
940 | } else { |
941 | for (int k = 0; k < i; ++k) |
942 | outDevice.putChar(pattern[k]); |
943 | outDevice.putChar(c); |
944 | i = 0; |
945 | } |
946 | } |
947 | |
948 | m_outDevice = &outDevice; |
949 | quint64 start = outDevice.pos(); |
950 | writeDataBlobs(); |
951 | quint64 len = outDevice.pos() - start; |
952 | |
953 | tempDevice.seek(tempDevice.pos() + len - 8); |
954 | foundSignature = true; |
955 | } |
956 | } |
957 | |
958 | //write out |
959 | if (m_verbose) |
960 | m_errorDevice->write("Outputting code\n" ); |
961 | if (!writeHeader()) { |
962 | m_errorDevice->write("Could not write header\n" ); |
963 | return false; |
964 | } |
965 | if (m_root) { |
966 | if (!writeDataBlobs()) { |
967 | m_errorDevice->write("Could not write data blobs.\n" ); |
968 | return false; |
969 | } |
970 | if (!writeDataNames()) { |
971 | m_errorDevice->write("Could not write file names\n" ); |
972 | return false; |
973 | } |
974 | if (!writeDataStructure()) { |
975 | m_errorDevice->write("Could not write data tree\n" ); |
976 | return false; |
977 | } |
978 | } |
979 | if (!writeInitializer()) { |
980 | m_errorDevice->write("Could not write footer\n" ); |
981 | return false; |
982 | } |
983 | outDevice.write(m_out.constData(), m_out.size()); |
984 | return true; |
985 | } |
986 | |
987 | void RCCResourceLibrary::writeDecimal(int value) |
988 | { |
989 | Q_ASSERT(m_format != RCCResourceLibrary::Binary); |
990 | char buf[std::numeric_limits<int>::digits10 + 2]; |
991 | int n = snprintf(buf, sizeof(buf), "%d" , value); |
992 | write(buf, n); |
993 | } |
994 | |
995 | static const char hexDigits[] = "0123456789abcdef" ; |
996 | |
997 | inline void RCCResourceLibrary::write2HexDigits(quint8 number) |
998 | { |
999 | writeChar(hexDigits[number >> 4]); |
1000 | writeChar(hexDigits[number & 0xf]); |
1001 | } |
1002 | |
1003 | void RCCResourceLibrary::writeHex(quint8 tmp) |
1004 | { |
1005 | switch (m_format) { |
1006 | case RCCResourceLibrary::Python3_Code: |
1007 | case RCCResourceLibrary::Python2_Code: |
1008 | if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { |
1009 | writeChar(char(tmp)); |
1010 | } else { |
1011 | writeChar('\\'); |
1012 | writeChar('x'); |
1013 | write2HexDigits(tmp); |
1014 | } |
1015 | break; |
1016 | default: |
1017 | writeChar('0'); |
1018 | writeChar('x'); |
1019 | if (tmp < 16) |
1020 | writeChar(hexDigits[tmp]); |
1021 | else |
1022 | write2HexDigits(tmp); |
1023 | writeChar(','); |
1024 | break; |
1025 | } |
1026 | } |
1027 | |
1028 | void RCCResourceLibrary::writeNumber2(quint16 number) |
1029 | { |
1030 | if (m_format == RCCResourceLibrary::Binary) { |
1031 | writeChar(number >> 8); |
1032 | writeChar(number); |
1033 | } else { |
1034 | writeHex(number >> 8); |
1035 | writeHex(number); |
1036 | } |
1037 | } |
1038 | |
1039 | void RCCResourceLibrary::writeNumber4(quint32 number) |
1040 | { |
1041 | if (m_format == RCCResourceLibrary::Pass2) { |
1042 | m_outDevice->putChar(char(number >> 24)); |
1043 | m_outDevice->putChar(char(number >> 16)); |
1044 | m_outDevice->putChar(char(number >> 8)); |
1045 | m_outDevice->putChar(char(number)); |
1046 | } else if (m_format == RCCResourceLibrary::Binary) { |
1047 | writeChar(number >> 24); |
1048 | writeChar(number >> 16); |
1049 | writeChar(number >> 8); |
1050 | writeChar(number); |
1051 | } else { |
1052 | writeHex(number >> 24); |
1053 | writeHex(number >> 16); |
1054 | writeHex(number >> 8); |
1055 | writeHex(number); |
1056 | } |
1057 | } |
1058 | |
1059 | void RCCResourceLibrary::writeNumber8(quint64 number) |
1060 | { |
1061 | if (m_format == RCCResourceLibrary::Pass2) { |
1062 | m_outDevice->putChar(char(number >> 56)); |
1063 | m_outDevice->putChar(char(number >> 48)); |
1064 | m_outDevice->putChar(char(number >> 40)); |
1065 | m_outDevice->putChar(char(number >> 32)); |
1066 | m_outDevice->putChar(char(number >> 24)); |
1067 | m_outDevice->putChar(char(number >> 16)); |
1068 | m_outDevice->putChar(char(number >> 8)); |
1069 | m_outDevice->putChar(char(number)); |
1070 | } else if (m_format == RCCResourceLibrary::Binary) { |
1071 | writeChar(number >> 56); |
1072 | writeChar(number >> 48); |
1073 | writeChar(number >> 40); |
1074 | writeChar(number >> 32); |
1075 | writeChar(number >> 24); |
1076 | writeChar(number >> 16); |
1077 | writeChar(number >> 8); |
1078 | writeChar(number); |
1079 | } else { |
1080 | writeHex(number >> 56); |
1081 | writeHex(number >> 48); |
1082 | writeHex(number >> 40); |
1083 | writeHex(number >> 32); |
1084 | writeHex(number >> 24); |
1085 | writeHex(number >> 16); |
1086 | writeHex(number >> 8); |
1087 | writeHex(number); |
1088 | } |
1089 | } |
1090 | |
1091 | bool RCCResourceLibrary::() |
1092 | { |
1093 | switch (m_format) { |
1094 | case C_Code: |
1095 | case Pass1: |
1096 | writeString("/****************************************************************************\n" ); |
1097 | writeString("** Resource object code\n" ); |
1098 | writeString("**\n" ); |
1099 | writeString("** Created by: The Resource Compiler for Qt version " ); |
1100 | writeByteArray(QT_VERSION_STR); |
1101 | writeString("\n**\n" ); |
1102 | writeString("** WARNING! All changes made in this file will be lost!\n" ); |
1103 | writeString( "*****************************************************************************/\n\n" ); |
1104 | break; |
1105 | case Python3_Code: |
1106 | case Python2_Code: |
1107 | writeString("# Resource object code (Python " ); |
1108 | writeChar(m_format == Python3_Code ? '3' : '2'); |
1109 | writeString(")\n" ); |
1110 | writeString("# Created by: object code\n" ); |
1111 | writeString("# Created by: The Resource Compiler for Qt version " ); |
1112 | writeByteArray(QT_VERSION_STR); |
1113 | writeString("\n" ); |
1114 | writeString("# WARNING! All changes made in this file will be lost!\n\n" ); |
1115 | writeString("from PySide2 import QtCore\n\n" ); |
1116 | break; |
1117 | case Binary: |
1118 | writeString("qres" ); |
1119 | writeNumber4(0); |
1120 | writeNumber4(0); |
1121 | writeNumber4(0); |
1122 | writeNumber4(0); |
1123 | if (m_formatVersion >= 3) |
1124 | writeNumber4(m_overallFlags); |
1125 | break; |
1126 | default: |
1127 | break; |
1128 | } |
1129 | return true; |
1130 | } |
1131 | |
1132 | bool RCCResourceLibrary::writeDataBlobs() |
1133 | { |
1134 | Q_ASSERT(m_errorDevice); |
1135 | switch (m_format) { |
1136 | case C_Code: |
1137 | writeString("static const unsigned char qt_resource_data[] = {\n" ); |
1138 | break; |
1139 | case Python3_Code: |
1140 | writeString("qt_resource_data = b\"\\\n" ); |
1141 | break; |
1142 | case Python2_Code: |
1143 | writeString("qt_resource_data = \"\\\n" ); |
1144 | break; |
1145 | case Binary: |
1146 | m_dataOffset = m_out.size(); |
1147 | break; |
1148 | default: |
1149 | break; |
1150 | } |
1151 | |
1152 | if (!m_root) |
1153 | return false; |
1154 | |
1155 | QStack<RCCFileInfo*> pending; |
1156 | pending.push(m_root); |
1157 | qint64 offset = 0; |
1158 | QString errorMessage; |
1159 | while (!pending.isEmpty()) { |
1160 | RCCFileInfo *file = pending.pop(); |
1161 | for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { |
1162 | RCCFileInfo *child = it.value(); |
1163 | if (child->m_flags & RCCFileInfo::Directory) |
1164 | pending.push(child); |
1165 | else { |
1166 | offset = child->writeDataBlob(*this, offset, &errorMessage); |
1167 | if (offset == 0) { |
1168 | m_errorDevice->write(errorMessage.toUtf8()); |
1169 | return false; |
1170 | } |
1171 | } |
1172 | } |
1173 | } |
1174 | switch (m_format) { |
1175 | case C_Code: |
1176 | writeString("\n};\n\n" ); |
1177 | break; |
1178 | case Python3_Code: |
1179 | case Python2_Code: |
1180 | writeString("\"\n\n" ); |
1181 | break; |
1182 | case Pass1: |
1183 | if (offset < 8) |
1184 | offset = 8; |
1185 | writeString("\nstatic const unsigned char qt_resource_data[" ); |
1186 | writeByteArray(QByteArray::number(offset)); |
1187 | writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n" ); |
1188 | break; |
1189 | default: |
1190 | break; |
1191 | } |
1192 | return true; |
1193 | } |
1194 | |
1195 | bool RCCResourceLibrary::writeDataNames() |
1196 | { |
1197 | switch (m_format) { |
1198 | case C_Code: |
1199 | case Pass1: |
1200 | writeString("static const unsigned char qt_resource_name[] = {\n" ); |
1201 | break; |
1202 | case Python3_Code: |
1203 | writeString("qt_resource_name = b\"\\\n" ); |
1204 | break; |
1205 | case Python2_Code: |
1206 | writeString("qt_resource_name = \"\\\n" ); |
1207 | break; |
1208 | case Binary: |
1209 | m_namesOffset = m_out.size(); |
1210 | break; |
1211 | default: |
1212 | break; |
1213 | } |
1214 | |
1215 | QHash<QString, int> names; |
1216 | QStack<RCCFileInfo*> pending; |
1217 | |
1218 | if (!m_root) |
1219 | return false; |
1220 | |
1221 | pending.push(m_root); |
1222 | qint64 offset = 0; |
1223 | while (!pending.isEmpty()) { |
1224 | RCCFileInfo *file = pending.pop(); |
1225 | for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { |
1226 | RCCFileInfo *child = it.value(); |
1227 | if (child->m_flags & RCCFileInfo::Directory) |
1228 | pending.push(child); |
1229 | if (names.contains(child->m_name)) { |
1230 | child->m_nameOffset = names.value(child->m_name); |
1231 | } else { |
1232 | names.insert(child->m_name, offset); |
1233 | offset = child->writeDataName(*this, offset); |
1234 | } |
1235 | } |
1236 | } |
1237 | switch (m_format) { |
1238 | case C_Code: |
1239 | case Pass1: |
1240 | writeString("\n};\n\n" ); |
1241 | break; |
1242 | case Python3_Code: |
1243 | case Python2_Code: |
1244 | writeString("\"\n\n" ); |
1245 | break; |
1246 | default: |
1247 | break; |
1248 | } |
1249 | return true; |
1250 | } |
1251 | |
1252 | struct qt_rcc_compare_hash |
1253 | { |
1254 | typedef bool result_type; |
1255 | result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const |
1256 | { |
1257 | return qt_hash(left->m_name) < qt_hash(right->m_name); |
1258 | } |
1259 | }; |
1260 | |
1261 | bool RCCResourceLibrary::writeDataStructure() |
1262 | { |
1263 | switch (m_format) { |
1264 | case C_Code: |
1265 | case Pass1: |
1266 | writeString("static const unsigned char qt_resource_struct[] = {\n" ); |
1267 | break; |
1268 | case Python3_Code: |
1269 | writeString("qt_resource_struct = b\"\\\n" ); |
1270 | break; |
1271 | case Python2_Code: |
1272 | writeString("qt_resource_struct = \"\\\n" ); |
1273 | break; |
1274 | case Binary: |
1275 | m_treeOffset = m_out.size(); |
1276 | break; |
1277 | default: |
1278 | break; |
1279 | } |
1280 | |
1281 | QStack<RCCFileInfo*> pending; |
1282 | |
1283 | if (!m_root) |
1284 | return false; |
1285 | |
1286 | //calculate the child offsets (flat) |
1287 | pending.push(m_root); |
1288 | int offset = 1; |
1289 | while (!pending.isEmpty()) { |
1290 | RCCFileInfo *file = pending.pop(); |
1291 | file->m_childOffset = offset; |
1292 | |
1293 | //sort by hash value for binary lookup |
1294 | QList<RCCFileInfo*> m_children = file->m_children.values(); |
1295 | std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); |
1296 | |
1297 | //write out the actual data now |
1298 | for (int i = 0; i < m_children.size(); ++i) { |
1299 | RCCFileInfo *child = m_children.at(i); |
1300 | ++offset; |
1301 | if (child->m_flags & RCCFileInfo::Directory) |
1302 | pending.push(child); |
1303 | } |
1304 | } |
1305 | |
1306 | //write out the structure (ie iterate again!) |
1307 | pending.push(m_root); |
1308 | m_root->writeDataInfo(*this); |
1309 | while (!pending.isEmpty()) { |
1310 | RCCFileInfo *file = pending.pop(); |
1311 | |
1312 | //sort by hash value for binary lookup |
1313 | QList<RCCFileInfo*> m_children = file->m_children.values(); |
1314 | std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); |
1315 | |
1316 | //write out the actual data now |
1317 | for (int i = 0; i < m_children.size(); ++i) { |
1318 | RCCFileInfo *child = m_children.at(i); |
1319 | child->writeDataInfo(*this); |
1320 | if (child->m_flags & RCCFileInfo::Directory) |
1321 | pending.push(child); |
1322 | } |
1323 | } |
1324 | switch (m_format) { |
1325 | case C_Code: |
1326 | case Pass1: |
1327 | writeString("\n};\n\n" ); |
1328 | break; |
1329 | case Python3_Code: |
1330 | case Python2_Code: |
1331 | writeString("\"\n\n" ); |
1332 | break; |
1333 | default: |
1334 | break; |
1335 | } |
1336 | |
1337 | return true; |
1338 | } |
1339 | |
1340 | void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) |
1341 | { |
1342 | if (m_useNameSpace) { |
1343 | writeString("QT_RCC_MANGLE_NAMESPACE(" ); |
1344 | writeByteArray(name); |
1345 | writeChar(')'); |
1346 | } else { |
1347 | writeByteArray(name); |
1348 | } |
1349 | } |
1350 | |
1351 | void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) |
1352 | { |
1353 | if (m_useNameSpace) { |
1354 | writeString("QT_RCC_PREPEND_NAMESPACE(" ); |
1355 | writeByteArray(name); |
1356 | writeChar(')'); |
1357 | } else { |
1358 | writeByteArray(name); |
1359 | } |
1360 | } |
1361 | |
1362 | bool RCCResourceLibrary::writeInitializer() |
1363 | { |
1364 | if (m_format == C_Code || m_format == Pass1) { |
1365 | //write("\nQT_BEGIN_NAMESPACE\n"); |
1366 | QString initNameStr = m_initName; |
1367 | if (!initNameStr.isEmpty()) { |
1368 | initNameStr.prepend(QLatin1Char('_')); |
1369 | auto isAsciiLetterOrNumber = [] (QChar c) -> bool { |
1370 | ushort ch = c.unicode(); |
1371 | return (ch >= '0' && ch <= '9') || |
1372 | (ch >= 'A' && ch <= 'Z') || |
1373 | (ch >= 'a' && ch <= 'z') || |
1374 | ch == '_'; |
1375 | }; |
1376 | for (QChar &c : initNameStr) { |
1377 | if (!isAsciiLetterOrNumber(c)) |
1378 | c = QLatin1Char('_'); |
1379 | } |
1380 | } |
1381 | QByteArray initName = initNameStr.toLatin1(); |
1382 | |
1383 | //init |
1384 | if (m_useNameSpace) { |
1385 | writeString("#ifdef QT_NAMESPACE\n" |
1386 | "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" |
1387 | "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" |
1388 | "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" |
1389 | "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" |
1390 | "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" |
1391 | " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" |
1392 | "#else\n" |
1393 | "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" |
1394 | "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" |
1395 | "#endif\n\n" ); |
1396 | |
1397 | writeString("#ifdef QT_NAMESPACE\n" |
1398 | "namespace QT_NAMESPACE {\n" |
1399 | "#endif\n\n" ); |
1400 | } |
1401 | |
1402 | if (m_root) { |
1403 | writeString("bool qRegisterResourceData" |
1404 | "(int, const unsigned char *, " |
1405 | "const unsigned char *, const unsigned char *);\n" ); |
1406 | writeString("bool qUnregisterResourceData" |
1407 | "(int, const unsigned char *, " |
1408 | "const unsigned char *, const unsigned char *);\n\n" ); |
1409 | |
1410 | if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { |
1411 | // use variable relocations with ELF and Mach-O |
1412 | writeString("#if defined(__ELF__) || defined(__APPLE__)\n" ); |
1413 | if (m_overallFlags & RCCFileInfo::Compressed) { |
1414 | writeString("static inline unsigned char qResourceFeatureZlib()\n" |
1415 | "{\n" |
1416 | " extern const unsigned char qt_resourceFeatureZlib;\n" |
1417 | " return qt_resourceFeatureZlib;\n" |
1418 | "}\n" ); |
1419 | } |
1420 | if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
1421 | writeString("static inline unsigned char qResourceFeatureZstd()\n" |
1422 | "{\n" |
1423 | " extern const unsigned char qt_resourceFeatureZstd;\n" |
1424 | " return qt_resourceFeatureZstd;\n" |
1425 | "}\n" ); |
1426 | } |
1427 | writeString("#else\n" ); |
1428 | if (m_overallFlags & RCCFileInfo::Compressed) |
1429 | writeString("unsigned char qResourceFeatureZlib();\n" ); |
1430 | if (m_overallFlags & RCCFileInfo::CompressedZstd) |
1431 | writeString("unsigned char qResourceFeatureZstd();\n" ); |
1432 | writeString("#endif\n\n" ); |
1433 | } |
1434 | } |
1435 | |
1436 | if (m_useNameSpace) |
1437 | writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n" ); |
1438 | |
1439 | QByteArray initResources = "qInitResources" ; |
1440 | initResources += initName; |
1441 | |
1442 | // Work around -Wmissing-declarations warnings. |
1443 | writeString("int " ); |
1444 | writeMangleNamespaceFunction(initResources); |
1445 | writeString("();\n" ); |
1446 | |
1447 | writeString("int " ); |
1448 | writeMangleNamespaceFunction(initResources); |
1449 | writeString("()\n{\n" ); |
1450 | |
1451 | if (m_root) { |
1452 | writeString(" int version = " ); |
1453 | writeDecimal(m_formatVersion); |
1454 | writeString(";\n " ); |
1455 | writeAddNamespaceFunction("qRegisterResourceData" ); |
1456 | writeString("\n (version, qt_resource_struct, " |
1457 | "qt_resource_name, qt_resource_data);\n" ); |
1458 | } |
1459 | writeString(" return 1;\n" ); |
1460 | writeString("}\n\n" ); |
1461 | |
1462 | //cleanup |
1463 | QByteArray cleanResources = "qCleanupResources" ; |
1464 | cleanResources += initName; |
1465 | |
1466 | // Work around -Wmissing-declarations warnings. |
1467 | writeString("int " ); |
1468 | writeMangleNamespaceFunction(cleanResources); |
1469 | writeString("();\n" ); |
1470 | |
1471 | writeString("int " ); |
1472 | writeMangleNamespaceFunction(cleanResources); |
1473 | writeString("()\n{\n" ); |
1474 | if (m_root) { |
1475 | writeString(" int version = " ); |
1476 | writeDecimal(m_formatVersion); |
1477 | writeString(";\n " ); |
1478 | |
1479 | // ODR-use certain symbols from QtCore if we require optional features |
1480 | if (m_overallFlags & RCCFileInfo::Compressed) { |
1481 | writeString("version += " ); |
1482 | writeAddNamespaceFunction("qResourceFeatureZlib()" ); |
1483 | writeString(";\n " ); |
1484 | } |
1485 | if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
1486 | writeString("version += " ); |
1487 | writeAddNamespaceFunction("qResourceFeatureZstd()" ); |
1488 | writeString(";\n " ); |
1489 | } |
1490 | |
1491 | writeAddNamespaceFunction("qUnregisterResourceData" ); |
1492 | writeString("\n (version, qt_resource_struct, " |
1493 | "qt_resource_name, qt_resource_data);\n" ); |
1494 | } |
1495 | writeString(" return 1;\n" ); |
1496 | writeString("}\n\n" ); |
1497 | |
1498 | |
1499 | writeString("namespace {\n" |
1500 | " struct initializer {\n" ); |
1501 | |
1502 | if (m_useNameSpace) { |
1503 | writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" |
1504 | " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n" ); |
1505 | } else { |
1506 | writeByteArray(" initializer() { " + initResources + "(); }\n" |
1507 | " ~initializer() { " + cleanResources + "(); }\n" ); |
1508 | } |
1509 | writeString(" } dummy;\n" |
1510 | "}\n" ); |
1511 | |
1512 | } else if (m_format == Binary) { |
1513 | int i = 4; |
1514 | char *p = m_out.data(); |
1515 | p[i++] = 0; |
1516 | p[i++] = 0; |
1517 | p[i++] = 0; |
1518 | p[i++] = m_formatVersion; |
1519 | |
1520 | p[i++] = (m_treeOffset >> 24) & 0xff; |
1521 | p[i++] = (m_treeOffset >> 16) & 0xff; |
1522 | p[i++] = (m_treeOffset >> 8) & 0xff; |
1523 | p[i++] = (m_treeOffset >> 0) & 0xff; |
1524 | |
1525 | p[i++] = (m_dataOffset >> 24) & 0xff; |
1526 | p[i++] = (m_dataOffset >> 16) & 0xff; |
1527 | p[i++] = (m_dataOffset >> 8) & 0xff; |
1528 | p[i++] = (m_dataOffset >> 0) & 0xff; |
1529 | |
1530 | p[i++] = (m_namesOffset >> 24) & 0xff; |
1531 | p[i++] = (m_namesOffset >> 16) & 0xff; |
1532 | p[i++] = (m_namesOffset >> 8) & 0xff; |
1533 | p[i++] = (m_namesOffset >> 0) & 0xff; |
1534 | |
1535 | if (m_formatVersion >= 3) { |
1536 | p[i++] = (m_overallFlags >> 24) & 0xff; |
1537 | p[i++] = (m_overallFlags >> 16) & 0xff; |
1538 | p[i++] = (m_overallFlags >> 8) & 0xff; |
1539 | p[i++] = (m_overallFlags >> 0) & 0xff; |
1540 | } |
1541 | } else if (m_format == Python3_Code || m_format == Python2_Code) { |
1542 | writeString("def qInitResources():\n" ); |
1543 | writeString(" QtCore.qRegisterResourceData(0x" ); |
1544 | write2HexDigits(m_formatVersion); |
1545 | writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n" ); |
1546 | writeString("def qCleanupResources():\n" ); |
1547 | writeString(" QtCore.qUnregisterResourceData(0x" ); |
1548 | write2HexDigits(m_formatVersion); |
1549 | writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n" ); |
1550 | writeString("qInitResources()\n" ); |
1551 | } |
1552 | return true; |
1553 | } |
1554 | |
1555 | QT_END_NAMESPACE |
1556 | |