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
dumpRecursive(const QDir & dir,QTextStream & out)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
createProject(const QString & outFileName)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)
makefileEscape(const QString & filepath)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
writeDepFile(QIODevice & iodev,const QStringList & depsList,const QString & targetName)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
runRcc(int argc,char * argv[])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 thresholdOption(QStringLiteral("threshold"), QStringLiteral("Threshold to consider compressing files."), QStringLiteral("level"));
184 parser.addOption(thresholdOption);
185
186 QCommandLineOption binaryOption(QStringLiteral("binary"), QStringLiteral("Output a binary file for use as a dynamic resource."));
187 parser.addOption(binaryOption);
188
189 QCommandLineOption generatorOption(QStringList{QStringLiteral("g"), QStringLiteral("generator")});
190 generatorOption.setDescription(QStringLiteral("Select generator."));
191 generatorOption.setValueName(QStringLiteral("cpp|python|python2"));
192 parser.addOption(generatorOption);
193
194 QCommandLineOption passOption(QStringLiteral("pass"), QStringLiteral("Pass number for big resources"), QStringLiteral("number"));
195 parser.addOption(passOption);
196
197 QCommandLineOption namespaceOption(QStringLiteral("namespace"), QStringLiteral("Turn off namespace macros."));
198 parser.addOption(namespaceOption);
199
200 QCommandLineOption verboseOption(QStringLiteral("verbose"), QStringLiteral("Enable verbose mode."));
201 parser.addOption(verboseOption);
202
203 QCommandLineOption listOption(QStringLiteral("list"), QStringLiteral("Only list .qrc file entries, do not generate code."));
204 parser.addOption(listOption);
205
206 QCommandLineOption mapOption(QStringLiteral("list-mapping"),
207 QStringLiteral("Only output a mapping of resource paths to file system paths defined in the .qrc file, do not generate code."));
208 parser.addOption(mapOption);
209
210 QCommandLineOption depFileOption(QStringList{QStringLiteral("d"), QStringLiteral("depfile")},
211 QStringLiteral("Write a depfile with the .qrc dependencies to <file>."), QStringLiteral("file"));
212 parser.addOption(depFileOption);
213
214 QCommandLineOption projectOption(QStringLiteral("project"), QStringLiteral("Output a resource file containing all files from the current directory."));
215 parser.addOption(projectOption);
216
217 QCommandLineOption formatVersionOption(QStringLiteral("format-version"), QStringLiteral("The RCC format version to write"), QStringLiteral("number"));
218 parser.addOption(formatVersionOption);
219
220 parser.addPositionalArgument(QStringLiteral("inputs"), QStringLiteral("Input files (*.qrc)."));
221
222
223 //parse options
224 parser.process(app);
225
226 QString errorMsg;
227
228 quint8 formatVersion = 3;
229 if (parser.isSet(formatVersionOption)) {
230 bool ok = false;
231 formatVersion = parser.value(formatVersionOption).toUInt(&ok);
232 if (!ok) {
233 errorMsg = QLatin1String("Invalid format version specified");
234 } else if (formatVersion < 1 || formatVersion > 3) {
235 errorMsg = QLatin1String("Unsupported format version specified");
236 }
237 }
238
239 RCCResourceLibrary library(formatVersion);
240 if (parser.isSet(nameOption))
241 library.setInitName(parser.value(nameOption));
242 if (parser.isSet(rootOption)) {
243 library.setResourceRoot(QDir::cleanPath(parser.value(rootOption)));
244 if (library.resourceRoot().isEmpty()
245 || library.resourceRoot().at(0) != QLatin1Char('/'))
246 errorMsg = QLatin1String("Root must start with a /");
247 }
248
249 if (parser.isSet(compressionAlgoOption))
250 library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg));
251 if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd)
252 errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher");
253 if (parser.isSet(nocompressOption))
254 library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None);
255 if (parser.isSet(compressOption) && errorMsg.isEmpty()) {
256 int level = library.parseCompressionLevel(library.compressionAlgorithm(), parser.value(compressOption), &errorMsg);
257 library.setCompressLevel(level);
258 }
259 if (parser.isSet(thresholdOption))
260 library.setCompressThreshold(parser.value(thresholdOption).toInt());
261 if (parser.isSet(binaryOption))
262 library.setFormat(RCCResourceLibrary::Binary);
263 if (parser.isSet(generatorOption)) {
264 auto value = parser.value(generatorOption);
265 if (value == QLatin1String("cpp"))
266 library.setFormat(RCCResourceLibrary::C_Code);
267 else if (value == QLatin1String("python"))
268 library.setFormat(RCCResourceLibrary::Python3_Code);
269 else if (value == QLatin1String("python2"))
270 library.setFormat(RCCResourceLibrary::Python2_Code);
271 else
272 errorMsg = QLatin1String("Invalid generator: ") + value;
273 }
274
275 if (parser.isSet(passOption)) {
276 if (parser.value(passOption) == QLatin1String("1"))
277 library.setFormat(RCCResourceLibrary::Pass1);
278 else if (parser.value(passOption) == QLatin1String("2"))
279 library.setFormat(RCCResourceLibrary::Pass2);
280 else
281 errorMsg = QLatin1String("Pass number must be 1 or 2");
282 }
283 if (parser.isSet(namespaceOption))
284 library.setUseNameSpace(!library.useNameSpace());
285 if (parser.isSet(verboseOption))
286 library.setVerbose(true);
287
288 const bool list = parser.isSet(listOption);
289 const bool map = parser.isSet(mapOption);
290 const bool projectRequested = parser.isSet(projectOption);
291 const QStringList filenamesIn = parser.positionalArguments();
292
293 for (const QString &file : filenamesIn) {
294 if (file == QLatin1String("-"))
295 continue;
296 else if (!QFile::exists(file)) {
297 qWarning("%s: File does not exist '%s'", argv[0], qPrintable(file));
298 return 1;
299 }
300 }
301
302 QString outFilename = parser.value(outputOption);
303 QString tempFilename = parser.value(tempOption);
304 QString depFilename = parser.value(depFileOption);
305
306 if (projectRequested) {
307 return createProject(outFilename);
308 }
309
310 if (filenamesIn.isEmpty())
311 errorMsg = QStringLiteral("No input files specified.");
312
313 if (!errorMsg.isEmpty()) {
314 fprintf(stderr, "%s: %s\n", argv[0], qPrintable(errorMsg));
315 parser.showHelp(1);
316 return 1;
317 }
318 QFile errorDevice;
319 errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text);
320
321 if (library.verbose())
322 errorDevice.write("Qt resource compiler\n");
323
324 library.setInputFiles(filenamesIn);
325
326 if (!library.readFiles(list || map, errorDevice))
327 return 1;
328
329 QFile out;
330
331 // open output
332 QIODevice::OpenMode mode = QIODevice::NotOpen;
333 switch (library.format()) {
334 case RCCResourceLibrary::C_Code:
335 case RCCResourceLibrary::Pass1:
336 case RCCResourceLibrary::Python3_Code:
337 case RCCResourceLibrary::Python2_Code:
338 mode = QIODevice::WriteOnly | QIODevice::Text;
339 break;
340 case RCCResourceLibrary::Pass2:
341 case RCCResourceLibrary::Binary:
342 mode = QIODevice::WriteOnly;
343 break;
344 }
345
346
347 if (outFilename.isEmpty() || outFilename == QLatin1String("-")) {
348 #ifdef Q_OS_WIN
349 // Make sure fwrite to stdout doesn't do LF->CRLF
350 if (library.format() == RCCResourceLibrary::Binary)
351 _setmode(_fileno(stdout), _O_BINARY);
352 // Make sure QIODevice does not do LF->CRLF,
353 // otherwise we'll end up in CRCRLF instead of
354 // CRLF.
355 mode &= ~QIODevice::Text;
356 #endif // Q_OS_WIN
357 // using this overload close() only flushes.
358 out.open(stdout, mode);
359 } else {
360 out.setFileName(outFilename);
361 if (!out.open(mode)) {
362 const QString msg = QString::fromLatin1("Unable to open %1 for writing: %2\n")
363 .arg(outFilename, out.errorString());
364 errorDevice.write(msg.toUtf8());
365 return 1;
366 }
367 }
368
369 // do the task
370 if (list) {
371 const QStringList data = library.dataFiles();
372 for (int i = 0; i < data.size(); ++i) {
373 out.write(qPrintable(QDir::cleanPath(data.at(i))));
374 out.write("\n");
375 }
376 return 0;
377 }
378
379 if (map) {
380 const RCCResourceLibrary::ResourceDataFileMap data = library.resourceDataFileMap();
381 for (auto it = data.begin(), end = data.end(); it != end; ++it) {
382 out.write(qPrintable(it.key()));
383 out.write("\t");
384 out.write(qPrintable(QDir::cleanPath(it.value())));
385 out.write("\n");
386 }
387 return 0;
388 }
389
390 // Write depfile
391 if (!depFilename.isEmpty()) {
392 QFile depout;
393 depout.setFileName(depFilename);
394
395 if (outFilename.isEmpty() || outFilename == QLatin1String("-")) {
396 const QString msg = QString::fromUtf8("Unable to write depfile when outputting to stdout!\n");
397 errorDevice.write(msg.toUtf8());
398 return 1;
399 }
400
401 if (!depout.open(QIODevice::WriteOnly | QIODevice::Text)) {
402 const QString msg = QString::fromUtf8("Unable to open depfile %1 for writing: %2\n")
403 .arg(depout.fileName(), depout.errorString());
404 errorDevice.write(msg.toUtf8());
405 return 1;
406 }
407
408 writeDepFile(depout, library.dataFiles(), outFilename);
409 depout.close();
410 }
411
412 QFile temp;
413 if (!tempFilename.isEmpty()) {
414 temp.setFileName(tempFilename);
415 if (!temp.open(QIODevice::ReadOnly)) {
416 const QString msg = QString::fromUtf8("Unable to open temporary file %1 for reading: %2\n")
417 .arg(outFilename, out.errorString());
418 errorDevice.write(msg.toUtf8());
419 return 1;
420 }
421 }
422 bool success = library.output(out, temp, errorDevice);
423 if (!success) {
424 // erase the output file if we failed
425 out.remove();
426 return 1;
427 }
428 return 0;
429 }
430
431 QT_END_NAMESPACE
432
main(int argc,char * argv[])433 int main(int argc, char *argv[])
434 {
435 // rcc uses a QHash to store files in the resource system.
436 // we must force a certain hash order when testing or tst_rcc will fail, see QTBUG-25078
437 // similar requirements exist for reproducibly builds.
438 qSetGlobalQHashSeed(0);
439 if (qGlobalQHashSeed() != 0)
440 qWarning("Cannot force QHash seed");
441
442 return QT_PREPEND_NAMESPACE(runRcc)(argc, argv);
443 }
444