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
printOut(const QString & out)51 static void printOut(const QString &out)
52 {
53 std::cout << qPrintable(out);
54 }
55
printErr(const QString & out)56 static void printErr(const QString &out)
57 {
58 std::cerr << qPrintable(out);
59 }
60
toJsonValue(const QJsonValue & v)61 static QJsonValue toJsonValue(const QJsonValue &v)
62 {
63 return v;
64 }
65
toJsonValue(const QString & s)66 static QJsonValue toJsonValue(const QString &s)
67 {
68 return QJsonValue(s);
69 }
70
toJsonValue(const QStringList & lst)71 static QJsonValue toJsonValue(const QStringList &lst)
72 {
73 return QJsonArray::fromStringList(lst);
74 }
75
76 template <class T>
setValue(QJsonObject & obj,const char * key,T value)77 void setValue(QJsonObject &obj, const char *key, T value)
78 {
79 obj[QLatin1String(key)] = toJsonValue(value);
80 }
81
82 class LD {
83 Q_DECLARE_TR_FUNCTIONS(LProDump)
84 };
85
printUsage()86 static void printUsage()
87 {
88 printOut(LD::tr(
89 "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
print(const QString & fileName,int lineNo,const QString & msg)112 static void print(const QString &fileName, int lineNo, const QString &msg)
113 {
114 if (lineNo > 0)
115 printErr(QString::fromLatin1("WARNING: %1:%2: %3\n").arg(fileName, QString::number(lineNo), msg));
116 else if (lineNo)
117 printErr(QString::fromLatin1("WARNING: %1: %2\n").arg(fileName, msg));
118 else
119 printErr(QString::fromLatin1("WARNING: %1\n").arg(msg));
120 }
121
122 class EvalHandler : public QMakeHandler {
123 public:
message(int type,const QString & msg,const QString & fileName,int lineNo)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
fileMessage(int type,const QString & msg)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(QLatin1String("WARNING: ") + msg + QLatin1Char('\n'));
135 }
136 }
137
aboutToEval(ProFile *,ProFile *,EvalFileType)138 virtual void aboutToEval(ProFile *, ProFile *, EvalFileType) {}
doneWithEval(ProFile *)139 virtual void doneWithEval(ProFile *) {}
140
141 bool verbose = true;
142 };
143
144 static EvalHandler evalHandler;
145
isSupportedExtension(const QString & ext)146 static 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
getResources(const QString & resourceFile,QMakeVfs * vfs)153 static QStringList getResources(const QString &resourceFile, QMakeVfs *vfs)
154 {
155 Q_ASSERT(vfs);
156 if (!vfs->exists(resourceFile, QMakeVfs::VfsCumulative))
157 return QStringList();
158 QString content;
159 QString errStr;
160 if (vfs->readFile(vfs->idForFileName(resourceFile, QMakeVfs::VfsCumulative),
161 &content, &errStr) != QMakeVfs::ReadOk) {
162 printErr(LD::tr("lprodump error: Cannot read %1: %2\n").arg(resourceFile, errStr));
163 return QStringList();
164 }
165 const ReadQrcResult rqr = readQrcFile(resourceFile, content);
166 if (rqr.hasError()) {
167 printErr(LD::tr("lprodump error: %1:%2: %3\n")
168 .arg(resourceFile, QString::number(rqr.line), rqr.errorString));
169 }
170 return rqr.files;
171 }
172
getSources(const char * var,const char * vvar,const QStringList & baseVPaths,const QString & projectDir,const ProFileEvaluator & visitor)173 static QStringList getSources(const char *var, const char *vvar, const QStringList &baseVPaths,
174 const QString &projectDir, const ProFileEvaluator &visitor)
175 {
176 QStringList vPaths = visitor.absolutePathValues(QLatin1String(vvar), projectDir);
177 vPaths += baseVPaths;
178 vPaths.removeDuplicates();
179 return visitor.absoluteFileValues(QLatin1String(var), projectDir, vPaths, 0);
180 }
181
getSources(const ProFileEvaluator & visitor,const QString & projectDir,const QStringList & excludes,QMakeVfs * vfs)182 static QStringList getSources(const ProFileEvaluator &visitor, const QString &projectDir,
183 const QStringList &excludes, QMakeVfs *vfs)
184 {
185 QStringList baseVPaths;
186 baseVPaths += visitor.absolutePathValues(QLatin1String("VPATH"), projectDir);
187 baseVPaths << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
188 baseVPaths.removeDuplicates();
189
190 QStringList sourceFiles;
191
192 // app/lib template
193 sourceFiles += getSources("SOURCES", "VPATH_SOURCES", baseVPaths, projectDir, visitor);
194 sourceFiles += getSources("HEADERS", "VPATH_HEADERS", baseVPaths, projectDir, visitor);
195
196 sourceFiles += getSources("FORMS", "VPATH_FORMS", baseVPaths, projectDir, visitor);
197
198 QStringList resourceFiles = getSources("RESOURCES", "VPATH_RESOURCES", baseVPaths, projectDir, visitor);
199 foreach (const QString &resource, resourceFiles)
200 sourceFiles += getResources(resource, vfs);
201
202 QStringList installs = visitor.values(QLatin1String("INSTALLS"))
203 + visitor.values(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(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(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(*it))
241 it = sourceFiles.erase(it);
242 else
243 ++it;
244 }
245 }
246
247 return sourceFiles;
248 }
249
getExcludes(const ProFileEvaluator & visitor,const QString & projectDirPath)250 QStringList getExcludes(const ProFileEvaluator &visitor, const QString &projectDirPath)
251 {
252 const QStringList trExcludes = visitor.values(QLatin1String("TR_EXCLUDE"));
253 QStringList excludes;
254 excludes.reserve(trExcludes.size());
255 const QDir projectDir(projectDirPath);
256 for (const QString &ex : trExcludes)
257 excludes << QDir::cleanPath(projectDir.absoluteFilePath(ex));
258 return excludes;
259 }
260
excludeProjects(const ProFileEvaluator & visitor,QStringList * subProjects)261 static 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(*it))
267 it = subProjects->erase(it);
268 else
269 ++it;
270 }
271 }
272 }
273
274 static QJsonArray processProjects(bool topLevel, const QStringList &proFiles,
275 const QHash<QString, QString> &outDirMap,
276 ProFileGlobals *option, QMakeVfs *vfs, QMakeParser *parser,
277 bool *fail);
278
processProject(const QString & proFile,ProFileGlobals * option,QMakeVfs * vfs,QMakeParser * parser,ProFileEvaluator & visitor)279 static QJsonObject processProject(const QString &proFile, ProFileGlobals *option, QMakeVfs *vfs,
280 QMakeParser *parser, ProFileEvaluator &visitor)
281 {
282 QJsonObject result;
283 QStringList tmp = visitor.values(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(QLatin1String("SUBDIRS"));
289 excludeProjects(visitor, &subProjects);
290 QStringList subProFiles;
291 QDir proDir(proPath);
292 foreach (const QString &subdir, subProjects) {
293 QString realdir = visitor.value(subdir + QLatin1String(".subdir"));
294 if (realdir.isEmpty())
295 realdir = visitor.value(subdir + QLatin1String(".file"));
296 if (realdir.isEmpty())
297 realdir = subdir;
298 QString subPro = QDir::cleanPath(proDir.absoluteFilePath(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(false, subProFiles,
308 QHash<QString, QString>(), option, vfs, parser,
309 nullptr);
310 if (!subResults.isEmpty())
311 setValue(result, "subProjects", subResults);
312 } else {
313 const QStringList excludes = getExcludes(visitor, proPath);
314 const QStringList sourceFiles = getSources(visitor, proPath, excludes, vfs);
315 setValue(result, "includePaths",
316 visitor.absolutePathValues(QLatin1String("INCLUDEPATH"), proPath));
317 setValue(result, "excluded", excludes);
318 setValue(result, "sources", sourceFiles);
319 }
320 return result;
321 }
322
processProjects(bool topLevel,const QStringList & proFiles,const QHash<QString,QString> & outDirMap,ProFileGlobals * option,QMakeVfs * vfs,QMakeParser * parser,bool * fail)323 static 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(QFileInfo(proFile).path(), outDirMap[proFile]);
331
332 ProFile *pro;
333 if (!(pro = parser->parsedProFile(proFile, 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(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(prj, "projectFile", proFile);
351 if (visitor.contains(QLatin1String("TRANSLATIONS"))) {
352 QStringList tsFiles;
353 QDir proDir(QFileInfo(proFile).path());
354 const QStringList translations = visitor.values(QLatin1String("TRANSLATIONS"));
355 for (const QString &tsFile : translations)
356 tsFiles << proDir.filePath(tsFile);
357 setValue(prj, "translations", tsFiles);
358 }
359 result.append(prj);
360 pro->deref();
361 }
362 return result;
363 }
364
main(int argc,char ** argv)365 int 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(LD::tr("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(LD::tr("lprodump version %1\n").arg(QLatin1String(QT_VERSION_STR)));
395 return 0;
396 } else if (arg == QLatin1String("-pro")) {
397 ++i;
398 if (i == argc) {
399 printErr(LD::tr("The -pro option should be followed by a filename of .pro file.\n"));
400 return 1;
401 }
402 QString file = QDir::cleanPath(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(LD::tr("The -pro-out option should be followed by a directory name.\n"));
409 return 1;
410 }
411 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
412 } else if (arg.startsWith(QLatin1String("-")) && arg != QLatin1String("-")) {
413 printErr(LD::tr("Unrecognized option '%1'.\n").arg(arg));
414 return 1;
415 } else {
416 QFileInfo fi(arg);
417 if (!fi.exists()) {
418 printErr(LD::tr("lprodump error: File '%1' does not exist.\n").arg(arg));
419 return 1;
420 }
421 if (!isProOrPriFile(arg)) {
422 printErr(LD::tr("lprodump error: '%1' is neither a .pro nor a .pri file.\n")
423 .arg(arg));
424 return 1;
425 }
426 QString cleanFile = QDir::cleanPath(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(qgetenv("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(QDir::currentPath(),
445 QStringList() << QLatin1String("CONFIG+=lupdate_run"));
446 QMakeVfs vfs;
447 QMakeParser parser(0, &vfs, &evalHandler);
448
449 QJsonArray results = processProjects(true, proFiles, outDirMap, &option, &vfs,
450 &parser, &fail);
451 if (fail)
452 return 1;
453
454 const QByteArray output = QJsonDocument(results).toJson(QJsonDocument::Compact);
455 if (outputFilePath.isEmpty()) {
456 puts(output.constData());
457 } else {
458 QFile f(outputFilePath);
459 if (!f.open(QIODevice::WriteOnly)) {
460 printErr(LD::tr("lprodump error: Cannot open %1 for writing.\n").arg(outputFilePath));
461 return 1;
462 }
463 f.write(output);
464 f.write("\n");
465 }
466 return 0;
467 }
468