1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "cpp_clang.h"
5#include "clangtoolastreader.h"
6#include "filesignificancecheck.h"
7#include "lupdatepreprocessoraction.h"
8#include "synchronized.h"
9#include "translator.h"
10
11#include <QLibraryInfo>
12#include <QtCore/qdir.h>
13#include <QtCore/qfileinfo.h>
14#include <QtCore/qjsonarray.h>
15#include <QtCore/qjsondocument.h>
16#include <QtCore/qjsonobject.h>
17#include <QtCore/qscopeguard.h>
18#include <QtCore/QProcess>
19#include <QStandardPaths>
20#include <QtTools/private/qttools-config_p.h>
21
22#include <clang/Tooling/CompilationDatabase.h>
23
24#include <algorithm>
25#include <limits>
26#include <thread>
27#include <iostream>
28#include <cstdlib>
29
30#include <cstdio>
31#include <memory>
32#include <stdexcept>
33#include <string>
34#include <array>
35
36using clang::tooling::CompilationDatabase;
37
38QT_BEGIN_NAMESPACE
39
40using namespace Qt::StringLiterals;
41
42Q_LOGGING_CATEGORY(lcClang, "qt.lupdate.clang");
43
44static QString getSysCompiler()
45{
46 QStringList candidates;
47 if (const char* local_compiler = std::getenv(name: "CXX")) {
48 candidates.push_back(t: QLatin1String(local_compiler));
49 } else {
50 candidates = {
51#ifdef Q_OS_WIN
52 QStringLiteral("cl"),
53#endif
54 QStringLiteral("clang++"),
55 QStringLiteral("gcc")
56 };
57 }
58 QString sysCompiler;
59 for (const QString &comp : candidates) {
60
61 sysCompiler = QStandardPaths::findExecutable(executableName: comp);
62 if (!sysCompiler.isEmpty())
63 break;
64 }
65 return sysCompiler;
66}
67
68static QByteArrayList getMSVCIncludePathsFromEnvironment()
69{
70 QList<QByteArray> pathList;
71 if (const char* includeEnv = std::getenv(name: "INCLUDE")) {
72 QByteArray includeList = QByteArray::fromRawData(data: includeEnv, size: strlen(s: includeEnv));
73 pathList = includeList.split(sep: ';');
74 }
75 for (auto it = pathList.begin(); it != pathList.end(); ++it) {
76 it->prepend(s: "-isystem");
77 }
78 return pathList;
79}
80
81static QStringList getProjectDirsFromEnvironment()
82{
83 QList<QByteArray> dirList;
84 QStringList rootdirs;
85 if (const char* includeEnv = std::getenv(name: "LUPDATE_ROOT_DIRS")) {
86 QByteArray includeList = QByteArray::fromRawData(data: includeEnv, size: strlen(s: includeEnv));
87 dirList = includeList.split(sep: ';');
88
89 for (auto dir : dirList) {
90 rootdirs.append(t: QString::fromStdString(s: dir.toStdString()));
91 }
92 }
93 return rootdirs;
94}
95
96
97static QByteArray frameworkSuffix()
98{
99 return QByteArrayLiteral(" (framework directory)");
100}
101
102QByteArrayList getIncludePathsFromCompiler()
103{
104
105 QList<QByteArray> pathList;
106 QString compiler = getSysCompiler();
107 if (compiler.isEmpty()) {
108 qWarning(msg: "lupdate: Could not determine system compiler.");
109 return pathList;
110 }
111
112 const QFileInfo fiCompiler(compiler);
113 const QString compilerName
114
115#ifdef Q_OS_WIN
116 = fiCompiler.completeBaseName();
117#else
118 = fiCompiler.fileName();
119#endif
120
121 if (compilerName == QLatin1String("cl"))
122 return getMSVCIncludePathsFromEnvironment();
123
124 if (compilerName != QLatin1String("gcc") && compilerName != QLatin1String("clang++")) {
125 qWarning(msg: "lupdate: Unknown compiler %s", qPrintable(compiler));
126 return pathList;
127 }
128
129 const QStringList compilerFlags = {
130 QStringLiteral("-E"), QStringLiteral("-x"), QStringLiteral("c++"),
131 QStringLiteral("-"), QStringLiteral("-v")
132 };
133
134 QProcess proc;
135 proc.setStandardInputFile(proc.nullDevice());
136 proc.start(program: compiler, arguments: compilerFlags);
137 proc.waitForFinished(msecs: 30000);
138 QByteArray buffer = proc.readAllStandardError();
139 proc.kill();
140
141 // ### TODO: Merge this with qdoc's getInternalIncludePaths()
142 const QByteArrayList stdErrLines = buffer.split(sep: '\n');
143 bool isIncludeDir = false;
144 for (const QByteArray &line : stdErrLines) {
145 if (isIncludeDir) {
146 if (line.startsWith(QByteArrayLiteral("End of search list"))) {
147 isIncludeDir = false;
148 } else {
149 QByteArray prefix("-isystem");
150 QByteArray headerPath{line.trimmed()};
151 if (headerPath.endsWith(bv: frameworkSuffix())) {
152 headerPath.truncate(pos: headerPath.size() - frameworkSuffix().size());
153 prefix = QByteArrayLiteral("-F");
154 }
155 pathList.append(t: prefix + headerPath);
156 }
157 } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
158 isIncludeDir = true;
159 }
160 }
161
162 return pathList;
163}
164
165std::vector<std::string> ClangCppParser::getAliasFunctionDefinition()
166{
167 QStringList aliases = trFunctionAliasManager.listAliases();
168 std::vector<std::string> results;
169 for (QString alias : aliases) {
170 std::string definition = "-D" + alias.toStdString();
171 switch (trFunctionAliasManager.trFunctionByName(trFunctionName: alias)) {
172 case TrFunctionAliasManager::Function_QT_TR_N_NOOP:
173 definition += "(x)=QT_TR_N_NOOP(x)";
174 results.push_back(x: definition);
175 break;
176 case TrFunctionAliasManager::Function_trUtf8:
177 case TrFunctionAliasManager::Function_tr:
178 definition += "=tr";
179 results.push_back(x: definition);
180 break;
181 case TrFunctionAliasManager::Function_QT_TR_NOOP:
182 definition += "(x)=QT_TR_NOOP(x)";
183 results.push_back(x: definition);
184 break;
185 case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8:
186 definition += "(x)=QT_TR_NOOP_UTF8(x)";
187 results.push_back(x: definition);
188 break;
189 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP:
190 definition += "(scope,x)=QT_TRANSLATE_N_NOOP(scope,x)";
191 results.push_back(x: definition);
192 break;
193 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3:
194 definition += "(scope, x, comment)=QT_TRANSLATE_N_NOOP3(scope, x, comment)";
195 results.push_back(x: definition);
196 break;
197 case TrFunctionAliasManager::Function_translate:
198 definition += "=QCoreApplication::translate";
199 results.push_back(x: definition);
200 break;
201 case TrFunctionAliasManager::Function_findMessage:
202 definition += "=findMessage";
203 results.push_back(x: definition);
204 break;
205 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
206 definition += "(scope,x)=QT_TRANSLATE_NOOP(scope,x)";
207 results.push_back(x: definition);
208 break;
209 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8:
210 definition += "(scope,x)=QT_TRANSLATE_NOOP_UTF8(scope,x)";
211 results.push_back(x: definition);
212 break;
213 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3:
214 definition += "(scope, x, comment)=QT_TRANSLATE_NOOP3(scope, x, comment)";
215 results.push_back(x: definition);
216 break;
217 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8:
218 definition += "(scope, x, comment)=QT_TRANSLATE_NOOP3_UTF8(scope, x, comment)";
219 results.push_back(x: definition);
220 break;
221 case TrFunctionAliasManager::Function_qtTrId:
222 definition += "=qtTrId";
223 results.push_back(x: definition);
224 break;
225 case TrFunctionAliasManager::Function_QT_TRID_N_NOOP:
226 case TrFunctionAliasManager::Function_QT_TRID_NOOP:
227 definition += "(id)=QT_TRID_NOOP(id)";
228 results.push_back(x: definition);
229 break;
230 case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS:
231 definition += "(context)=Q_DECLARE_TR_FUNCTIONS(context)";
232 results.push_back(x: definition);
233 break;
234 default:
235 break;
236 }
237 }
238 return results;
239}
240
241static std::vector<std::string> aliasDefinition;
242
243static clang::tooling::ArgumentsAdjuster getClangArgumentAdjuster()
244{
245 const QByteArrayList compilerIncludeFlags = getIncludePathsFromCompiler();
246 return [=](const clang::tooling::CommandLineArguments &args, llvm::StringRef /*unused*/) {
247 clang::tooling::CommandLineArguments adjustedArgs(args);
248 clang::tooling::CommandLineArguments adjustedArgsTemp;
249
250 adjustedArgsTemp.push_back(x: "-fparse-all-comments");
251 adjustedArgsTemp.push_back(x: "-nostdinc");
252
253 // Turn off SSE support to avoid usage of gcc builtins.
254 // TODO: Look into what Qt Creator does.
255 // Pointers: HeaderPathFilter::removeGccInternalIncludePaths()
256 // and gccInstallDir() in gcctoolchain.cpp
257 // Also needed for Mac, No need for CLANG_RESOURCE_DIR when this is part of the argument.
258 adjustedArgsTemp.push_back(x: "-mno-sse");
259
260#ifdef Q_OS_WIN
261 adjustedArgsTemp.push_back("-fms-compatibility-version=19");
262 adjustedArgsTemp.push_back("-DQ_COMPILER_UNIFORM_INIT"); // qtbase + clang-cl hack
263 // avoid constexpr error connected with offsetof (QTBUG-97380)
264 adjustedArgsTemp.push_back("-D_CRT_USE_BUILTIN_OFFSETOF");
265#endif
266 adjustedArgsTemp.push_back(x: "-Wno-everything");
267
268 for (const QByteArray &flag : compilerIncludeFlags)
269 adjustedArgsTemp.push_back(x: flag.data());
270
271 for (auto alias : aliasDefinition) {
272 adjustedArgsTemp.push_back(x: alias);
273 }
274
275 clang::tooling::CommandLineArguments::iterator it = llvm::find(Range&: adjustedArgs, Val: "--");
276 adjustedArgs.insert(position: it, first: adjustedArgsTemp.begin(), last: adjustedArgsTemp.end());
277 return adjustedArgs;
278 };
279}
280
281bool ClangCppParser::stringContainsTranslationInformation(llvm::StringRef ba)
282{
283 // pre-process the files by a simple text search if there is any occurrence
284 // of things we are interested in
285 constexpr llvm::StringLiteral qDeclareTrFunction("Q_DECLARE_TR_FUNCTIONS(");
286 constexpr llvm::StringLiteral qtTrNoop("QT_TR_NOOP(");
287 constexpr llvm::StringLiteral qtTrNoopUTF8("QT_TR_NOOP)UTF8(");
288 constexpr llvm::StringLiteral qtTrNNoop("QT_TR_N_NOOP(");
289 constexpr llvm::StringLiteral qtTrIdNoop("QT_TRID_NOOP(");
290 constexpr llvm::StringLiteral qtTrIdNNoop("QT_TRID_N_NOOP(");
291 constexpr llvm::StringLiteral qtTranslateNoop("QT_TRANSLATE_NOOP(");
292 constexpr llvm::StringLiteral qtTranslateNoopUTF8("QT_TRANSLATE_NOOP_UTF8(");
293 constexpr llvm::StringLiteral qtTranslateNNoop("QT_TRANSLATE_N_NOOP(");
294 constexpr llvm::StringLiteral qtTranslateNoop3("QT_TRANSLATE_NOOP3(");
295 constexpr llvm::StringLiteral qtTranslateNoop3UTF8("QT_TRANSLATE_NOOP3_UTF8(");
296 constexpr llvm::StringLiteral qtTranslateNNoop3("QT_TRANSLATE_N_NOOP3(");
297 constexpr llvm::StringLiteral translatorComment("TRANSLATOR ");
298 constexpr llvm::StringLiteral qtTrId("qtTrId(");
299 constexpr llvm::StringLiteral tr("tr(");
300 constexpr llvm::StringLiteral trUtf8("trUtf8(");
301 constexpr llvm::StringLiteral translate("translate(");
302
303 const size_t pos = ba.find_first_of(Chars: "QT_TR");
304 const auto baSliced = ba.slice(Start: pos, End: llvm::StringRef::npos);
305 if (pos != llvm::StringRef::npos) {
306 if (baSliced.contains(Other: qtTrNoop) || baSliced.contains(Other: qtTrNoopUTF8) || baSliced.contains(Other: qtTrNNoop)
307 || baSliced.contains(Other: qtTrIdNoop) || baSliced.contains(Other: qtTrIdNNoop)
308 || baSliced.contains(Other: qtTranslateNoop) || baSliced.contains(Other: qtTranslateNoopUTF8)
309 || baSliced.contains(Other: qtTranslateNNoop) || baSliced.contains(Other: qtTranslateNoop3)
310 || baSliced.contains(Other: qtTranslateNoop3UTF8) || baSliced.contains(Other: qtTranslateNNoop3))
311 return true;
312 }
313
314 if (ba.contains(Other: qDeclareTrFunction) || ba.contains(Other: translatorComment) || ba.contains(Other: qtTrId) || ba.contains(Other: tr)
315 || ba.contains(Other: trUtf8) || ba.contains(Other: translate))
316 return true;
317
318 for (QString alias : trFunctionAliasManager.listAliases()) {
319 if (ba.contains(qPrintable(alias)))
320 return true;
321 }
322
323
324 return false;
325}
326
327static bool generateCompilationDatabase(const QString &outputFilePath, const ConversionData &cd)
328{
329 QJsonArray commandObjects;
330 const QString buildDir = QDir::currentPath();
331 const QString fakefileName = QLatin1String("dummmy.cpp");
332 QJsonObject obj;
333 obj[QLatin1String("file")] = fakefileName;
334 obj[QLatin1String("directory")] = buildDir;
335 QJsonArray args = {
336 QLatin1String("clang++"),
337 QLatin1String("-std=gnu++17"),
338 #ifndef Q_OS_WIN
339 QLatin1String("-fPIC"),
340 #endif
341 };
342
343#if defined(Q_OS_MACOS) && QT_CONFIG(framework)
344 const QString installPath = QLibraryInfo::path(QLibraryInfo::LibrariesPath);
345 QString arg = QLatin1String("-F") + installPath;
346 args.push_back(arg);
347#endif
348
349 for (const QString &path : cd.m_includePath) {
350 QString arg = QLatin1String("-I") + path;
351 args.push_back(t: std::move(arg));
352 }
353
354 obj[QLatin1String("arguments")] = args;
355 commandObjects.append(value: obj);
356
357 QJsonDocument doc(commandObjects);
358 QFile file(outputFilePath);
359 if (!file.open(flags: QIODevice::WriteOnly))
360 return false;
361 file.write(data: doc.toJson());
362 return true;
363}
364
365// Sort messages in such a way that they appear in the same order like in the given file list.
366static void sortMessagesByFileOrder(ClangCppParser::TranslatorMessageVector &messages,
367 const QStringList &files)
368{
369 // first sort messages by line number
370 std::stable_sort(first: messages.begin(), last: messages.end(),
371 comp: [&](const TranslatorMessage &lhs, const TranslatorMessage &rhs) {
372 auto i = lhs.lineNumber();
373 auto k = rhs.lineNumber();
374 return i < k;
375 });
376
377 QHash<QString, QStringList::size_type> indexByPath;
378 for (const TranslatorMessage &m : messages)
379 indexByPath[m.fileName()] = std::numeric_limits<QStringList::size_type>::max();
380
381 for (QStringList::size_type i = 0; i < files.size(); ++i)
382 indexByPath[files[i]] = i;
383
384 std::stable_sort(first: messages.begin(), last: messages.end(),
385 comp: [&](const TranslatorMessage &lhs, const TranslatorMessage &rhs) {
386 auto i = indexByPath.value(key: lhs.fileName());
387 auto k = indexByPath.value(key: rhs.fileName());
388 return i < k;
389 });
390}
391
392bool ClangCppParser::hasAliases()
393{
394 QStringList listAlias = trFunctionAliasManager.listAliases();
395 if (listAlias.size() > 0)
396 return true;
397 return false;
398}
399
400void ClangCppParser::loadCPP(Translator &translator, const QStringList &files, ConversionData &cd,
401 bool *fail)
402{
403 FileSignificanceCheck::create();
404 auto cleanup = qScopeGuard(f&: FileSignificanceCheck::destroy);
405 FileSignificanceCheck::the()->setExclusionPatterns(cd.m_excludes);
406 if (cd.m_rootDirs.size() > 0)
407 FileSignificanceCheck::the()->setRootDirectories(cd.m_rootDirs);
408 else
409 FileSignificanceCheck::the()->setRootDirectories(getProjectDirsFromEnvironment());
410
411 if (hasAliases())
412 aliasDefinition = getAliasFunctionDefinition();
413
414 // pre-process the files by a simple text search if there is any occurrence
415 // of things we are interested in
416 qCDebug(lcClang) << "Load CPP \n";
417 std::vector<std::string> sources;
418 for (const QString &filename : files) {
419 qCDebug(lcClang) << "File: " << filename << " \n";
420 sources.emplace_back(args: filename.toStdString());
421 }
422
423 std::string errorMessage;
424 std::unique_ptr<CompilationDatabase> db;
425 if (cd.m_compilationDatabaseDir.isEmpty()) {
426 db = CompilationDatabase::autoDetectFromDirectory(SourceDir: ".", ErrorMessage&: errorMessage);
427 if (!db && !files.isEmpty()) {
428 db = CompilationDatabase::autoDetectFromSource(SourceFile: files.first().toStdString(),
429 ErrorMessage&: errorMessage);
430 }
431 } else {
432 db = CompilationDatabase::autoDetectFromDirectory(SourceDir: cd.m_compilationDatabaseDir.toStdString(),
433 ErrorMessage&: errorMessage);
434 }
435
436 if (!db) {
437 const QString dbFilePath = QStringLiteral("compile_commands.json");
438 qCDebug(lcClang) << "Generating compilation database" << dbFilePath;
439 if (!generateCompilationDatabase(outputFilePath: dbFilePath, cd)) {
440 *fail = true;
441 cd.appendError(error: u"Cannot generate compilation database."_s);
442 return;
443 }
444 errorMessage.clear();
445 db = CompilationDatabase::loadFromDirectory(BuildDirectory: ".", ErrorMessage&: errorMessage);
446 }
447
448 if (!db) {
449 *fail = true;
450 cd.appendError(error: QString::fromStdString(s: errorMessage));
451 return;
452 }
453
454 TranslationStores ast, qdecl, qnoop;
455 Stores stores(ast, qdecl, qnoop);
456
457 std::vector<std::thread> producers;
458 ReadSynchronizedRef<std::string> ppSources(sources);
459 WriteSynchronizedRef<TranslationRelatedStore> ppStore(stores.Preprocessor);
460 size_t idealProducerCount = std::min(a: ppSources.size(), b: size_t(std::thread::hardware_concurrency()));
461 clang::tooling::ArgumentsAdjuster argumentsAdjusterSyntaxOnly =
462 clang::tooling::getClangSyntaxOnlyAdjuster();
463 clang::tooling::ArgumentsAdjuster argumentsAdjusterLocal = getClangArgumentAdjuster();
464 clang::tooling::ArgumentsAdjuster argumentsAdjuster =
465 clang::tooling::combineAdjusters(First: argumentsAdjusterLocal, Second: argumentsAdjusterSyntaxOnly);
466
467 for (size_t i = 0; i < idealProducerCount; ++i) {
468 std::thread producer([&ppSources, &db, &ppStore, &argumentsAdjuster]() {
469 std::string file;
470 while (ppSources.next(value: &file)) {
471 clang::tooling::ClangTool tool(*db, file);
472 tool.appendArgumentsAdjuster(Adjuster: argumentsAdjuster);
473 tool.run(Action: new LupdatePreprocessorActionFactory(&ppStore));
474 }
475 });
476 producers.emplace_back(args: std::move(producer));
477 }
478 for (auto &producer : producers)
479 producer.join();
480 producers.clear();
481
482 ReadSynchronizedRef<std::string> astSources(sources);
483 idealProducerCount = std::min(a: astSources.size(), b: size_t(std::thread::hardware_concurrency()));
484 for (size_t i = 0; i < idealProducerCount; ++i) {
485 std::thread producer([&astSources, &db, &stores, &argumentsAdjuster]() {
486 std::string file;
487 while (astSources.next(value: &file)) {
488 clang::tooling::ClangTool tool(*db, file);
489 tool.appendArgumentsAdjuster(Adjuster: argumentsAdjuster);
490 tool.run(Action: new LupdateToolActionFactory(&stores));
491 }
492 });
493 producers.emplace_back(args: std::move(producer));
494 }
495 for (auto &producer : producers)
496 producer.join();
497 producers.clear();
498
499 TranslationStores finalStores;
500 WriteSynchronizedRef<TranslationRelatedStore> wsv(finalStores);
501
502 ReadSynchronizedRef<TranslationRelatedStore> rsv(ast);
503 ClangCppParser::correctAstTranslationContext(ast&: rsv, newAst&: wsv, qDecl: qdecl);
504
505 ReadSynchronizedRef<TranslationRelatedStore> rsvQNoop(qnoop);
506 //unlike ast translation context, qnoop context don't need to be corrected
507 //(because Q_DECLARE_TR_FUNCTION context is already applied).
508 ClangCppParser::finalize(ast&: rsvQNoop, newAst&: wsv);
509
510 TranslatorMessageVector messages;
511 for (auto &store : finalStores)
512 ClangCppParser::collectMessages(result&: messages, store);
513
514 sortMessagesByFileOrder(messages, files);
515
516 for (TranslatorMessage &msg : messages) {
517 if (!msg.warning().isEmpty()) {
518 std::cerr << qPrintable(msg.warning());
519 if (msg.warningOnly() == true)
520 continue;
521 }
522 translator.extend(msg: std::move(msg), cd);
523 }
524}
525
526void ClangCppParser::collectMessages(TranslatorMessageVector &result,
527 TranslationRelatedStore &store)
528{
529 if (!store.isValid(printwarning: true)) {
530 if (store.lupdateWarning.isEmpty())
531 return;
532 // The message needs to be added to the results so that the warning can be ordered
533 // and printed in a consistent way.
534 // the message won't appear in the .ts file
535 result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural: false, isID: false, isWarningOnly: true));
536 return;
537 }
538
539 qCDebug(lcClang) << "---------------------------------------------------------------Filling translator for " << store.funcName;
540 qCDebug(lcClang) << " contextRetrieved " << store.contextRetrieved;
541 qCDebug(lcClang) << " source " << store.lupdateSource;
542
543 bool plural = false;
544 switch (trFunctionAliasManager.trFunctionByName(trFunctionName: store.funcName)) {
545 // handle tr
546 case TrFunctionAliasManager::Function_QT_TR_N_NOOP:
547 plural = true;
548 Q_FALLTHROUGH();
549 case TrFunctionAliasManager::Function_tr:
550 case TrFunctionAliasManager::Function_trUtf8:
551 case TrFunctionAliasManager::Function_QT_TR_NOOP:
552 case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8:
553 if (!store.lupdateSourceWhenId.isEmpty()) {
554 std::stringstream warning;
555 warning << qPrintable(store.lupdateLocationFile) << ":"
556 << store.lupdateLocationLine << ":"
557 << store.locationCol << ": "
558 << "//% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n";
559 store.lupdateWarning.append(s: QString::fromStdString(s: warning.str()));
560 qCDebug(lcClang) << "//% is ignored when using tr function\n";
561 }
562 if (store.contextRetrieved.isEmpty() && store.contextArg.isEmpty()) {
563 std::stringstream warning;
564 warning << qPrintable(store.lupdateLocationFile) << ":"
565 << store.lupdateLocationLine << ":"
566 << store.locationCol << ": "
567 << qPrintable(store.funcName) << " cannot be called without context."
568 << " The call is ignored (missing Q_OBJECT maybe?)\n";
569 store.lupdateWarning.append(s: QString::fromStdString(s: warning.str()));
570 qCDebug(lcClang) << "tr() cannot be called without context \n";
571 // The message need to be added to the results so that the warning can be ordered
572 // and printed in a consistent way.
573 // the message won't appear in the .ts file
574 result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false, isWarningOnly: true));
575 } else
576 result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false));
577 break;
578
579 // handle translate and findMessage
580 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP:
581 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3:
582 plural = true;
583 Q_FALLTHROUGH();
584 case TrFunctionAliasManager::Function_translate:
585 case TrFunctionAliasManager::Function_findMessage:
586 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
587 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8:
588 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3:
589 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8:
590 if (!store.lupdateSourceWhenId.isEmpty()) {
591 std::stringstream warning;
592 warning << qPrintable(store.lupdateLocationFile) << ":"
593 << store.lupdateLocationLine << ":"
594 << store.locationCol << ": "
595 << "//% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n";
596 store.lupdateWarning.append(s: QString::fromStdString(s: warning.str()));
597 qCDebug(lcClang) << "//% is ignored when using translate function\n";
598 }
599 result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false));
600 break;
601
602 // handle qtTrId
603 case TrFunctionAliasManager::Function_QT_TRID_N_NOOP:
604 plural = true;
605 Q_FALLTHROUGH();
606 case TrFunctionAliasManager::Function_qtTrId:
607 case TrFunctionAliasManager::Function_QT_TRID_NOOP:
608 if (!store.lupdateIdMetaData.isEmpty()) {
609 std::stringstream warning;
610 warning << qPrintable(store.lupdateLocationFile) << ":"
611 << store.lupdateLocationLine << ":"
612 << store.locationCol << ": "
613 << "//= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n";
614 store.lupdateWarning.append(s: QString::fromStdString(s: warning.str()));
615 qCDebug(lcClang) << "//= is ignored when using qtTrId function \n";
616 }
617 result.push_back(x: translatorMessage(store, id: store.lupdateId, plural, isID: true));
618 break;
619 default:
620 if (store.funcName == QStringLiteral("TRANSLATOR"))
621 result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false));
622 }
623}
624
625static QString ensureCanonicalPath(const QString &filePath)
626{
627 QFileInfo fi(filePath);
628 if (fi.isRelative())
629 fi.setFile(QDir::current().absoluteFilePath(fileName: filePath));
630 return fi.canonicalFilePath();
631}
632
633TranslatorMessage ClangCppParser::translatorMessage(const TranslationRelatedStore &store,
634 const QString &id, bool plural, bool isId, bool isWarningOnly)
635{
636 if (isWarningOnly) {
637 TranslatorMessage msg;
638 // msg filled with file name and line number should be enough for the message ordering
639 msg.setFileName(ensureCanonicalPath(filePath: store.lupdateLocationFile));
640 msg.setLineNumber(store.lupdateLocationLine);
641 msg.setWarning(store.lupdateWarning);
642 msg.setWarningOnly(isWarningOnly);
643 return msg;
644 }
645
646 QString context;
647 if (!isId) {
648 context = ParserTool::transcode(str: store.contextArg.isEmpty() ? store.contextRetrieved
649 : store.contextArg);
650 }
651
652 TranslatorMessage msg(context,
653 ParserTool::transcode(str: isId ? store.lupdateSourceWhenId
654 : store.lupdateSource),
655 ParserTool::transcode(str: store.lupdateComment),
656 QString(),
657 ensureCanonicalPath(filePath: store.lupdateLocationFile),
658 store.lupdateLocationLine,
659 QStringList(),
660 TranslatorMessage::Type::Unfinished,
661 (plural ? plural : !store.lupdatePlural.isEmpty()));
662
663 if (!store.lupdateAllMagicMetaData.empty())
664 msg.setExtras(store.lupdateAllMagicMetaData);
665 msg.setExtraComment(ParserTool::transcode(str: store.lupdateExtraComment));
666 msg.setId(ParserTool::transcode(str: id));
667 if (!store.lupdateWarning.isEmpty())
668 msg.setWarning(store.lupdateWarning);
669 return msg;
670}
671
672#define START_THREADS(RSV, WSV) \
673 std::vector<std::thread> producers; \
674 const size_t idealProducerCount = std::min(RSV.size(), size_t(std::thread::hardware_concurrency())); \
675 \
676 for (size_t i = 0; i < idealProducerCount; ++i) { \
677 std::thread producer([&]() { \
678 TranslationRelatedStore store; \
679 while (RSV.next(&store)) { \
680 if (!store.contextArg.isEmpty()) { \
681 WSV.emplace_back(std::move(store)); \
682 continue; \
683 }
684
685#define JOIN_THREADS(WSV) \
686 WSV.emplace_back(std::move(store)); \
687 } \
688 }); \
689 producers.emplace_back(std::move(producer)); \
690 } \
691 \
692 for (auto &producer : producers) \
693 producer.join();
694
695void ClangCppParser::finalize(ReadSynchronizedRef<TranslationRelatedStore> &ast,
696 WriteSynchronizedRef<TranslationRelatedStore> &newAst)
697{
698 START_THREADS(ast, newAst)
699 JOIN_THREADS(newAst)
700}
701
702void ClangCppParser::correctAstTranslationContext(ReadSynchronizedRef<TranslationRelatedStore> &ast,
703 WriteSynchronizedRef<TranslationRelatedStore> &newAst, const TranslationStores &qDecl)
704{
705 START_THREADS(ast, newAst)
706
707 // If there is a Q_DECLARE_TR_FUNCTION the context given there takes
708 // priority over the retrieved context. The retrieved context for
709 // Q_DECLARE_TR_FUNCTION (where the macro was) has to fit the retrieved
710 // context of the tr function if there is already a argument giving the
711 // context, it has priority
712 for (const auto &declareStore : qDecl) {
713 qCDebug(lcClang) << "----------------------------";
714 qCDebug(lcClang) << "Tr call context retrieved " << store.contextRetrieved;
715 qCDebug(lcClang) << "Tr call source " << store.lupdateSource;
716 qCDebug(lcClang) << "- DECLARE context retrieved " << declareStore.contextRetrieved;
717 qCDebug(lcClang) << "- DECLARE context Arg " << declareStore.contextArg;
718 if (declareStore.contextRetrieved.isEmpty())
719 continue;
720 if (!declareStore.contextRetrieved.startsWith(s: store.contextRetrieved))
721 continue;
722 if (store.contextRetrieved == declareStore.contextRetrieved) {
723 qCDebug(lcClang) << "* Tr call context retrieved " << store.contextRetrieved;
724 qCDebug(lcClang) << "* Tr call source " << store.lupdateSource;
725 qCDebug(lcClang) << "* DECLARE context retrieved " << declareStore.contextRetrieved;
726 qCDebug(lcClang) << "* DECLARE context Arg " << declareStore.contextArg;
727 store.contextRetrieved = declareStore.contextArg;
728 // store.contextArg should never be overwritten.
729 break;
730 }
731 }
732
733 JOIN_THREADS(newAst)
734}
735
736#undef START_THREADS
737#undef JOIN_THREADS
738
739QT_END_NAMESPACE
740

source code of qttools/src/linguist/lupdate/cpp_clang.cpp