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 | |
36 | using clang::tooling::CompilationDatabase; |
37 | |
38 | QT_BEGIN_NAMESPACE |
39 | |
40 | using namespace Qt::StringLiterals; |
41 | |
42 | Q_LOGGING_CATEGORY(lcClang, "qt.lupdate.clang" ); |
43 | |
44 | static 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 | |
68 | static 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 | |
81 | static 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 | |
97 | static QByteArray frameworkSuffix() |
98 | { |
99 | return QByteArrayLiteral(" (framework directory)" ); |
100 | } |
101 | |
102 | QByteArrayList 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 {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 | |
165 | std::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 | |
241 | static std::vector<std::string> aliasDefinition; |
242 | |
243 | static 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 | |
281 | bool 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 ("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 | |
327 | static 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. |
366 | static 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 | |
392 | bool ClangCppParser::hasAliases() |
393 | { |
394 | QStringList listAlias = trFunctionAliasManager.listAliases(); |
395 | if (listAlias.size() > 0) |
396 | return true; |
397 | return false; |
398 | } |
399 | |
400 | void 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 | |
526 | void 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 | |
625 | static 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 | |
633 | TranslatorMessage 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 | |
695 | void ClangCppParser::finalize(ReadSynchronizedRef<TranslationRelatedStore> &ast, |
696 | WriteSynchronizedRef<TranslationRelatedStore> &newAst) |
697 | { |
698 | START_THREADS(ast, newAst) |
699 | JOIN_THREADS(newAst) |
700 | } |
701 | |
702 | void 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 | |
739 | QT_END_NAMESPACE |
740 | |