1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <profileevaluator.h>
30#include <profileutils.h>
31#include <qmakeparser.h>
32#include <qmakevfs.h>
33#include <qrcreader.h>
34
35#include <QtCore/QCoreApplication>
36#include <QtCore/QDebug>
37#include <QtCore/QDir>
38#include <QtCore/QDirIterator>
39#include <QtCore/QFile>
40#include <QtCore/QFileInfo>
41#include <QtCore/QRegExp>
42#include <QtCore/QString>
43#include <QtCore/QStringList>
44
45#include <QtCore/QJsonArray>
46#include <QtCore/QJsonDocument>
47#include <QtCore/QJsonObject>
48
49#include <iostream>
50
51static void printOut(const QString &out)
52{
53 std::cout << qPrintable(out);
54}
55
56static void printErr(const QString &out)
57{
58 std::cerr << qPrintable(out);
59}
60
61static QJsonValue toJsonValue(const QJsonValue &v)
62{
63 return v;
64}
65
66static QJsonValue toJsonValue(const QString &s)
67{
68 return QJsonValue(s);
69}
70
71static QJsonValue toJsonValue(const QStringList &lst)
72{
73 return QJsonArray::fromStringList(list: lst);
74}
75
76template <class T>
77void setValue(QJsonObject &obj, const char *key, T value)
78{
79 obj[QLatin1String(key)] = toJsonValue(value);
80}
81
82class LD {
83 Q_DECLARE_TR_FUNCTIONS(LProDump)
84};
85
86static void printUsage()
87{
88 printOut(out: LD::tr(
89 sourceText: "Usage:\n"
90 " lprodump [options] project-file...\n"
91 "lprodump is part of Qt's Linguist tool chain. It extracts information\n"
92 "from qmake projects to a .json file. This file can be passed to\n"
93 "lupdate/lrelease using the -project option.\n\n"
94 "Options:\n"
95 " -help Display this information and exit.\n"
96 " -silent\n"
97 " Do not explain what is being done.\n"
98 " -pro <filename>\n"
99 " Name of a .pro file. Useful for files with .pro file syntax but\n"
100 " different file suffix. Projects are recursed into and merged.\n"
101 " -pro-out <directory>\n"
102 " Virtual output directory for processing subsequent .pro files.\n"
103 " -pro-debug\n"
104 " Trace processing .pro files. Specify twice for more verbosity.\n"
105 " -out <filename>\n"
106 " Name of the output file.\n"
107 " -version\n"
108 " Display the version of lprodump and exit.\n"
109 ));
110}
111
112static void print(const QString &fileName, int lineNo, const QString &msg)
113{
114 if (lineNo > 0)
115 printErr(out: QString::fromLatin1(str: "WARNING: %1:%2: %3\n").arg(args: fileName, args: QString::number(lineNo), args: msg));
116 else if (lineNo)
117 printErr(out: QString::fromLatin1(str: "WARNING: %1: %2\n").arg(a1: fileName, a2: msg));
118 else
119 printErr(out: QString::fromLatin1(str: "WARNING: %1\n").arg(a: msg));
120}
121
122class EvalHandler : public QMakeHandler {
123public:
124 virtual void message(int type, const QString &msg, const QString &fileName, int lineNo)
125 {
126 if (verbose && !(type & CumulativeEvalMessage) && (type & CategoryMask) == ErrorMessage)
127 print(fileName, lineNo, msg);
128 }
129
130 virtual void fileMessage(int type, const QString &msg)
131 {
132 if (verbose && !(type & CumulativeEvalMessage) && (type & CategoryMask) == ErrorMessage) {
133 // "Downgrade" errors, as we don't really care for them
134 printErr(out: QLatin1String("WARNING: ") + msg + QLatin1Char('\n'));
135 }
136 }
137
138 virtual void aboutToEval(ProFile *, ProFile *, EvalFileType) {}
139 virtual void doneWithEval(ProFile *) {}
140
141 bool verbose = true;
142};
143
144static EvalHandler evalHandler;
145
146static bool isSupportedExtension(const QString &ext)
147{
148 return ext == QLatin1String("qml")
149 || ext == QLatin1String("js") || ext == QLatin1String("qs")
150 || ext == QLatin1String("ui") || ext == QLatin1String("jui");
151}
152
153static QStringList getResources(const QString &resourceFile, QMakeVfs *vfs)
154{
155 Q_ASSERT(vfs);
156 if (!vfs->exists(fn: resourceFile, flags: QMakeVfs::VfsCumulative))
157 return QStringList();
158 QString content;
159 QString errStr;
160 if (vfs->readFile(id: vfs->idForFileName(fn: resourceFile, flags: QMakeVfs::VfsCumulative),
161 contents: &content, errStr: &errStr) != QMakeVfs::ReadOk) {
162 printErr(out: LD::tr(sourceText: "lprodump error: Cannot read %1: %2\n").arg(args: resourceFile, args&: errStr));
163 return QStringList();
164 }
165 const ReadQrcResult rqr = readQrcFile(resourceFile, content);
166 if (rqr.hasError()) {
167 printErr(out: LD::tr(sourceText: "lprodump error: %1:%2: %3\n")
168 .arg(args: resourceFile, args: QString::number(rqr.line), args: rqr.errorString));
169 }
170 return rqr.files;
171}
172
173static QStringList getSources(const char *var, const char *vvar, const QStringList &baseVPaths,
174 const QString &projectDir, const ProFileEvaluator &visitor)
175{
176 QStringList vPaths = visitor.absolutePathValues(variable: QLatin1String(vvar), baseDirectory: projectDir);
177 vPaths += baseVPaths;
178 vPaths.removeDuplicates();
179 return visitor.absoluteFileValues(variable: QLatin1String(var), baseDirectory: projectDir, searchDirs: vPaths, pro: 0);
180}
181
182static QStringList getSources(const ProFileEvaluator &visitor, const QString &projectDir,
183 const QStringList &excludes, QMakeVfs *vfs)
184{
185 QStringList baseVPaths;
186 baseVPaths += visitor.absolutePathValues(variable: QLatin1String("VPATH"), baseDirectory: projectDir);
187 baseVPaths << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
188 baseVPaths.removeDuplicates();
189
190 QStringList sourceFiles;
191
192 // app/lib template
193 sourceFiles += getSources(var: "SOURCES", vvar: "VPATH_SOURCES", baseVPaths, projectDir, visitor);
194 sourceFiles += getSources(var: "HEADERS", vvar: "VPATH_HEADERS", baseVPaths, projectDir, visitor);
195
196 sourceFiles += getSources(var: "FORMS", vvar: "VPATH_FORMS", baseVPaths, projectDir, visitor);
197
198 QStringList resourceFiles = getSources(var: "RESOURCES", vvar: "VPATH_RESOURCES", baseVPaths, projectDir, visitor);
199 foreach (const QString &resource, resourceFiles)
200 sourceFiles += getResources(resourceFile: resource, vfs);
201
202 QStringList installs = visitor.values(variableName: QLatin1String("INSTALLS"))
203 + visitor.values(variableName: QLatin1String("DEPLOYMENT"));
204 installs.removeDuplicates();
205 QDir baseDir(projectDir);
206 foreach (const QString inst, installs) {
207 foreach (const QString &file, visitor.values(inst + QLatin1String(".files"))) {
208 QFileInfo info(file);
209 if (!info.isAbsolute())
210 info.setFile(baseDir.absoluteFilePath(fileName: file));
211 QStringList nameFilter;
212 QString searchPath;
213 if (info.isDir()) {
214 nameFilter << QLatin1String("*");
215 searchPath = info.filePath();
216 } else {
217 nameFilter << info.fileName();
218 searchPath = info.path();
219 }
220
221 QDirIterator iterator(searchPath, nameFilter,
222 QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks,
223 QDirIterator::Subdirectories);
224 while (iterator.hasNext()) {
225 iterator.next();
226 QFileInfo cfi = iterator.fileInfo();
227 if (isSupportedExtension(ext: cfi.suffix()))
228 sourceFiles << cfi.filePath();
229 }
230 }
231 }
232
233 sourceFiles.removeDuplicates();
234 sourceFiles.sort();
235
236 foreach (const QString &ex, excludes) {
237 // TODO: take advantage of the file list being sorted
238 QRegExp rx(ex, Qt::CaseSensitive, QRegExp::Wildcard);
239 for (QStringList::Iterator it = sourceFiles.begin(); it != sourceFiles.end(); ) {
240 if (rx.exactMatch(str: *it))
241 it = sourceFiles.erase(it);
242 else
243 ++it;
244 }
245 }
246
247 return sourceFiles;
248}
249
250QStringList getExcludes(const ProFileEvaluator &visitor, const QString &projectDirPath)
251{
252 const QStringList trExcludes = visitor.values(variableName: QLatin1String("TR_EXCLUDE"));
253 QStringList excludes;
254 excludes.reserve(alloc: trExcludes.size());
255 const QDir projectDir(projectDirPath);
256 for (const QString &ex : trExcludes)
257 excludes << QDir::cleanPath(path: projectDir.absoluteFilePath(fileName: ex));
258 return excludes;
259}
260
261static void excludeProjects(const ProFileEvaluator &visitor, QStringList *subProjects)
262{
263 foreach (const QString &ex, visitor.values(QLatin1String("TR_EXCLUDE"))) {
264 QRegExp rx(ex, Qt::CaseSensitive, QRegExp::Wildcard);
265 for (QStringList::Iterator it = subProjects->begin(); it != subProjects->end(); ) {
266 if (rx.exactMatch(str: *it))
267 it = subProjects->erase(it);
268 else
269 ++it;
270 }
271 }
272}
273
274static QJsonArray processProjects(bool topLevel, const QStringList &proFiles,
275 const QHash<QString, QString> &outDirMap,
276 ProFileGlobals *option, QMakeVfs *vfs, QMakeParser *parser,
277 bool *fail);
278
279static QJsonObject processProject(const QString &proFile, ProFileGlobals *option, QMakeVfs *vfs,
280 QMakeParser *parser, ProFileEvaluator &visitor)
281{
282 QJsonObject result;
283 QStringList tmp = visitor.values(variableName: QLatin1String("CODECFORSRC"));
284 if (!tmp.isEmpty())
285 result[QStringLiteral("codec")] = tmp.last();
286 QString proPath = QFileInfo(proFile).path();
287 if (visitor.templateType() == ProFileEvaluator::TT_Subdirs) {
288 QStringList subProjects = visitor.values(variableName: QLatin1String("SUBDIRS"));
289 excludeProjects(visitor, subProjects: &subProjects);
290 QStringList subProFiles;
291 QDir proDir(proPath);
292 foreach (const QString &subdir, subProjects) {
293 QString realdir = visitor.value(variableName: subdir + QLatin1String(".subdir"));
294 if (realdir.isEmpty())
295 realdir = visitor.value(variableName: subdir + QLatin1String(".file"));
296 if (realdir.isEmpty())
297 realdir = subdir;
298 QString subPro = QDir::cleanPath(path: proDir.absoluteFilePath(fileName: realdir));
299 QFileInfo subInfo(subPro);
300 if (subInfo.isDir()) {
301 subProFiles << (subPro + QLatin1Char('/')
302 + subInfo.fileName() + QLatin1String(".pro"));
303 } else {
304 subProFiles << subPro;
305 }
306 }
307 QJsonArray subResults = processProjects(topLevel: false, proFiles: subProFiles,
308 outDirMap: QHash<QString, QString>(), option, vfs, parser,
309 fail: nullptr);
310 if (!subResults.isEmpty())
311 setValue(obj&: result, key: "subProjects", value: subResults);
312 } else {
313 const QStringList excludes = getExcludes(visitor, projectDirPath: proPath);
314 const QStringList sourceFiles = getSources(visitor, projectDir: proPath, excludes, vfs);
315 setValue(obj&: result, key: "includePaths",
316 value: visitor.absolutePathValues(variable: QLatin1String("INCLUDEPATH"), baseDirectory: proPath));
317 setValue(obj&: result, key: "excluded", value: excludes);
318 setValue(obj&: result, key: "sources", value: sourceFiles);
319 }
320 return result;
321}
322
323static QJsonArray processProjects(bool topLevel, const QStringList &proFiles,
324 const QHash<QString, QString> &outDirMap,
325 ProFileGlobals *option, QMakeVfs *vfs, QMakeParser *parser, bool *fail)
326{
327 QJsonArray result;
328 foreach (const QString &proFile, proFiles) {
329 if (!outDirMap.isEmpty())
330 option->setDirectories(input_dir: QFileInfo(proFile).path(), output_dir: outDirMap[proFile]);
331
332 ProFile *pro;
333 if (!(pro = parser->parsedProFile(fileName: proFile, flags: topLevel ? QMakeParser::ParseReportMissing
334 : QMakeParser::ParseDefault))) {
335 if (topLevel)
336 *fail = true;
337 continue;
338 }
339 ProFileEvaluator visitor(option, parser, vfs, &evalHandler);
340 visitor.setCumulative(true);
341 visitor.setOutputDir(option->shadowedPath(fileName: pro->directoryName()));
342 if (!visitor.accept(pro)) {
343 if (topLevel)
344 *fail = true;
345 pro->deref();
346 continue;
347 }
348
349 QJsonObject prj = processProject(proFile, option, vfs, parser, visitor);
350 setValue(obj&: prj, key: "projectFile", value: proFile);
351 if (visitor.contains(variableName: QLatin1String("TRANSLATIONS"))) {
352 QStringList tsFiles;
353 QDir proDir(QFileInfo(proFile).path());
354 const QStringList translations = visitor.values(variableName: QLatin1String("TRANSLATIONS"));
355 for (const QString &tsFile : translations)
356 tsFiles << proDir.filePath(fileName: tsFile);
357 setValue(obj&: prj, key: "translations", value: tsFiles);
358 }
359 result.append(value: prj);
360 pro->deref();
361 }
362 return result;
363}
364
365int main(int argc, char **argv)
366{
367 QCoreApplication app(argc, argv);
368 QStringList args = app.arguments();
369 QStringList proFiles;
370 QString outDir = QDir::currentPath();
371 QHash<QString, QString> outDirMap;
372 QString outputFilePath;
373 int proDebug = 0;
374
375 for (int i = 1; i < args.size(); ++i) {
376 QString arg = args.at(i);
377 if (arg == QLatin1String("-help")
378 || arg == QLatin1String("--help")
379 || arg == QLatin1String("-h")) {
380 printUsage();
381 return 0;
382 } else if (arg == QLatin1String("-out")) {
383 ++i;
384 if (i == argc) {
385 printErr(out: LD::tr(sourceText: "The option -out requires a parameter.\n"));
386 return 1;
387 }
388 outputFilePath = args[i];
389 } else if (arg == QLatin1String("-silent")) {
390 evalHandler.verbose = false;
391 } else if (arg == QLatin1String("-pro-debug")) {
392 proDebug++;
393 } else if (arg == QLatin1String("-version")) {
394 printOut(out: LD::tr(sourceText: "lprodump version %1\n").arg(a: QLatin1String(QT_VERSION_STR)));
395 return 0;
396 } else if (arg == QLatin1String("-pro")) {
397 ++i;
398 if (i == argc) {
399 printErr(out: LD::tr(sourceText: "The -pro option should be followed by a filename of .pro file.\n"));
400 return 1;
401 }
402 QString file = QDir::cleanPath(path: QFileInfo(args[i]).absoluteFilePath());
403 proFiles += file;
404 outDirMap[file] = outDir;
405 } else if (arg == QLatin1String("-pro-out")) {
406 ++i;
407 if (i == argc) {
408 printErr(out: LD::tr(sourceText: "The -pro-out option should be followed by a directory name.\n"));
409 return 1;
410 }
411 outDir = QDir::cleanPath(path: QFileInfo(args[i]).absoluteFilePath());
412 } else if (arg.startsWith(s: QLatin1String("-")) && arg != QLatin1String("-")) {
413 printErr(out: LD::tr(sourceText: "Unrecognized option '%1'.\n").arg(a: arg));
414 return 1;
415 } else {
416 QFileInfo fi(arg);
417 if (!fi.exists()) {
418 printErr(out: LD::tr(sourceText: "lprodump error: File '%1' does not exist.\n").arg(a: arg));
419 return 1;
420 }
421 if (!isProOrPriFile(filePath: arg)) {
422 printErr(out: LD::tr(sourceText: "lprodump error: '%1' is neither a .pro nor a .pri file.\n")
423 .arg(a: arg));
424 return 1;
425 }
426 QString cleanFile = QDir::cleanPath(path: fi.absoluteFilePath());
427 proFiles << cleanFile;
428 outDirMap[cleanFile] = outDir;
429 }
430 } // for args
431
432 if (proFiles.isEmpty()) {
433 printUsage();
434 return 1;
435 }
436
437 bool fail = false;
438 ProFileGlobals option;
439 option.qmake_abslocation = QString::fromLocal8Bit(str: qgetenv(varName: "QMAKE"));
440 if (option.qmake_abslocation.isEmpty())
441 option.qmake_abslocation = app.applicationDirPath() + QLatin1String("/qmake");
442 option.debugLevel = proDebug;
443 option.initProperties();
444 option.setCommandLineArguments(pwd: QDir::currentPath(),
445 args: QStringList() << QLatin1String("CONFIG+=lupdate_run"));
446 QMakeVfs vfs;
447 QMakeParser parser(0, &vfs, &evalHandler);
448
449 QJsonArray results = processProjects(topLevel: true, proFiles, outDirMap, option: &option, vfs: &vfs,
450 parser: &parser, fail: &fail);
451 if (fail)
452 return 1;
453
454 const QByteArray output = QJsonDocument(results).toJson(format: QJsonDocument::Compact);
455 if (outputFilePath.isEmpty()) {
456 puts(s: output.constData());
457 } else {
458 QFile f(outputFilePath);
459 if (!f.open(flags: QIODevice::WriteOnly)) {
460 printErr(out: LD::tr(sourceText: "lprodump error: Cannot open %1 for writing.\n").arg(a: outputFilePath));
461 return 1;
462 }
463 f.write(data: output);
464 f.write(data: "\n");
465 }
466 return 0;
467}
468

source code of qttools/src/linguist/lprodump/main.cpp