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 "lupdate.h"
6
7#include <profileutils.h>
8#include <projectdescriptionreader.h>
9#include <qrcreader.h>
10#include <runqttool.h>
11#include <translator.h>
12
13#include <QtCore/QCoreApplication>
14#include <QtCore/QDir>
15#include <QtCore/QFile>
16#include <QtCore/QFileInfo>
17#include <QtCore/QLibraryInfo>
18#include <QtCore/QRegularExpression>
19#include <QtCore/QString>
20#include <QtCore/QStringList>
21#include <QtCore/QTranslator>
22
23#include <iostream>
24
25using namespace Qt::StringLiterals;
26
27QString commandLineCompilationDatabaseDir; // for the path to the json file passed as a command line argument.
28 // Has priority over what is in the .pro file and passed to the project.
30
31// Can't have an array of QStaticStringData<N> for different N, so
32// use QString, which requires constructor calls. Doesn't matter
33// much, since this is in an app, not a lib:
35// MSVC can't handle the lambda in this array if QStringLiteral expands
36// to a lambda. In that case, use a QString instead.
37#if defined(Q_CC_MSVC) && defined(Q_COMPILER_LAMBDA)
38#define STRINGLITERAL(F) QLatin1String(#F),
39#else
40#define STRINGLITERAL(F) QStringLiteral(#F),
41#endif
42 LUPDATE_FOR_EACH_TR_FUNCTION(STRINGLITERAL)
43#undef STRINGLITERAL
44};
46
47static int trFunctionByDefaultName(const QString &trFunctionName)
48{
49 for (int i = 0; i < TrFunctionAliasManager::NumTrFunctions; ++i)
50 if (trFunctionName == defaultTrFunctionNames[i])
51 return i;
52 return -1;
53}
54
57{
58 for (int i = 0; i < NumTrFunctions; ++i)
59 m_trFunctionAliases[i].push_back(defaultTrFunctionNames[i]);
60}
61
63
64int TrFunctionAliasManager::trFunctionByName(const QString &trFunctionName) const
65{
66 ensureTrFunctionHashUpdated();
67 // this function needs to be fast
68 const auto it = m_nameToTrFunctionMap.constFind(trFunctionName);
69 return it == m_nameToTrFunctionMap.cend() ? -1 : *it;
70}
71
72void TrFunctionAliasManager::modifyAlias(int trFunction, const QString &alias, Operation op)
73{
74 QList<QString> &list = m_trFunctionAliases[trFunction];
75 if (op == SetAlias)
76 list.clear();
77 list.push_back(alias);
78 m_nameToTrFunctionMap.clear();
79}
80
81void TrFunctionAliasManager::ensureTrFunctionHashUpdated() const
82{
83 if (!m_nameToTrFunctionMap.empty())
84 return;
85
86 NameToTrFunctionMap nameToTrFunctionMap;
87 for (int i = 0; i < NumTrFunctions; ++i)
88 for (const QString &alias : m_trFunctionAliases[i])
89 nameToTrFunctionMap[alias] = TrFunction(i);
90 // commit:
91 m_nameToTrFunctionMap.swap(nameToTrFunctionMap);
92}
93
95{
96 ensureTrFunctionHashUpdated();
97 return m_nameToTrFunctionMap;
98}
99
101{
102 QStringList result;
104 for (int i = 0; i < TrFunctionAliasManager::NumTrFunctions; ++i)
105 result.push_back(defaultTrFunctionNames[i]);
106 return result;
107}
108
110{
111 QStringList result;
112 result.reserve(NumTrFunctions);
113 for (int i = 0; i < NumTrFunctions; ++i)
114 result.push_back(defaultTrFunctionNames[i] + " (="_L1 + m_trFunctionAliases[i].join(u'=')
115 + u')');
116 return result;
117}
118
120{
121 QStringList result;
122 result.reserve(NumTrFunctions);
123 for (int i = 0; i < NumTrFunctions; ++i) {
124 for (int ii = 1; ii < m_trFunctionAliases[i].size() ; ii++) {
125 // ii = 0 is the default name. Not listed here
126 result.push_back(m_trFunctionAliases[i][ii]);
127 }
128 }
129 return result;
130}
131
133
134QString ParserTool::transcode(const QString &str)
135{
136 static const char tab[] = "abfnrtv";
137 static const char backTab[] = "\a\b\f\n\r\t\v";
138 // This function has to convert back to bytes, as C's \0* sequences work at that level.
139 const QByteArray in = str.toUtf8();
140 QByteArray out;
141
142 out.reserve(in.size());
143 for (qsizetype i = 0; i < in.size();) {
144 uchar c = in[i++];
145 if (c == '\\') {
146 if (i >= in.size())
147 break;
148 c = in[i++];
149
150 if (c == '\n')
151 continue;
152
153 if (c == 'x' || c == 'u' || c == 'U') {
154 qsizetype maxSize = 2; // \x
155 if (c == 'u')
156 maxSize = 4;
157 else if (c == 'U')
158 maxSize = 8;
159 maxSize = std::min(in.size(), i + maxSize);
160
161 const bool unicode = (c != 'x');
162 QByteArray hex;
163 while (i < maxSize && isxdigit((c = in[i]))) {
164 hex += c;
165 i++;
166 }
167 if (unicode)
168 out += QString(QChar(hex.toUInt(nullptr, 16))).toUtf8();
169 else
170 out += hex.toUInt(nullptr, 16);
171 } else if (c >= '0' && c < '8') {
172 QByteArray oct;
173 int n = 0;
174 oct += c;
175 while (n < 2 && i < in.size() && (c = in[i]) >= '0' && c < '8') {
176 i++;
177 n++;
178 oct += c;
179 }
180 out += oct.toUInt(0, 8);
181 } else {
182 const char *p = strchr(tab, c);
183 out += !p ? c : backTab[p - tab];
184 }
185 } else {
186 out += c;
187 }
188 }
189 return QString::fromUtf8(out.constData(), out.size());
190}
191
193
194static void printOut(const QString & out)
195{
196 std::cout << qPrintable(out);
197}
198
199static void printErr(const QString & out)
200{
201 std::cerr << qPrintable(out);
202}
203
204static void printWarning(UpdateOptions options,
205 const QString &msg,
206 const QString &warningMsg = {},
207 const QString &errorMsg = {})
208{
209 QString text = msg;
210 if (options & Werror) {
211 text.prepend("lupdate error: "_L1);
212 if (!errorMsg.isEmpty())
213 text.append(" "_L1).append(errorMsg);
214 } else {
215 text.prepend("lupdate warning: "_L1);
216 if (!warningMsg.isEmpty())
217 text.append(" "_L1).append(warningMsg);
218 }
219
220 printErr(text);
221}
222
223static void recursiveFileInfoList(const QDir &dir,
224 const QSet<QString> &nameFilters, QDir::Filters filter,
225 QFileInfoList *fileinfolist)
226{
227 for (const QFileInfo &fi : dir.entryInfoList(filter))
228 if (fi.isDir())
229 recursiveFileInfoList(QDir(fi.absoluteFilePath()), nameFilters, filter, fileinfolist);
230 else if (nameFilters.contains(fi.suffix()))
231 fileinfolist->append(fi);
232}
233
234static void printUsage()
235{
236 printOut(
237 "Usage:\n"
238 " lupdate [options] [project-file]...\n"
239 " lupdate [options] [source-file|path|@lst-file]... -ts ts-files|@lst-file\n\n"
240 "lupdate is part of Qt's Linguist tool chain. It extracts translatable\n"
241 "messages from Qt UI files, C++, Java and JavaScript/QtScript source code.\n"
242 "Extracted messages are stored in textual translation source files (typically\n"
243 "Qt TS XML). New and modified messages can be merged into existing TS files.\n\n"
244 "Passing .pro files to lupdate is deprecated.\n"
245 "Please use the lupdate-pro tool instead.\n\n"
246 "Options:\n"
247 " -help Display this information and exit.\n"
248 " -no-obsolete\n"
249 " Drop all obsolete and vanished strings.\n"
250 " -extensions <ext>[,<ext>]...\n"
251 " Process files with the given extensions only.\n"
252 " The extension list must be separated with commas, not with whitespace.\n"
253 " Default: '%1'.\n"
254 " -pluralonly\n"
255 " Only include plural form messages.\n"
256 " -silent\n"
257 " Do not explain what is being done.\n"
258 " -no-sort\n"
259 " Do not sort contexts in TS files.\n"
260 " -sort-messages\n"
261 " Sort messages in a context alphabetically in TS files.\n"
262 " -no-recursive\n"
263 " Do not recursively scan directories.\n"
264 " -recursive\n"
265 " Recursively scan directories (default).\n"
266 " -warnings-are-errors\n"
267 " Treat warnings as errors.\n"
268 " -I <includepath> or -I<includepath>\n"
269 " Additional location to look for include files.\n"
270 " May be specified multiple times.\n"
271 " -locations {absolute|relative|none}\n"
272 " Specify/override how source code references are saved in TS files.\n"
273 " absolute: Source file path is relative to target file. Absolute line\n"
274 " number is stored.\n"
275 " relative: Source file path is relative to target file. Line number is\n"
276 " relative to other entries in the same source file.\n"
277 " none: no information about source location is stored.\n"
278 " Guessed from existing TS files if not specified.\n"
279 " Default is absolute for new files.\n"
280 " -no-ui-lines\n"
281 " Do not record line numbers in references to UI files.\n"
282 " -disable-heuristic {sametext|similartext}\n"
283 " Disable the named merge heuristic. Can be specified multiple times.\n"
284 " -project <filename>\n"
285 " Name of a file containing the project's description in JSON format.\n"
286 " Such a file may be generated from a .pro file using the lprodump tool.\n"
287 " -pro <filename>\n"
288 " Name of a .pro file. Useful for files with .pro file syntax but\n"
289 " different file suffix. Projects are recursed into and merged.\n"
290 " This option is deprecated. Use the lupdate-pro tool instead.\n"
291 " -pro-out <directory>\n"
292 " Virtual output directory for processing subsequent .pro files.\n"
293 " -pro-debug\n"
294 " Trace processing .pro files. Specify twice for more verbosity.\n"
295 " -source-language <language>[_<region>]\n"
296 " Specify the language of the source strings for new files.\n"
297 " Defaults to POSIX if not specified.\n"
298 " -target-language <language>[_<region>]\n"
299 " Specify the language of the translations for new files.\n"
300 " Guessed from the file name if not specified.\n"
301 " -tr-function-alias <function>{+=,=}<alias>[,<function>{+=,=}<alias>]...\n"
302 " With +=, recognize <alias> as an alternative spelling of <function>.\n"
303 " With =, recognize <alias> as the only spelling of <function>.\n"
304 " Available <function>s (with their currently defined aliases) are:\n"
305 " %2\n"
306 " -ts <ts-file>...\n"
307 " Specify the output file(s). This will override the TRANSLATIONS.\n"
308 " -version\n"
309 " Display the version of lupdate and exit.\n"
310 " @lst-file\n"
311 " Read additional file names (one per line) or includepaths (one per\n"
312 " line, and prefixed with -I) from lst-file.\n"_L1.arg(
313 m_defaultExtensions,
314 trFunctionAliasManager.availableFunctionsWithAliases().join(
315 "\n "_L1)));
316}
317
318static bool handleTrFunctionAliases(const QString &arg)
319{
320 for (const QString &pair : arg.split(u',', Qt::SkipEmptyParts)) {
321 const int equalSign = pair.indexOf(u'=');
322 if (equalSign < 0) {
323 printErr(QStringLiteral("tr-function mapping '%1' in -tr-function-alias is missing the '='.\n").arg(pair));
324 return false;
325 }
326 const bool plusEqual = equalSign > 0 && pair[equalSign - 1] == u'+';
327 const int trFunctionEnd = plusEqual ? equalSign-1 : equalSign;
328 const QString trFunctionName = pair.left(trFunctionEnd).trimmed();
329 const QString alias = pair.mid(equalSign+1).trimmed();
330 const int trFunction = trFunctionByDefaultName(trFunctionName);
331 if (trFunction < 0) {
332 printErr(QStringLiteral("Unknown tr-function '%1' in -tr-function-alias option.\n"
333 "Available tr-functions are: %2")
334 .arg(trFunctionName, availableFunctions().join(u',')));
335 return false;
336 }
337 if (alias.isEmpty()) {
338 printErr(QStringLiteral("Empty alias for tr-function '%1' in -tr-function-alias option.\n")
339 .arg(trFunctionName));
340 return false;
341 }
342 trFunctionAliasManager.modifyAlias(trFunction, alias,
343 plusEqual ? TrFunctionAliasManager::AddAlias : TrFunctionAliasManager::SetAlias);
344 }
345 return true;
346}
347
348static void updateTsFiles(const Translator &fetchedTor, const QStringList &tsFileNames,
349 const QStringList &alienFiles,
350 const QString &sourceLanguage, const QString &targetLanguage,
351 UpdateOptions options, bool *fail)
352{
353 for (int i = 0; i < fetchedTor.messageCount(); i++) {
354 const TranslatorMessage &msg = fetchedTor.constMessage(i);
355 if (!msg.id().isEmpty() && msg.sourceText().isEmpty()) {
356 printWarning(options,
357 "Message with id '%1' has no source.\n"_L1.arg(msg.id()));
358 if (options & Werror)
359 return;
360 }
361 }
362 QList<Translator> aliens;
363 for (const QString &fileName : alienFiles) {
364 ConversionData cd;
365 Translator tor;
366 if (!tor.load(fileName, cd, "auto"_L1)) {
367 printErr(cd.error());
368 *fail = true;
369 continue;
370 }
371 tor.resolveDuplicates();
372 aliens << tor;
373 }
374 QDir dir;
375 QString err;
376 for (const QString &fileName : tsFileNames) {
377 QString fn = dir.relativeFilePath(fileName);
378 ConversionData cd;
379 Translator tor;
380 cd.m_sortContexts = !(options & NoSort);
381 cd.m_sortMessages = options & SortMessages;
382 if (QFile(fileName).exists()) {
383 if (!tor.load(fileName, cd, "auto"_L1)) {
384 printErr(cd.error());
385 *fail = true;
386 continue;
387 }
388 tor.resolveDuplicates();
389 cd.clearErrors();
390 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode()) {
391 printWarning(options,
392 "Specified target language '%1' disagrees with"
393 " existing file's language '%2'.\n"_L1
394 .arg(targetLanguage, tor.languageCode()),
395 u"Ignoring.\n"_s);
396 if (options & Werror)
397 return;
398 }
399 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode()) {
400 printWarning(options,
401 "Specified source language '%1' disagrees with"
402 " existing file's language '%2'.\n"_L1
403 .arg(sourceLanguage, tor.sourceLanguageCode()),
404 u"Ignoring.\n"_s);
405 if (options & Werror)
406 return;
407 }
408 // If there is translation in the file, the language should be recognized
409 // (when the language is not recognized, plural translations are lost)
410 if (tor.translationsExist()) {
411
412 if (tor.languageCode().isEmpty()) {
413 printErr("File %1 won't be updated: it does not specify any "
414 "target languages. To set a target language, open "
415 "the file in Qt Linguist.\n"_L1.arg(fileName));
416 continue;
417 }
418 QLocale::Language l;
419 QLocale::Territory c;
420 tor.languageAndTerritory(tor.languageCode(), &l, &c);
421 QStringList forms;
422 if (!getNumerusInfo(l, c, 0, &forms, 0)) {
423 printErr(QStringLiteral("File %1 won't be updated: it contains translation but the"
424 " target language is not recognized\n").arg(fileName));
425 continue;
426 }
427 }
428 } else {
429 if (!targetLanguage.isEmpty())
430 tor.setLanguageCode(targetLanguage);
431 else
432 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
433 if (!sourceLanguage.isEmpty())
434 tor.setSourceLanguageCode(sourceLanguage);
435 }
436 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
437 if (options & NoLocations)
438 tor.setLocationsType(Translator::NoLocations);
439 else if (options & RelativeLocations)
440 tor.setLocationsType(Translator::RelativeLocations);
441 else if (options & AbsoluteLocations)
442 tor.setLocationsType(Translator::AbsoluteLocations);
443 if (options & Verbose)
444 printOut(QStringLiteral("Updating '%1'...\n").arg(fn));
445
446 UpdateOptions theseOptions = options;
447 if (tor.locationsType() == Translator::NoLocations) // Could be set from file
448 theseOptions |= NoLocations;
449 Translator out = merge(tor, fetchedTor, aliens, theseOptions, err);
450
451 if ((options & Verbose) && !err.isEmpty()) {
452 printOut(err);
453 err.clear();
454 }
455 if (options & PluralOnly) {
456 if (options & Verbose)
457 printOut(QStringLiteral("Stripping non plural forms in '%1'...\n").arg(fn));
458 out.stripNonPluralForms();
459 }
460 if (options & NoObsolete)
461 out.stripObsoleteMessages();
462 out.stripEmptyContexts();
463
464 out.normalizeTranslations(cd);
465 if (!cd.errors().isEmpty()) {
466 printErr(cd.error());
467 cd.clearErrors();
468 }
469 if (!out.save(fileName, cd, "auto"_L1)) {
470 printErr(cd.error());
471 *fail = true;
472 }
473 }
474}
475
476static bool readFileContent(const QString &filePath, QByteArray *content, QString *errorString)
477{
478 QFile file(filePath);
479 if (!file.open(QIODevice::ReadOnly)) {
480 *errorString = file.errorString();
481 return false;
482 }
483 *content = file.readAll();
484 return true;
485}
486
487static bool readFileContent(const QString &filePath, QString *content, QString *errorString)
488{
489 QByteArray ba;
490 if (!readFileContent(filePath, &ba, errorString))
491 return false;
492 *content = QString::fromLocal8Bit(ba);
493 return true;
494}
495
496static void removeExcludedSources(Projects &projects)
497{
498 for (Project &project : projects) {
499 for (const QRegularExpression &rx : project.excluded) {
500 for (auto it = project.sources.begin(); it != project.sources.end(); ) {
501 if (rx.match(*it).hasMatch())
502 it = project.sources.erase(it);
503 else
504 ++it;
505 }
506 }
507 removeExcludedSources(project.subProjects);
508 }
509}
510
511static QStringList getResources(const QString &resourceFile)
512{
513 if (!QFile::exists(resourceFile))
514 return QStringList();
515 QString content;
516 QString errStr;
517 if (!readFileContent(resourceFile, &content, &errStr)) {
518 printErr(QStringLiteral("lupdate error: Can not read %1: %2\n").arg(resourceFile, errStr));
519 return QStringList();
520 }
521 ReadQrcResult rqr = readQrcFile(resourceFile, content);
522 if (rqr.hasError()) {
523 printErr(QStringLiteral("lupdate error: %1:%2: %3\n")
524 .arg(resourceFile, QString::number(rqr.line), rqr.errorString));
525 }
526 return rqr.files;
527}
528
529// Remove .qrc files from the project and return them as absolute paths.
531{
532 auto it = project.sources.begin();
533 QStringList qrcFiles;
534 while (it != project.sources.end()) {
535 QFileInfo fi(*it);
536 QString fn = QDir::cleanPath(fi.absoluteFilePath());
537 if (fn.endsWith(".qrc"_L1, Qt::CaseInsensitive)) {
538 qrcFiles += fn;
539 it = project.sources.erase(it);
540 } else {
541 ++it;
542 }
543 }
544 return qrcFiles;
545}
546
547// Replace all .qrc files in the project with their content.
548static void expandQrcFiles(Project &project)
549{
550 for (const QString &qrcFile : extractQrcFiles(project))
551 project.sources << getResources(qrcFile);
552}
553
554static bool processTs(Translator &fetchedTor, const QString &file, ConversionData &cd)
555{
556 for (const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
557 if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
558 Translator tor;
559 if (tor.load(file, cd, fmt.extension)) {
560 for (TranslatorMessage msg : tor.messages()) {
561 msg.setType(TranslatorMessage::Unfinished);
562 msg.setTranslations(QStringList());
563 msg.setTranslatorComment(QString());
564 fetchedTor.extend(msg, cd);
565 }
566 }
567 return true;
568 }
569 }
570 return false;
571}
572
573static void processSources(Translator &fetchedTor, const QStringList &sourceFiles,
574 ConversionData &cd, UpdateOptions options)
575{
576#ifdef QT_NO_QML
577 bool requireQmlSupport = false;
578#endif
579 QStringList sourceFilesCpp;
580 for (const auto &sourceFile : sourceFiles) {
581 if (sourceFile.endsWith(".java"_L1, Qt::CaseInsensitive))
582 loadJava(fetchedTor, sourceFile, cd);
583 else if (sourceFile.endsWith(".ui"_L1, Qt::CaseInsensitive)
584 || sourceFile.endsWith(".jui"_L1, Qt::CaseInsensitive))
585 loadUI(fetchedTor, sourceFile, cd);
586#ifndef QT_NO_QML
587 else if (sourceFile.endsWith(".js"_L1, Qt::CaseInsensitive)
588 || sourceFile.endsWith(".qs"_L1, Qt::CaseInsensitive)) {
589 loadQScript(fetchedTor, sourceFile, cd);
590 } else if (sourceFile.endsWith(".mjs"_L1, Qt::CaseInsensitive)) {
591 loadJSModule(fetchedTor, sourceFile, cd);
592 } else if (sourceFile.endsWith(".qml"_L1, Qt::CaseInsensitive))
593 loadQml(fetchedTor, sourceFile, cd);
594#else
595 else if (sourceFile.endsWith(".qml"_L1, Qt::CaseInsensitive)
596 || sourceFile.endsWith(".js"_L1, Qt::CaseInsensitive)
597 || sourceFile.endsWith(".mjs"_L1, Qt::CaseInsensitive)
598 || sourceFile.endsWith(".qs"_L1, Qt::CaseInsensitive))
599 requireQmlSupport = true;
600#endif // QT_NO_QML
601 else if (sourceFile.endsWith(u".py", Qt::CaseInsensitive))
602 loadPython(fetchedTor, sourceFile, cd);
603 else if (!processTs(fetchedTor, sourceFile, cd))
604 sourceFilesCpp << sourceFile;
605 }
606
607#ifdef QT_NO_QML
608 if (requireQmlSupport) {
609 printWarning(options, u"missing qml/javascript support\n"_s,
610 u"Some files have been ignored.\n"_s);
611 if (options & Werror)
612 return;
613 }
614#else
615 Q_UNUSED(options)
616#endif
617
618 loadCPP(fetchedTor, sourceFilesCpp, cd);
619
620 if (!cd.error().isEmpty())
621 printErr(cd.error());
622}
623
624static QSet<QString> projectRoots(const QString &projectFile, const QStringList &sourceFiles)
625{
626 const QString proPath = QFileInfo(projectFile).path();
627 QSet<QString> sourceDirs;
628 sourceDirs.insert(proPath + u'/');
629 for (const QString &sf : sourceFiles)
630 sourceDirs.insert(sf.left(sf.lastIndexOf(u'/') + 1));
631 QStringList rootList = sourceDirs.values();
632 rootList.sort();
633 for (int prev = 0, curr = 1; curr < rootList.size(); )
634 if (rootList.at(curr).startsWith(rootList.at(prev)))
635 rootList.removeAt(curr);
636 else
637 prev = curr++;
638 return QSet<QString>(rootList.cbegin(), rootList.cend());
639}
640
642{
643public:
644 ProjectProcessor(const QString &sourceLanguage,
645 const QString &targetLanguage)
648 {
649 }
650
651 void processProjects(bool topLevel, UpdateOptions options, const Projects &projects,
652 bool nestComplain, Translator *parentTor, bool *fail) const
653 {
654 for (const Project &prj : projects)
655 processProject(options, prj, topLevel, nestComplain, parentTor, fail);
656 }
657
658private:
659
660 void processProject(UpdateOptions options, const Project &prj, bool topLevel,
661 bool nestComplain, Translator *parentTor, bool *fail) const
662 {
663
664 QString codecForSource = prj.codec.toLower();
665 if (!codecForSource.isEmpty()) {
666 if (codecForSource == "utf-16"_L1 || codecForSource == "utf16"_L1) {
667 options |= SourceIsUtf16;
668 } else if (codecForSource == "utf-8"_L1 || codecForSource == "utf8"_L1) {
669 options &= ~SourceIsUtf16;
670 } else {
671 printWarning(
672 options,
673 "Codec for source '%1' is invalid.\n"_L1.arg(codecForSource),
674 u"Falling back to UTF-8.\n"_s);
675 if (options & Werror)
676 return;
677 options &= ~SourceIsUtf16;
678 }
679 }
680
681 const QString projectFile = prj.filePath;
682 const QStringList sources = prj.sources;
684 cd.m_noUiLines = options & NoUiLines;
685 cd.m_projectRoots = projectRoots(projectFile, sources);
686 QStringList projectRootDirs;
687 for (auto dir : cd.m_projectRoots)
688 projectRootDirs.append(dir);
689 cd.m_rootDirs = projectRootDirs;
690 cd.m_includePath = prj.includePaths;
691 cd.m_excludes = prj.excluded;
692 cd.m_sourceIsUtf16 = options & SourceIsUtf16;
694 cd.m_compilationDatabaseDir = prj.compileCommands;
695 else
696 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
697
698 QStringList tsFiles;
699 if (prj.translations) {
700 tsFiles = *prj.translations;
701 if (parentTor) {
702 if (topLevel) {
703 printWarning(options, u"Existing top level."_s,
704 "TS files from command line will "
705 "override TRANSLATIONS in %1.\n"_L1.arg(projectFile),
706 u"Terminating the operation.\n"_s);
707 if (options & Werror)
708 return;
709 goto noTrans;
710 } else if (nestComplain) {
711 printWarning(options,
712 "TS files from command line "
713 "prevent recursing into %1.\n"_L1.arg(projectFile));
714 return;
715 }
716 }
717 if (tsFiles.isEmpty()) {
718 // This might mean either a buggy PRO file or an intentional detach -
719 // we can't know without seeing the actual RHS of the assignment ...
720 // Just assume correctness and be silent.
721 return;
722 }
723 Translator tor;
724 processProjects(false, options, prj.subProjects, false, &tor, fail);
725 processSources(tor, sources, cd, options);
726 updateTsFiles(tor, tsFiles, QStringList(), m_sourceLanguage, m_targetLanguage,
727 options, fail);
728 return;
729 }
730
731
732 noTrans:
733
734 if (!parentTor) {
735 if (topLevel) {
736 printWarning(options, u"no TS files specified."_s,
737 "Only diagnostics will be produced for %1.\n"_L1.arg(projectFile),
738 u"Terminating the operation.\n"_s);
739 if (options & Werror)
740 return;
741 }
742 Translator tor;
743 processProjects(false, options, prj.subProjects, nestComplain, &tor, fail);
744 processSources(tor, sources, cd, options);
745 } else {
746 processProjects(false, options, prj.subProjects, nestComplain, parentTor, fail);
747 processSources(*parentTor, sources, cd, options);
748 }
749 }
750
751 QString m_sourceLanguage;
752 QString m_targetLanguage;
753};
754
755int main(int argc, char **argv)
756{
757 QCoreApplication app(argc, argv);
758#ifndef QT_BOOTSTRAPPED
759#ifndef Q_OS_WIN32
760 QTranslator translator;
761 QTranslator qtTranslator;
762 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
763 if (translator.load("linguist_en"_L1, resourceDir)
764 && qtTranslator.load("qt_en"_L1, resourceDir)) {
765 app.installTranslator(&translator);
766 app.installTranslator(&qtTranslator);
767 }
768#endif // Q_OS_WIN32
769#endif
770
771 m_defaultExtensions = QLatin1String("java,jui,ui,c,c++,cc,cpp,cxx,ch,h,"_L1
772 "h++,hh,hpp,hxx,js,mjs,qs,qml,qrc"_L1);
773
774 QStringList args = app.arguments();
775 QStringList tsFileNames;
776 QStringList proFiles;
777 QString projectDescriptionFile;
778 QString outDir = QDir::currentPath();
779 QMultiHash<QString, QString> allCSources;
780 QSet<QString> projectRoots;
781 QStringList sourceFiles;
782 QStringList resourceFiles;
783 QStringList includePath;
784 QStringList alienFiles;
785 QString targetLanguage;
786 QString sourceLanguage;
787
788 UpdateOptions options =
789 Verbose | // verbose is on by default starting with Qt 4.2
791 int numFiles = 0;
792 bool metTsFlag = false;
793 bool metXTsFlag = false;
794 bool recursiveScan = true;
795
796 bool fail = false;
797
798 QString extensions = m_defaultExtensions;
799 QSet<QString> extensionsNameFilters;
800
801 for (int i = 1; i < args.size(); ++i) {
802 QString arg = args.at(i);
803 if (arg == "-help"_L1 || arg == "--help"_L1 || arg == "-h"_L1) {
805 return 0;
806 } else if (arg == "-list-languages"_L1) {
807 printOut(getNumerusInfoString());
808 return 0;
809 } else if (arg == "-pluralonly"_L1) {
810 options |= PluralOnly;
811 continue;
812 } else if (arg == "-noobsolete"_L1 || arg == "-no-obsolete"_L1) {
813 options |= NoObsolete;
814 continue;
815 } else if (arg == "-silent"_L1) {
816 options &= ~Verbose;
817 continue;
818 } else if (arg == "-pro-debug"_L1) {
819 continue;
820 } else if (arg == "-project"_L1) {
821 ++i;
822 if (i == argc) {
823 printErr(u"The option -project requires a parameter.\n"_s);
824 return 1;
825 }
826 if (!projectDescriptionFile.isEmpty()) {
827 printErr(u"The option -project must appear only once.\n"_s);
828 return 1;
829 }
830 projectDescriptionFile = args[i];
831 numFiles++;
832 continue;
833 } else if (arg == "-target-language"_L1) {
834 ++i;
835 if (i == argc) {
836 printErr(u"The option -target-language requires a parameter.\n"_s);
837 return 1;
838 }
839 targetLanguage = args[i];
840 continue;
841 } else if (arg == "-source-language"_L1) {
842 ++i;
843 if (i == argc) {
844 printErr(u"The option -source-language requires a parameter.\n"_s);
845 return 1;
846 }
847 sourceLanguage = args[i];
848 continue;
849 } else if (arg == "-disable-heuristic"_L1) {
850 ++i;
851 if (i == argc) {
852 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
853 return 1;
854 }
855 arg = args[i];
856 if (arg == "sametext"_L1) {
857 options &= ~HeuristicSameText;
858 } else if (arg == "similartext"_L1) {
859 options &= ~HeuristicSimilarText;
860 } else {
861 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
862 return 1;
863 }
864 continue;
865 } else if (arg == "-locations"_L1) {
866 ++i;
867 if (i == argc) {
868 printErr(u"The option -locations requires a parameter.\n"_s);
869 return 1;
870 }
871 if (args[i] == "none"_L1) {
872 options |= NoLocations;
873 } else if (args[i] == "relative"_L1) {
874 options |= RelativeLocations;
875 } else if (args[i] == "absolute"_L1) {
876 options |= AbsoluteLocations;
877 } else {
878 printErr(u"Invalid parameter passed to -locations.\n"_s);
879 return 1;
880 }
881 continue;
882 } else if (arg == "-no-ui-lines"_L1) {
883 options |= NoUiLines;
884 continue;
885 } else if (arg == "-verbose"_L1) {
886 options |= Verbose;
887 continue;
888 } else if (arg == "-warnings-are-errors"_L1) {
889 options |= Werror;
890 continue;
891 } else if (arg == "-no-recursive"_L1) {
892 recursiveScan = false;
893 continue;
894 } else if (arg == "-recursive"_L1) {
895 recursiveScan = true;
896 continue;
897 } else if (arg == "-no-sort"_L1 || arg == "-nosort"_L1) {
898 options |= NoSort;
899 continue;
900 } else if (arg == "-sort-messages"_L1) {
901 options |= SortMessages;
902 continue;
903 } else if (arg == "-version"_L1) {
904 printOut(QStringLiteral("lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
905 return 0;
906 } else if (arg == "-ts"_L1) {
907 metTsFlag = true;
908 metXTsFlag = false;
909 continue;
910 } else if (arg == "-xts"_L1) {
911 metTsFlag = false;
912 metXTsFlag = true;
913 continue;
914 } else if (arg == "-extensions"_L1) {
915 ++i;
916 if (i == argc) {
917 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
918 return 1;
919 }
920 extensions = args[i];
921 continue;
922 } else if (arg == "-tr-function-alias"_L1) {
923 ++i;
924 if (i == argc) {
925 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
926 return 1;
927 }
928 if (!handleTrFunctionAliases(args[i]))
929 return 1;
930 continue;
931 } else if (arg == "-pro"_L1) {
932 ++i;
933 if (i == argc) {
934 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
935 return 1;
936 }
937 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
938 proFiles += file;
939 numFiles++;
940 continue;
941 } else if (arg == "-pro-out"_L1) {
942 ++i;
943 if (i == argc) {
944 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
945 return 1;
946 }
947 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
948 continue;
949 } else if (arg.startsWith("-I"_L1)) {
950 if (arg.size() == 2) {
951 ++i;
952 if (i == argc) {
953 printErr(u"The -I option should be followed by a path.\n"_s);
954 return 1;
955 }
956 includePath += args[i];
957 } else {
958 includePath += args[i].mid(2);
959 }
960 continue;
961 }
962 else if (arg.startsWith("-"_L1) && arg != "-"_L1) {
963 printErr("Unrecognized option '%1'.\n"_L1.arg(arg));
964 return 1;
965 }
966
967 QStringList files;
968 if (arg.startsWith("@"_L1)) {
969 QFile lstFile(arg.mid(1));
970 if (!lstFile.open(QIODevice::ReadOnly)) {
971 printErr(QStringLiteral("lupdate error: List file '%1' is not readable.\n")
972 .arg(lstFile.fileName()));
973 return 1;
974 }
975 while (!lstFile.atEnd()) {
976 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
977
978 if (lineContent.startsWith("-I"_L1)) {
979 if (lineContent.size() == 2) {
980 printErr(u"The -I option should be followed by a path.\n"_s);
981 return 1;
982 }
983 includePath += lineContent.mid(2);
984 } else {
985 files << lineContent;
986 }
987 }
988 } else {
989 files << arg;
990 }
991 if (metTsFlag) {
992 for (const QString &file : std::as_const(files)) {
993 bool found = false;
994 for (const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
995 if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
996 QFileInfo fi(file);
997 if (!fi.exists() || fi.isWritable()) {
998 tsFileNames.append(QFileInfo(file).absoluteFilePath());
999 } else {
1000 printWarning(options,
1001 "For some reason, '%1' is not writable.\n"_L1
1002 .arg(file));
1003 if (options & Werror)
1004 return 1;
1005 }
1006 found = true;
1007 break;
1008 }
1009 }
1010 if (!found) {
1011 printErr(QStringLiteral("lupdate error: File '%1' has no recognized extension.\n")
1012 .arg(file));
1013 return 1;
1014 }
1015 }
1016 numFiles++;
1017 } else if (metXTsFlag) {
1018 alienFiles += files;
1019 } else {
1020 for (const QString &file : std::as_const(files)) {
1021 QFileInfo fi(file);
1022 if (!fi.exists()) {
1023 printErr(QStringLiteral("lupdate error: File '%1' does not exist.\n").arg(file));
1024 return 1;
1025 }
1026 if (isProOrPriFile(file)) {
1027 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
1028 proFiles << cleanFile;
1029 } else if (fi.isDir()) {
1030 if (options & Verbose)
1031 printOut(QStringLiteral("Scanning directory '%1'...\n").arg(file));
1032 QDir dir = QDir(fi.filePath());
1033 projectRoots.insert(dir.absolutePath() + u'/');
1034 if (extensionsNameFilters.isEmpty()) {
1035 for (QString ext : extensions.split(u',')) {
1036 ext = ext.trimmed();
1037 if (ext.startsWith(u'.'))
1038 ext.remove(0, 1);
1039 extensionsNameFilters.insert(ext);
1040 }
1041 }
1042 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
1043 if (recursiveScan)
1044 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
1045 QFileInfoList fileinfolist;
1046 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
1047 int scanRootLen = dir.absolutePath().size();
1048 for (const QFileInfo &fi : std::as_const(fileinfolist)) {
1049 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1050 if (fn.endsWith(".qrc"_L1, Qt::CaseInsensitive)) {
1051 resourceFiles << fn;
1052 } else {
1053 sourceFiles << fn;
1054
1055 if (!fn.endsWith(".java"_L1) && !fn.endsWith(".jui"_L1)
1056 && !fn.endsWith(".ui"_L1) && !fn.endsWith(".js"_L1)
1057 && !fn.endsWith(".mjs"_L1) && !fn.endsWith(".qs"_L1)
1058 && !fn.endsWith(".qml"_L1)) {
1059 int offset = 0;
1060 int depth = 0;
1061 do {
1062 offset = fn.lastIndexOf(u'/', offset - 1);
1063 QString ffn = fn.mid(offset + 1);
1064 allCSources.insert(ffn, fn);
1065 } while (++depth < 3 && offset > scanRootLen);
1066 }
1067 }
1068 }
1069 } else {
1070 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1071 if (fn.endsWith(".qrc"_L1, Qt::CaseInsensitive))
1072 resourceFiles << fn;
1073 else
1074 sourceFiles << fn;
1075 projectRoots.insert(fi.absolutePath() + u'/');
1076 }
1077 }
1078 numFiles++;
1079 }
1080 } // for args
1081
1082 if (numFiles == 0) {
1084 return 1;
1085 }
1086
1087 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1) {
1088 printWarning(options,
1089 u"-target-language usually only makes sense with exactly one TS file.\n"_s);
1090 if (options & Werror)
1091 return 1;
1092 }
1093
1094 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
1095 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
1096 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
1097 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
1098 u"'update_translations' target.\n"_s);
1099 return 1;
1100 }
1101
1102 QString errorString;
1103 if (!proFiles.isEmpty()) {
1104 runInternalQtTool(u"lupdate-pro"_s, app.arguments().mid(1));
1105 return 0;
1106 }
1107
1108 Projects projectDescription;
1109 if (!projectDescriptionFile.isEmpty()) {
1110 projectDescription = readProjectDescription(projectDescriptionFile, &errorString);
1111 if (!errorString.isEmpty()) {
1112 printErr(QStringLiteral("lupdate error: %1\n").arg(errorString));
1113 return 1;
1114 }
1115 if (projectDescription.empty()) {
1116 printErr(QStringLiteral("lupdate error:"
1117 " Could not find project descriptions in %1.\n")
1118 .arg(projectDescriptionFile));
1119 return 1;
1120 }
1121 removeExcludedSources(projectDescription);
1122 for (Project &project : projectDescription)
1123 expandQrcFiles(project);
1124 }
1125
1126 if (projectDescription.empty()) {
1127 if (tsFileNames.isEmpty()) {
1128 printWarning(options, u"no TS files specified."_s,
1129 u"Only diagnostics will be produced.\n"_s,
1130 u"Terminating the operation.\n"_s);
1131 if (options & Werror)
1132 return 1;
1133 }
1134
1135 Translator fetchedTor;
1136 ConversionData cd;
1137 cd.m_noUiLines = options & NoUiLines;
1138 cd.m_sourceIsUtf16 = options & SourceIsUtf16;
1139 cd.m_projectRoots = projectRoots;
1140 cd.m_includePath = includePath;
1141 cd.m_allCSources = allCSources;
1142 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
1143 cd.m_rootDirs = rootDirs;
1144 for (const QString &resource : std::as_const(resourceFiles))
1145 sourceFiles << getResources(resource);
1146 processSources(fetchedTor, sourceFiles, cd, options);
1147 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1148 sourceLanguage, targetLanguage, options, &fail);
1149 } else {
1150 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
1151 printErr(QStringLiteral("lupdate error:"
1152 " Both project and source files / include paths specified.\n"));
1153 return 1;
1154 }
1155 ProjectProcessor projectProcessor(sourceLanguage, targetLanguage);
1156 if (!tsFileNames.isEmpty()) {
1157 Translator fetchedTor;
1158 projectProcessor.processProjects(true, options, projectDescription, true, &fetchedTor,
1159 &fail);
1160 if (!fail) {
1161 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1162 sourceLanguage, targetLanguage, options, &fail);
1163 }
1164 } else {
1165 projectProcessor.processProjects(true, options, projectDescription, false, nullptr,
1166 &fail);
1167 }
1168 }
1169 return fail ? 1 : 0;
1170}
bool m_sourceIsUtf16
Definition translator.h:44
void processProjects(bool topLevel, UpdateOptions options, const Projects &projects, bool nestComplain, Translator *parentTor, bool *fail) const
Definition main.cpp:651
ProjectProcessor(const QString &sourceLanguage, const QString &targetLanguage)
Definition main.cpp:644
bool hasError() const
Definition qrcreader.h:17
QStringList availableFunctionsWithAliases() const
Definition main.cpp:109
QStringList listAliases() const
Definition main.cpp:119
int trFunctionByName(const QString &trFunctionName) const
Definition main.cpp:64
void modifyAlias(int trFunction, const QString &alias, Operation op)
Definition main.cpp:72
const NameToTrFunctionMap & nameToTrFunctionMap() const
Definition main.cpp:94
int messageCount() const
Definition translator.h:139
const TranslatorMessage & constMessage(int i) const
Definition translator.h:142
#define LUPDATE_FOR_EACH_TR_FUNCTION(UNARY_MACRO)
Definition lupdate.h:57
@ NoLocations
Definition lupdate.h:32
@ SortMessages
Definition lupdate.h:36
@ Verbose
Definition lupdate.h:24
@ NoObsolete
Definition lupdate.h:25
@ PluralOnly
Definition lupdate.h:26
@ Werror
Definition lupdate.h:35
@ NoSort
Definition lupdate.h:27
@ HeuristicSimilarText
Definition lupdate.h:29
@ SourceIsUtf16
Definition lupdate.h:34
@ HeuristicSameText
Definition lupdate.h:28
@ AbsoluteLocations
Definition lupdate.h:30
@ RelativeLocations
Definition lupdate.h:31
@ NoUiLines
Definition lupdate.h:33
std::vector< Project > Projects
Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4==0)
static void printOut(const QString &out)
Definition main.cpp:21
static void printErr(const QString &out)
Definition main.cpp:15
static void printUsage()
Definition main.cpp:60
static QStringList getResources(const QString &resourceFile)
Definition main.cpp:511
static QString m_defaultExtensions
Definition main.cpp:192
static void printWarning(UpdateOptions options, const QString &msg, const QString &warningMsg={}, const QString &errorMsg={})
Definition main.cpp:204
QStringList rootDirs
Definition main.cpp:29
static QStringList availableFunctions()
Definition main.cpp:100
static int trFunctionByDefaultName(const QString &trFunctionName)
Definition main.cpp:47
static void removeExcludedSources(Projects &projects)
Definition main.cpp:496
static const QString defaultTrFunctionNames[]
Definition main.cpp:34
static void expandQrcFiles(Project &project)
Definition main.cpp:548
static void recursiveFileInfoList(const QDir &dir, const QSet< QString > &nameFilters, QDir::Filters filter, QFileInfoList *fileinfolist)
Definition main.cpp:223
TrFunctionAliasManager trFunctionAliasManager
Definition main.cpp:132
static bool readFileContent(const QString &filePath, QByteArray *content, QString *errorString)
Definition main.cpp:476
QString commandLineCompilationDatabaseDir
Definition main.cpp:27
static void updateTsFiles(const Translator &fetchedTor, const QStringList &tsFileNames, const QStringList &alienFiles, const QString &sourceLanguage, const QString &targetLanguage, UpdateOptions options, bool *fail)
Definition main.cpp:348
static bool processTs(Translator &fetchedTor, const QString &file, ConversionData &cd)
Definition main.cpp:554
static QSet< QString > projectRoots(const QString &projectFile, const QStringList &sourceFiles)
Definition main.cpp:624
static bool handleTrFunctionAliases(const QString &arg)
Definition main.cpp:318
static QStringList extractQrcFiles(Project &project)
Definition main.cpp:530
static void processSources(Translator &fetchedTor, const QStringList &sourceFiles, ConversionData &cd, UpdateOptions options)
Definition main.cpp:573
int main(int argc, char *argv[])
[ctor_close]