Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
5#include <linguistproject/profileutils.h>
6#include <linguistproject/projectdescriptionreader.h>
7#include <linguistproject/projsongenerator.h>
8#include <linguistproject/projectprocessor.h>
9#include <trlib/trparser.h>
10#include <translator.h>
11
12#include <QtCore/QCoreApplication>
13#include <QtCore/QDir>
14#include <QtCore/QFile>
15#include <QtCore/QFileInfo>
16#include <QtCore/QLibraryInfo>
17#include <QtCore/QRegularExpression>
18#include <QtCore/QString>
19#include <QtCore/QStringList>
20#include <QtCore/QTranslator>
21
22#include <iostream>
23
24using namespace Qt::StringLiterals;
25
27
28static void printOut(const QString & out)
29{
30 std::cout << qPrintable(out);
31}
32
33static void printErr(const QString & out)
34{
35 std::cerr << qPrintable(out);
36}
37
38static void printWarning(UpdateOptions options,
39 const QString &msg,
40 const QString &warningMsg = {},
41 const QString &errorMsg = {})
42{
43 QString text = msg;
44 if (options & Werror) {
45 text.prepend("lupdate error: "_L1);
46 if (!errorMsg.isEmpty())
47 text.append(" "_L1).append(errorMsg);
48 } else {
49 text.prepend("lupdate warning: "_L1);
50 if (!warningMsg.isEmpty())
51 text.append(" "_L1).append(warningMsg);
52 }
53
54 printErr(text);
55}
56
57static void recursiveFileInfoList(const QDir &dir,
58 const QSet<QString> &nameFilters, QDir::Filters filter,
59 QFileInfoList *fileinfolist)
60{
61 for (const QFileInfo &fi : dir.entryInfoList(filter))
62 if (fi.isDir())
63 recursiveFileInfoList(QDir(fi.absoluteFilePath()), nameFilters, filter, fileinfolist);
64 else if (nameFilters.contains(fi.suffix()))
65 fileinfolist->append(fi);
66}
67
68static void printUsage()
69{
70 printOut(
71 "Usage:\n"
72 " lupdate [options] [project-file]...\n"
73 " lupdate [options] [source-file|path|@lst-file]... -ts ts-files|@lst-file\n\n"
74 "lupdate is part of Qt's Linguist tool chain. It extracts translatable\n"
75 "messages from Qt UI files, C++, Java and JavaScript/QtScript source code.\n"
76 "Extracted messages are stored in textual translation source files (typically\n"
77 "Qt TS XML). New and modified messages can be merged into existing TS files.\n\n"
78 "Passing .pro files to lupdate is deprecated.\n"
79 "Please use the lupdate-pro tool instead.\n\n"
80 "Options:\n"
81 " -help Display this information and exit.\n"
82 " -no-obsolete\n"
83 " Drop all obsolete and vanished strings.\n"
84 " -extensions <ext>[,<ext>]...\n"
85 " Process files with the given extensions only.\n"
86 " The extension list must be separated with commas, not with whitespace.\n"
87 " Default: '%1'.\n"
88 " -pluralonly\n"
89 " Only include plural form messages.\n"
90 " -silent\n"
91 " Do not explain what is being done.\n"
92 " -no-sort\n"
93 " Do not sort contexts in TS files.\n"
94 " -sort-messages\n"
95 " Sort messages in a context alphabetically in TS files.\n"
96 " -no-recursive\n"
97 " Do not recursively scan directories.\n"
98 " -recursive\n"
99 " Recursively scan directories (default).\n"
100 " -warnings-are-errors\n"
101 " Treat warnings as errors.\n"
102 " -I <includepath> or -I<includepath>\n"
103 " Additional location to look for include files.\n"
104 " May be specified multiple times.\n"
105 " -locations {absolute|relative|none}\n"
106 " Specify/override how source code references are saved in TS files.\n"
107 " absolute: Source file path is relative to target file. Absolute line\n"
108 " number is stored.\n"
109 " relative: Source file path is relative to target file. Line number is\n"
110 " relative to other entries in the same source file.\n"
111 " none: no information about source location is stored.\n"
112 " Guessed from existing TS files if not specified.\n"
113 " Default is absolute for new files.\n"
114 " -no-ui-lines\n"
115 " Do not record line numbers in references to UI files.\n"
116 " -disable-heuristic {sametext|similartext}\n"
117 " Disable the named merge heuristic. Can be specified multiple times.\n"
118 " -project <filename>\n"
119 " Name of a file containing the project's description in JSON format.\n"
120 " Such a file may be generated from a .pro file using lupdate-pro -dump-json.\n"
121 " -pro <filename>\n"
122 " Name of a .pro file. Useful for files with .pro file syntax but\n"
123 " different file suffix. Projects are recursed into and merged.\n"
124 " This option is deprecated. Use the lupdate-pro tool instead.\n"
125 " -pro-out <directory>\n"
126 " Virtual output directory for processing subsequent .pro files.\n"
127 " -pro-debug\n"
128 " Trace processing .pro files. Specify twice for more verbosity.\n"
129 " -source-language <language>[_<region>]\n"
130 " Specify the language of the source strings for new files.\n"
131 " Defaults to POSIX if not specified.\n"
132 " -target-language <language>[_<region>]\n"
133 " Specify the language of the translations for new files.\n"
134 " Guessed from the file name if not specified.\n"
135 " -tr-function-alias <function>{+=,=}<alias>[,<function>{+=,=}<alias>]...\n"
136 " With +=, recognize <alias> as an alternative spelling of <function>.\n"
137 " With =, recognize <alias> as the only spelling of <function>.\n"
138 " Available <function>s (with their currently defined aliases) are:\n"
139 " %2\n"
140 " -ts <ts-file>...\n"
141 " Specify the output file(s). This will override the TRANSLATIONS.\n"
142 " -version\n"
143 " Display the version of lupdate and exit.\n"
144 " @lst-file\n"
145 " Read additional file names (one per line) or includepaths (one per\n"
146 " line, and prefixed with -I) from lst-file.\n"_L1.arg(
147 m_defaultExtensions,
148 trFunctionAliasManager.availableFunctionsWithAliases().join(
149 "\n "_L1)));
150}
151
152int main(int argc, char **argv)
153{
154 QCoreApplication app(argc, argv);
155#ifndef QT_BOOTSTRAPPED
156#ifndef Q_OS_WIN32
157 QTranslator translator;
158 QTranslator qtTranslator;
159 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
160 if (translator.load("linguist_en"_L1, resourceDir)
161 && qtTranslator.load("qt_en"_L1, resourceDir)) {
162 app.installTranslator(&translator);
163 app.installTranslator(&qtTranslator);
164 }
165#endif // Q_OS_WIN32
166#endif
167
168 m_defaultExtensions = QLatin1String("java,jui,ui,c,c++,cc,cpp,cxx,ch,h,"_L1
169 "h++,hh,hpp,hxx,js,mjs,qs,qml,qrc"_L1);
170
171 QStringList args = app.arguments();
172 QStringList tsFileNames;
173 QStringList proFiles;
174 QString projectDescriptionFile;
175 QString outDir = QDir::currentPath();
176 QMultiHash<QString, QString> allCSources;
177 QSet<QString> projectRoots;
178 QStringList sourceFiles;
179 QStringList resourceFiles;
180 QStringList includePath;
181 QStringList alienFiles;
182 QString targetLanguage;
183 QString sourceLanguage;
184
185 UpdateOptions options =
186 Verbose | // verbose is on by default starting with Qt 4.2
188 int numFiles = 0;
189 bool metTsFlag = false;
190 bool metXTsFlag = false;
191 bool recursiveScan = true;
192
193 QString extensions = m_defaultExtensions;
194 QSet<QString> extensionsNameFilters;
195
196 for (int i = 1; i < args.size(); ++i) {
197 QString arg = args.at(i);
198 if (arg == "-help"_L1 || arg == "--help"_L1 || arg == "-h"_L1) {
200 return 0;
201 } else if (arg == "-list-languages"_L1) {
202 printOut(getNumerusInfoString());
203 return 0;
204 } else if (arg == "-pluralonly"_L1) {
205 options |= PluralOnly;
206 continue;
207 } else if (arg == "-noobsolete"_L1 || arg == "-no-obsolete"_L1) {
208 options |= NoObsolete;
209 continue;
210 } else if (arg == "-silent"_L1) {
211 options &= ~Verbose;
212 continue;
213 } else if (arg == "-pro-debug"_L1) {
214 continue;
215 } else if (arg == "-project"_L1) {
216 ++i;
217 if (i == argc) {
218 printErr(u"The option -project requires a parameter.\n"_s);
219 return 1;
220 }
221 if (!projectDescriptionFile.isEmpty()) {
222 printErr(u"The option -project must appear only once.\n"_s);
223 return 1;
224 }
225 projectDescriptionFile = args[i];
226 numFiles++;
227 continue;
228 } else if (arg == "-target-language"_L1) {
229 ++i;
230 if (i == argc) {
231 printErr(u"The option -target-language requires a parameter.\n"_s);
232 return 1;
233 }
234 targetLanguage = args[i];
235 continue;
236 } else if (arg == "-source-language"_L1) {
237 ++i;
238 if (i == argc) {
239 printErr(u"The option -source-language requires a parameter.\n"_s);
240 return 1;
241 }
242 sourceLanguage = args[i];
243 continue;
244 } else if (arg == "-disable-heuristic"_L1) {
245 ++i;
246 if (i == argc) {
247 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
248 return 1;
249 }
250 arg = args[i];
251 if (arg == "sametext"_L1) {
252 options &= ~HeuristicSameText;
253 } else if (arg == "similartext"_L1) {
254 options &= ~HeuristicSimilarText;
255 } else {
256 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
257 return 1;
258 }
259 continue;
260 } else if (arg == "-locations"_L1) {
261 ++i;
262 if (i == argc) {
263 printErr(u"The option -locations requires a parameter.\n"_s);
264 return 1;
265 }
266 if (args[i] == "none"_L1) {
267 options |= NoLocations;
268 } else if (args[i] == "relative"_L1) {
269 options |= RelativeLocations;
270 } else if (args[i] == "absolute"_L1) {
271 options |= AbsoluteLocations;
272 } else {
273 printErr(u"Invalid parameter passed to -locations.\n"_s);
274 return 1;
275 }
276 continue;
277 } else if (arg == "-no-ui-lines"_L1) {
278 options |= NoUiLines;
279 continue;
280 } else if (arg == "-verbose"_L1) {
281 options |= Verbose;
282 continue;
283 } else if (arg == "-warnings-are-errors"_L1) {
284 options |= Werror;
285 continue;
286 } else if (arg == "-no-recursive"_L1) {
287 recursiveScan = false;
288 continue;
289 } else if (arg == "-recursive"_L1) {
290 recursiveScan = true;
291 continue;
292 } else if (arg == "-no-sort"_L1 || arg == "-nosort"_L1) {
293 options |= NoSort;
294 continue;
295 } else if (arg == "-sort-messages"_L1) {
296 options |= SortMessages;
297 continue;
298 } else if (arg == "-version"_L1) {
299 printOut(QStringLiteral("lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
300 return 0;
301 } else if (arg == "-ts"_L1) {
302 metTsFlag = true;
303 metXTsFlag = false;
304 continue;
305 } else if (arg == "-xts"_L1) {
306 metTsFlag = false;
307 metXTsFlag = true;
308 continue;
309 } else if (arg == "-extensions"_L1) {
310 ++i;
311 if (i == argc) {
312 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
313 return 1;
314 }
315 extensions = args[i];
316 continue;
317 } else if (arg == "-tr-function-alias"_L1) {
318 ++i;
319 if (i == argc) {
320 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
321 return 1;
322 }
323 if (!parseTrFunctionAliasString(args[i]))
324 return 1;
325 continue;
326 } else if (arg == "-pro"_L1) {
327 printErr(u"lupdate warning: The -pro option is deprecated. "
328 u"Please use the lupdate-pro tool instead.\n"_s);
329 ++i;
330 if (i == argc) {
331 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
332 return 1;
333 }
334 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
335 proFiles += file;
336 numFiles++;
337 continue;
338 } else if (arg == "-pro-out"_L1) {
339 ++i;
340 if (i == argc) {
341 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
342 return 1;
343 }
344 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
345 continue;
346 } else if (arg.startsWith("-I"_L1)) {
347 if (arg.size() == 2) {
348 ++i;
349 if (i == argc) {
350 printErr(u"The -I option should be followed by a path.\n"_s);
351 return 1;
352 }
353 includePath += args[i];
354 } else {
355 includePath += args[i].mid(2);
356 }
357 continue;
358 }
359 else if (arg.startsWith("-"_L1) && arg != "-"_L1) {
360 printErr("Unrecognized option '%1'.\n"_L1.arg(arg));
361 return 1;
362 }
363
364 QStringList files;
365 if (arg.startsWith("@"_L1)) {
366 QFile lstFile(arg.mid(1));
367 if (!lstFile.open(QIODevice::ReadOnly)) {
368 printErr(QStringLiteral("lupdate error: List file '%1' is not readable.\n")
369 .arg(lstFile.fileName()));
370 return 1;
371 }
372 while (!lstFile.atEnd()) {
373 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
374
375 if (lineContent.startsWith("-I"_L1)) {
376 if (lineContent.size() == 2) {
377 printErr(u"The -I option should be followed by a path.\n"_s);
378 return 1;
379 }
380 includePath += lineContent.mid(2);
381 } else {
382 files << lineContent;
383 }
384 }
385 } else {
386 files << arg;
387 }
388 if (metTsFlag) {
389 for (const QString &file : std::as_const(files)) {
390 bool found = false;
391 for (const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
392 if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
393 QFileInfo fi(file);
394 if (!fi.exists() || fi.isWritable()) {
395 tsFileNames.append(QFileInfo(file).absoluteFilePath());
396 } else {
397 printWarning(options,
398 "For some reason, '%1' is not writable.\n"_L1
399 .arg(file));
400 if (options & Werror)
401 return 1;
402 }
403 found = true;
404 break;
405 }
406 }
407 if (!found) {
408 printErr(QStringLiteral("lupdate error: File '%1' has no recognized extension.\n")
409 .arg(file));
410 return 1;
411 }
412 }
413 numFiles++;
414 } else if (metXTsFlag) {
415 alienFiles += files;
416 } else {
417 for (const QString &file : std::as_const(files)) {
418 QFileInfo fi(file);
419 if (!fi.exists()) {
420 printErr(QStringLiteral("lupdate error: File '%1' does not exist.\n").arg(file));
421 return 1;
422 }
423 if (isProOrPriFile(file)) {
424 printErr(u"lupdate warning: Passing .pro/.pri files to lupdate is deprecated. "
425 u"Please use the lupdate-pro tool instead.\n"_s);
426 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
427 proFiles << cleanFile;
428 } else if (fi.isDir()) {
429 if (options & Verbose)
430 printOut(QStringLiteral("Scanning directory '%1'...\n").arg(file));
431 QDir dir = QDir(fi.filePath());
432 projectRoots.insert(dir.absolutePath() + u'/');
433 if (extensionsNameFilters.isEmpty()) {
434 for (QString ext : extensions.split(u',')) {
435 ext = ext.trimmed();
436 if (ext.startsWith(u'.'))
437 ext.remove(0, 1);
438 extensionsNameFilters.insert(ext);
439 }
440 }
441 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
442 if (recursiveScan)
443 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
444 QFileInfoList fileinfolist;
445 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
446 int scanRootLen = dir.absolutePath().size();
447 for (const QFileInfo &fi : std::as_const(fileinfolist)) {
448 QString fn = QDir::cleanPath(fi.absoluteFilePath());
449 if (fn.endsWith(".qrc"_L1, Qt::CaseInsensitive)) {
450 resourceFiles << fn;
451 } else {
452 sourceFiles << fn;
453
454 if (!fn.endsWith(".java"_L1) && !fn.endsWith(".jui"_L1)
455 && !fn.endsWith(".ui"_L1) && !fn.endsWith(".js"_L1)
456 && !fn.endsWith(".mjs"_L1) && !fn.endsWith(".qs"_L1)
457 && !fn.endsWith(".qml"_L1)) {
458 int offset = 0;
459 int depth = 0;
460 do {
461 offset = fn.lastIndexOf(u'/', offset - 1);
462 QString ffn = fn.mid(offset + 1);
463 allCSources.insert(ffn, fn);
464 } while (++depth < 3 && offset > scanRootLen);
465 }
466 }
467 }
468 } else {
469 QString fn = QDir::cleanPath(fi.absoluteFilePath());
470 if (fn.endsWith(".qrc"_L1, Qt::CaseInsensitive))
471 resourceFiles << fn;
472 else
473 sourceFiles << fn;
474 projectRoots.insert(fi.absolutePath() + u'/');
475 }
476 }
477 numFiles++;
478 }
479 } // for args
480
481 if (numFiles == 0) {
483 return 1;
484 }
485
486 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1) {
487 printWarning(options,
488 u"-target-language usually only makes sense with exactly one TS file.\n"_s);
489 if (options & Werror)
490 return 1;
491 }
492
493 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
494 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
495 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
496 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
497 u"'update_translations' target.\n"_s);
498 return 1;
499 }
500
501 QString errorString;
502 Projects projectDescription;
503
504 if (!proFiles.isEmpty()) {
505 QStringList translationsVariables = { u"TRANSLATIONS"_s };
506 QHash<QString, QString> outDirMap;
507 QString outDir = QDir::currentPath();
508 for (const QString &proFile : std::as_const(proFiles))
509 outDirMap[proFile] = outDir;
510
511 QString errorString;
512 QJsonArray results;
513 projectDescription = generateProjects(proFiles, translationsVariables, outDirMap,
514 0, false, &errorString, &results);
515 if (!errorString.isEmpty()) {
516 printErr("lupdate error: %1\n"_L1.arg(errorString));
517 return 1;
518 }
519 if (projectDescription.empty()) {
520 printErr("lupdate error: No projects found in .pro files\n"_L1);
521 return 1;
522 }
523 } else if (!projectDescriptionFile.isEmpty()) {
524 projectDescription = projectDescriptionFromFile(projectDescriptionFile, &errorString);
525 if (!errorString.isEmpty()) {
526 printErr(QStringLiteral("lupdate error: %1\n").arg(errorString));
527 return 1;
528 }
529 if (projectDescription.empty()) {
530 printErr(QStringLiteral("lupdate error:"
531 " Could not find project descriptions in %1.\n")
532 .arg(projectDescriptionFile));
533 return 1;
534 }
535 }
536
537 bool ok = true;
538 if (projectDescription.empty()) { // Direct source file processing mode
539 if (tsFileNames.isEmpty()) {
540 printWarning(options, u"no TS files specified."_s,
541 u"Only diagnostics will be produced.\n"_s,
542 u"Terminating the operation.\n"_s);
543 if (options & Werror)
544 return 1;
545 }
546
547 for (const QString &resource : std::as_const(resourceFiles))
548 sourceFiles << getSourceFilesFromQrc(resource);
549 ok &= processSourceFiles(sourceFiles, tsFileNames, alienFiles, projectRoots, includePath,
550 allCSources, sourceLanguage, targetLanguage, options);
551 } else { // Project-based processing mode
552 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
553 printErr(QStringLiteral("lupdate error:"
554 " Both project and source files / include paths specified.\n"));
555 return 1;
556 }
557
558 ok &= processProjectDescription(projectDescription, tsFileNames, alienFiles,
559 sourceLanguage, targetLanguage, options);
560 }
561 return ok ? 0 : 1;
562}
std::vector< Project > Projects
static void printOut(const QString &out)
Definition main.cpp:22
static void printErr(const QString &out)
Definition main.cpp:16
static void printUsage()
Definition main.cpp:24
static QString m_defaultExtensions
Definition main.cpp:26
static void printWarning(UpdateOptions options, const QString &msg, const QString &warningMsg={}, const QString &errorMsg={})
Definition main.cpp:38
static void recursiveFileInfoList(const QDir &dir, const QSet< QString > &nameFilters, QDir::Filters filter, QFileInfoList *fileinfolist)
Definition main.cpp:57
int main(int argc, char *argv[])
[ctor_close]
@ NoLocations
Definition trparser.h:23
@ SortMessages
Definition trparser.h:27
@ Verbose
Definition trparser.h:15
@ NoObsolete
Definition trparser.h:16
@ PluralOnly
Definition trparser.h:17
@ Werror
Definition trparser.h:26
@ NoSort
Definition trparser.h:18
@ HeuristicSimilarText
Definition trparser.h:20
@ HeuristicSameText
Definition trparser.h:19
@ AbsoluteLocations
Definition trparser.h:21
@ RelativeLocations
Definition trparser.h:22
@ NoUiLines
Definition trparser.h:24