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