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 <qdebug.h> |
33 | #include <qdir.h> |
34 | #include <qfile.h> |
35 | #include <qfileinfo.h> |
36 | #include <qhashfunctions.h> |
37 | #include <qtextstream.h> |
38 | #include <qatomic.h> |
39 | #include <qglobal.h> |
40 | #include <qcoreapplication.h> |
41 | #include <qcommandlineoption.h> |
42 | #include <qcommandlineparser.h> |
43 | |
44 | #ifdef Q_OS_WIN |
45 | # include <fcntl.h> |
46 | # include <io.h> |
47 | # include <stdio.h> |
48 | #endif // Q_OS_WIN |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | void dumpRecursive(const QDir &dir, QTextStream &out) |
53 | { |
54 | const QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot |
55 | | QDir::NoSymLinks); |
56 | for (const QFileInfo &entry : entries) { |
57 | if (entry.isDir()) { |
58 | dumpRecursive(entry.filePath(), out); |
59 | } else { |
60 | out << QLatin1String("<file>" ) |
61 | << entry.filePath() |
62 | << QLatin1String("</file>\n" ); |
63 | } |
64 | } |
65 | } |
66 | |
67 | int createProject(const QString &outFileName) |
68 | { |
69 | QDir currentDir = QDir::current(); |
70 | QString currentDirName = currentDir.dirName(); |
71 | if (currentDirName.isEmpty()) |
72 | currentDirName = QLatin1String("root" ); |
73 | |
74 | QFile file; |
75 | bool isOk = false; |
76 | if (outFileName.isEmpty()) { |
77 | isOk = file.open(stdout, QFile::WriteOnly | QFile::Text); |
78 | } else { |
79 | file.setFileName(outFileName); |
80 | isOk = file.open(QFile::WriteOnly | QFile::Text); |
81 | } |
82 | if (!isOk) { |
83 | fprintf(stderr, "Unable to open %s: %s\n" , |
84 | outFileName.isEmpty() ? qPrintable(outFileName) : "standard output" , |
85 | qPrintable(file.errorString())); |
86 | return 1; |
87 | } |
88 | |
89 | QTextStream out(&file); |
90 | out << QLatin1String("<!DOCTYPE RCC><RCC version=\"1.0\">\n" |
91 | "<qresource>\n" ); |
92 | |
93 | // use "." as dir to get relative file pathes |
94 | dumpRecursive(QDir(QLatin1String("." )), out); |
95 | |
96 | out << QLatin1String("</qresource>\n" |
97 | "</RCC>\n" ); |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | // Escapes a path for use in a Depfile (Makefile syntax) |
103 | QString makefileEscape(const QString &filepath) |
104 | { |
105 | // Always use forward slashes |
106 | QString result = QDir::cleanPath(filepath); |
107 | // Spaces are escaped with a backslash |
108 | result.replace(QLatin1Char(' '), QLatin1String("\\ " )); |
109 | // Pipes are escaped with a backslash |
110 | result.replace(QLatin1Char('|'), QLatin1String("\\|" )); |
111 | // Dollars are escaped with a dollar |
112 | result.replace(QLatin1Char('$'), QLatin1String("$$" )); |
113 | |
114 | return result; |
115 | } |
116 | |
117 | void writeDepFile(QIODevice &iodev, const QStringList &depsList, const QString &targetName) |
118 | { |
119 | QTextStream out(&iodev); |
120 | out << qPrintable(makefileEscape(targetName)); |
121 | out << QLatin1Char(':'); |
122 | |
123 | // Write depfile |
124 | for (int i = 0; i < depsList.size(); ++i) { |
125 | out << QLatin1Char(' '); |
126 | |
127 | out << qPrintable(makefileEscape(depsList.at(i))); |
128 | } |
129 | |
130 | out << QLatin1Char('\n'); |
131 | } |
132 | |
133 | int runRcc(int argc, char *argv[]) |
134 | { |
135 | QCoreApplication app(argc, argv); |
136 | QCoreApplication::setApplicationVersion(QStringLiteral(QT_VERSION_STR)); |
137 | |
138 | // Note that rcc isn't translated. |
139 | // If you use this code as an example for a translated app, make sure to translate the strings. |
140 | QCommandLineParser parser; |
141 | parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); |
142 | parser.setApplicationDescription(QLatin1String("Qt Resource Compiler version " QT_VERSION_STR)); |
143 | parser.addHelpOption(); |
144 | parser.addVersionOption(); |
145 | |
146 | QCommandLineOption outputOption(QStringList() << QStringLiteral("o" ) << QStringLiteral("output" )); |
147 | outputOption.setDescription(QStringLiteral("Write output to <file> rather than stdout." )); |
148 | outputOption.setValueName(QStringLiteral("file" )); |
149 | parser.addOption(outputOption); |
150 | |
151 | QCommandLineOption tempOption(QStringList() << QStringLiteral("t" ) << QStringLiteral("temp" )); |
152 | tempOption.setDescription(QStringLiteral("Use temporary <file> for big resources." )); |
153 | tempOption.setValueName(QStringLiteral("file" )); |
154 | parser.addOption(tempOption); |
155 | |
156 | QCommandLineOption nameOption(QStringLiteral("name" ), QStringLiteral("Create an external initialization function with <name>." ), QStringLiteral("name" )); |
157 | parser.addOption(nameOption); |
158 | |
159 | QCommandLineOption rootOption(QStringLiteral("root" ), QStringLiteral("Prefix resource access path with root path." ), QStringLiteral("path" )); |
160 | parser.addOption(rootOption); |
161 | |
162 | #if QT_CONFIG(zstd) && !defined(QT_NO_COMPRESS) |
163 | # define ALGOS "[zstd], zlib, none" |
164 | #elif QT_CONFIG(zstd) |
165 | # define ALGOS "[zstd], none" |
166 | #elif !defined(QT_NO_COMPRESS) |
167 | # define ALGOS "[zlib], none" |
168 | #else |
169 | # define ALGOS "[none]" |
170 | #endif |
171 | const QString &algoDescription = |
172 | QStringLiteral("Compress input files using algorithm <algo> (" ALGOS ")." ); |
173 | QCommandLineOption compressionAlgoOption(QStringLiteral("compress-algo" ), algoDescription, QStringLiteral("algo" )); |
174 | parser.addOption(compressionAlgoOption); |
175 | #undef ALGOS |
176 | |
177 | QCommandLineOption compressOption(QStringLiteral("compress" ), QStringLiteral("Compress input files by <level>." ), QStringLiteral("level" )); |
178 | parser.addOption(compressOption); |
179 | |
180 | QCommandLineOption nocompressOption(QStringLiteral("no-compress" ), QStringLiteral("Disable all compression. Same as --compress-algo=none." )); |
181 | parser.addOption(nocompressOption); |
182 | |
183 | QCommandLineOption noZstdOption(QStringLiteral("no-zstd" ), QStringLiteral("Disable usage of zstd compression." )); |
184 | parser.addOption(noZstdOption); |
185 | |
186 | QCommandLineOption thresholdOption(QStringLiteral("threshold" ), QStringLiteral("Threshold to consider compressing files." ), QStringLiteral("level" )); |
187 | parser.addOption(thresholdOption); |
188 | |
189 | QCommandLineOption binaryOption(QStringLiteral("binary" ), QStringLiteral("Output a binary file for use as a dynamic resource." )); |
190 | parser.addOption(binaryOption); |
191 | |
192 | QCommandLineOption generatorOption(QStringList{QStringLiteral("g" ), QStringLiteral("generator" )}); |
193 | generatorOption.setDescription(QStringLiteral("Select generator." )); |
194 | generatorOption.setValueName(QStringLiteral("cpp|python|python2" )); |
195 | parser.addOption(generatorOption); |
196 | |
197 | QCommandLineOption passOption(QStringLiteral("pass" ), QStringLiteral("Pass number for big resources" ), QStringLiteral("number" )); |
198 | parser.addOption(passOption); |
199 | |
200 | QCommandLineOption namespaceOption(QStringLiteral("namespace" ), QStringLiteral("Turn off namespace macros." )); |
201 | parser.addOption(namespaceOption); |
202 | |
203 | QCommandLineOption verboseOption(QStringLiteral("verbose" ), QStringLiteral("Enable verbose mode." )); |
204 | parser.addOption(verboseOption); |
205 | |
206 | QCommandLineOption listOption(QStringLiteral("list" ), QStringLiteral("Only list .qrc file entries, do not generate code." )); |
207 | parser.addOption(listOption); |
208 | |
209 | QCommandLineOption mapOption(QStringLiteral("list-mapping" ), |
210 | QStringLiteral("Only output a mapping of resource paths to file system paths defined in the .qrc file, do not generate code." )); |
211 | parser.addOption(mapOption); |
212 | |
213 | QCommandLineOption depFileOption(QStringList{QStringLiteral("d" ), QStringLiteral("depfile" )}, |
214 | QStringLiteral("Write a depfile with the .qrc dependencies to <file>." ), QStringLiteral("file" )); |
215 | parser.addOption(depFileOption); |
216 | |
217 | QCommandLineOption projectOption(QStringLiteral("project" ), QStringLiteral("Output a resource file containing all files from the current directory." )); |
218 | parser.addOption(projectOption); |
219 | |
220 | QCommandLineOption formatVersionOption(QStringLiteral("format-version" ), QStringLiteral("The RCC format version to write" ), QStringLiteral("number" )); |
221 | parser.addOption(formatVersionOption); |
222 | |
223 | parser.addPositionalArgument(QStringLiteral("inputs" ), QStringLiteral("Input files (*.qrc)." )); |
224 | |
225 | |
226 | //parse options |
227 | parser.process(app); |
228 | |
229 | QString errorMsg; |
230 | |
231 | quint8 formatVersion = 3; |
232 | if (parser.isSet(formatVersionOption)) { |
233 | bool ok = false; |
234 | formatVersion = parser.value(formatVersionOption).toUInt(&ok); |
235 | if (!ok) { |
236 | errorMsg = QLatin1String("Invalid format version specified" ); |
237 | } else if (formatVersion < 1 || formatVersion > 3) { |
238 | errorMsg = QLatin1String("Unsupported format version specified" ); |
239 | } |
240 | } |
241 | |
242 | RCCResourceLibrary library(formatVersion); |
243 | if (parser.isSet(nameOption)) |
244 | library.setInitName(parser.value(nameOption)); |
245 | if (parser.isSet(rootOption)) { |
246 | library.setResourceRoot(QDir::cleanPath(parser.value(rootOption))); |
247 | if (library.resourceRoot().isEmpty() |
248 | || library.resourceRoot().at(0) != QLatin1Char('/')) |
249 | errorMsg = QLatin1String("Root must start with a /" ); |
250 | } |
251 | |
252 | if (parser.isSet(compressionAlgoOption)) |
253 | library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg)); |
254 | if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd) |
255 | errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher" ); |
256 | if (parser.isSet(nocompressOption)) |
257 | library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); |
258 | if (parser.isSet(noZstdOption)) |
259 | library.setNoZstd(true); |
260 | if (parser.isSet(compressOption) && errorMsg.isEmpty()) { |
261 | int level = library.parseCompressionLevel(library.compressionAlgorithm(), parser.value(compressOption), &errorMsg); |
262 | library.setCompressLevel(level); |
263 | } |
264 | if (parser.isSet(thresholdOption)) |
265 | library.setCompressThreshold(parser.value(thresholdOption).toInt()); |
266 | if (parser.isSet(binaryOption)) |
267 | library.setFormat(RCCResourceLibrary::Binary); |
268 | if (parser.isSet(generatorOption)) { |
269 | auto value = parser.value(generatorOption); |
270 | if (value == QLatin1String("cpp" )) |
271 | library.setFormat(RCCResourceLibrary::C_Code); |
272 | else if (value == QLatin1String("python" )) |
273 | library.setFormat(RCCResourceLibrary::Python3_Code); |
274 | else if (value == QLatin1String("python2" )) |
275 | library.setFormat(RCCResourceLibrary::Python2_Code); |
276 | else |
277 | errorMsg = QLatin1String("Invalid generator: " ) + value; |
278 | } |
279 | |
280 | if (parser.isSet(passOption)) { |
281 | if (parser.value(passOption) == QLatin1String("1" )) |
282 | library.setFormat(RCCResourceLibrary::Pass1); |
283 | else if (parser.value(passOption) == QLatin1String("2" )) |
284 | library.setFormat(RCCResourceLibrary::Pass2); |
285 | else |
286 | errorMsg = QLatin1String("Pass number must be 1 or 2" ); |
287 | } |
288 | if (parser.isSet(namespaceOption)) |
289 | library.setUseNameSpace(!library.useNameSpace()); |
290 | if (parser.isSet(verboseOption)) |
291 | library.setVerbose(true); |
292 | |
293 | const bool list = parser.isSet(listOption); |
294 | const bool map = parser.isSet(mapOption); |
295 | const bool projectRequested = parser.isSet(projectOption); |
296 | const QStringList filenamesIn = parser.positionalArguments(); |
297 | |
298 | for (const QString &file : filenamesIn) { |
299 | if (file == QLatin1String("-" )) |
300 | continue; |
301 | else if (!QFile::exists(file)) { |
302 | qWarning("%s: File does not exist '%s'" , argv[0], qPrintable(file)); |
303 | return 1; |
304 | } |
305 | } |
306 | |
307 | QString outFilename = parser.value(outputOption); |
308 | QString tempFilename = parser.value(tempOption); |
309 | QString depFilename = parser.value(depFileOption); |
310 | |
311 | if (projectRequested) { |
312 | return createProject(outFilename); |
313 | } |
314 | |
315 | if (filenamesIn.isEmpty()) |
316 | errorMsg = QStringLiteral("No input files specified." ); |
317 | |
318 | if (!errorMsg.isEmpty()) { |
319 | fprintf(stderr, "%s: %s\n" , argv[0], qPrintable(errorMsg)); |
320 | parser.showHelp(1); |
321 | return 1; |
322 | } |
323 | QFile errorDevice; |
324 | errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text); |
325 | |
326 | if (library.verbose()) |
327 | errorDevice.write("Qt resource compiler\n" ); |
328 | |
329 | library.setInputFiles(filenamesIn); |
330 | |
331 | if (!library.readFiles(list || map, errorDevice)) |
332 | return 1; |
333 | |
334 | QFile out; |
335 | |
336 | // open output |
337 | QIODevice::OpenMode mode = QIODevice::NotOpen; |
338 | switch (library.format()) { |
339 | case RCCResourceLibrary::C_Code: |
340 | case RCCResourceLibrary::Pass1: |
341 | case RCCResourceLibrary::Python3_Code: |
342 | case RCCResourceLibrary::Python2_Code: |
343 | mode = QIODevice::WriteOnly | QIODevice::Text; |
344 | break; |
345 | case RCCResourceLibrary::Pass2: |
346 | case RCCResourceLibrary::Binary: |
347 | mode = QIODevice::WriteOnly; |
348 | break; |
349 | } |
350 | |
351 | |
352 | if (outFilename.isEmpty() || outFilename == QLatin1String("-" )) { |
353 | #ifdef Q_OS_WIN |
354 | // Make sure fwrite to stdout doesn't do LF->CRLF |
355 | if (library.format() == RCCResourceLibrary::Binary) |
356 | _setmode(_fileno(stdout), _O_BINARY); |
357 | // Make sure QIODevice does not do LF->CRLF, |
358 | // otherwise we'll end up in CRCRLF instead of |
359 | // CRLF. |
360 | mode &= ~QIODevice::Text; |
361 | #endif // Q_OS_WIN |
362 | // using this overload close() only flushes. |
363 | out.open(stdout, mode); |
364 | } else { |
365 | out.setFileName(outFilename); |
366 | if (!out.open(mode)) { |
367 | const QString msg = QString::fromLatin1("Unable to open %1 for writing: %2\n" ) |
368 | .arg(outFilename, out.errorString()); |
369 | errorDevice.write(msg.toUtf8()); |
370 | return 1; |
371 | } |
372 | } |
373 | |
374 | // do the task |
375 | if (list) { |
376 | const QStringList data = library.dataFiles(); |
377 | for (int i = 0; i < data.size(); ++i) { |
378 | out.write(qPrintable(QDir::cleanPath(data.at(i)))); |
379 | out.write("\n" ); |
380 | } |
381 | return 0; |
382 | } |
383 | |
384 | if (map) { |
385 | const RCCResourceLibrary::ResourceDataFileMap data = library.resourceDataFileMap(); |
386 | for (auto it = data.begin(), end = data.end(); it != end; ++it) { |
387 | out.write(qPrintable(it.key())); |
388 | out.write("\t" ); |
389 | out.write(qPrintable(QDir::cleanPath(it.value()))); |
390 | out.write("\n" ); |
391 | } |
392 | return 0; |
393 | } |
394 | |
395 | // Write depfile |
396 | if (!depFilename.isEmpty()) { |
397 | QFile depout; |
398 | depout.setFileName(depFilename); |
399 | |
400 | if (outFilename.isEmpty() || outFilename == QLatin1String("-" )) { |
401 | const QString msg = QString::fromUtf8("Unable to write depfile when outputting to stdout!\n" ); |
402 | errorDevice.write(msg.toUtf8()); |
403 | return 1; |
404 | } |
405 | |
406 | if (!depout.open(QIODevice::WriteOnly | QIODevice::Text)) { |
407 | const QString msg = QString::fromUtf8("Unable to open depfile %1 for writing: %2\n" ) |
408 | .arg(depout.fileName(), depout.errorString()); |
409 | errorDevice.write(msg.toUtf8()); |
410 | return 1; |
411 | } |
412 | |
413 | writeDepFile(depout, library.dataFiles(), outFilename); |
414 | depout.close(); |
415 | } |
416 | |
417 | QFile temp; |
418 | if (!tempFilename.isEmpty()) { |
419 | temp.setFileName(tempFilename); |
420 | if (!temp.open(QIODevice::ReadOnly)) { |
421 | const QString msg = QString::fromUtf8("Unable to open temporary file %1 for reading: %2\n" ) |
422 | .arg(outFilename, out.errorString()); |
423 | errorDevice.write(msg.toUtf8()); |
424 | return 1; |
425 | } |
426 | } |
427 | bool success = library.output(out, temp, errorDevice); |
428 | if (!success) { |
429 | // erase the output file if we failed |
430 | out.remove(); |
431 | return 1; |
432 | } |
433 | return 0; |
434 | } |
435 | |
436 | QT_END_NAMESPACE |
437 | |
438 | int main(int argc, char *argv[]) |
439 | { |
440 | // rcc uses a QHash to store files in the resource system. |
441 | // we must force a certain hash order when testing or tst_rcc will fail, see QTBUG-25078 |
442 | // similar requirements exist for reproducibly builds. |
443 | qSetGlobalQHashSeed(0); |
444 | if (qGlobalQHashSeed() != 0) |
445 | qWarning("Cannot force QHash seed" ); |
446 | |
447 | return QT_PREPEND_NAMESPACE(runRcc)(argc, argv); |
448 | } |
449 | |