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 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 | |
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 | #if defined(Q_PROCESSOR_X86) || defined(Q_OS_MACOS) |
254 | // Turn off SSE support to avoid usage of gcc builtins. |
255 | // TODO: Look into what Qt Creator does. |
256 | // Pointers: HeaderPathFilter::removeGccInternalIncludePaths() |
257 | // and gccInstallDir() in gcctoolchain.cpp |
258 | // Also needed for Macs (and if libclang is built for X86) |
259 | // No need for CLANG_RESOURCE_DIR when this is part of the argument. |
260 | adjustedArgsTemp.push_back(x: "-mno-sse"); |
261 | #endif |
262 | |
263 | #ifdef Q_OS_WIN |
264 | adjustedArgsTemp.push_back("-fms-compatibility-version=19"); |
265 | adjustedArgsTemp.push_back("-DQ_COMPILER_UNIFORM_INIT"); // qtbase + clang-cl hack |
266 | // avoid constexpr error connected with offsetof (QTBUG-97380) |
267 | adjustedArgsTemp.push_back("-D_CRT_USE_BUILTIN_OFFSETOF"); |
268 | #endif |
269 | adjustedArgsTemp.push_back(x: "-Wno-everything"); |
270 | |
271 | for (const QByteArray &flag : compilerIncludeFlags) |
272 | adjustedArgsTemp.push_back(x: flag.data()); |
273 | |
274 | for (auto alias : aliasDefinition) { |
275 | adjustedArgsTemp.push_back(x: alias); |
276 | } |
277 | |
278 | clang::tooling::CommandLineArguments::iterator it = llvm::find(Range&: adjustedArgs, Val: "--"); |
279 | adjustedArgs.insert(position: it, first: adjustedArgsTemp.begin(), last: adjustedArgsTemp.end()); |
280 | return adjustedArgs; |
281 | }; |
282 | } |
283 | |
284 | bool ClangCppParser::stringContainsTranslationInformation(llvm::StringRef ba) |
285 | { |
286 | // pre-process the files by a simple text search if there is any occurrence |
287 | // of things we are interested in |
288 | constexpr llvm::StringLiteral qDeclareTrFunction("Q_DECLARE_TR_FUNCTIONS("); |
289 | constexpr llvm::StringLiteral qtTrNoop("QT_TR_NOOP("); |
290 | constexpr llvm::StringLiteral qtTrNoopUTF8("QT_TR_NOOP)UTF8("); |
291 | constexpr llvm::StringLiteral qtTrNNoop("QT_TR_N_NOOP("); |
292 | constexpr llvm::StringLiteral qtTrIdNoop("QT_TRID_NOOP("); |
293 | constexpr llvm::StringLiteral qtTrIdNNoop("QT_TRID_N_NOOP("); |
294 | constexpr llvm::StringLiteral qtTranslateNoop("QT_TRANSLATE_NOOP("); |
295 | constexpr llvm::StringLiteral qtTranslateNoopUTF8("QT_TRANSLATE_NOOP_UTF8("); |
296 | constexpr llvm::StringLiteral qtTranslateNNoop("QT_TRANSLATE_N_NOOP("); |
297 | constexpr llvm::StringLiteral qtTranslateNoop3("QT_TRANSLATE_NOOP3("); |
298 | constexpr llvm::StringLiteral qtTranslateNoop3UTF8("QT_TRANSLATE_NOOP3_UTF8("); |
299 | constexpr llvm::StringLiteral qtTranslateNNoop3("QT_TRANSLATE_N_NOOP3("); |
300 | constexpr llvm::StringLiteral translatorComment("TRANSLATOR "); |
301 | constexpr llvm::StringLiteral qtTrId("qtTrId("); |
302 | constexpr llvm::StringLiteral tr("tr("); |
303 | constexpr llvm::StringLiteral trUtf8("trUtf8("); |
304 | constexpr llvm::StringLiteral translate("translate("); |
305 | |
306 | const size_t pos = ba.find_first_of(Chars: "QT_TR"); |
307 | const auto baSliced = ba.slice(Start: pos, End: llvm::StringRef::npos); |
308 | if (pos != llvm::StringRef::npos) { |
309 | if (baSliced.contains(Other: qtTrNoop) || baSliced.contains(Other: qtTrNoopUTF8) || baSliced.contains(Other: qtTrNNoop) |
310 | || baSliced.contains(Other: qtTrIdNoop) || baSliced.contains(Other: qtTrIdNNoop) |
311 | || baSliced.contains(Other: qtTranslateNoop) || baSliced.contains(Other: qtTranslateNoopUTF8) |
312 | || baSliced.contains(Other: qtTranslateNNoop) || baSliced.contains(Other: qtTranslateNoop3) |
313 | || baSliced.contains(Other: qtTranslateNoop3UTF8) || baSliced.contains(Other: qtTranslateNNoop3)) |
314 | return true; |
315 | } |
316 | |
317 | if (ba.contains(Other: qDeclareTrFunction) || ba.contains(Other: translatorComment) || ba.contains(Other: qtTrId) || ba.contains(Other: tr) |
318 | || ba.contains(Other: trUtf8) || ba.contains(Other: translate)) |
319 | return true; |
320 | |
321 | for (QString alias : trFunctionAliasManager.listAliases()) { |
322 | if (ba.contains(qPrintable(alias))) |
323 | return true; |
324 | } |
325 | |
326 | |
327 | return false; |
328 | } |
329 | |
330 | static bool generateCompilationDatabase(const QString &outputFilePath, const ConversionData &cd) |
331 | { |
332 | QJsonArray commandObjects; |
333 | const QString buildDir = QDir::currentPath(); |
334 | const QString fakefileName = QLatin1String("dummmy.cpp"); |
335 | QJsonObject obj; |
336 | obj[QLatin1String("file")] = fakefileName; |
337 | obj[QLatin1String("directory")] = buildDir; |
338 | QJsonArray args = { |
339 | QLatin1String("clang++"), |
340 | QLatin1String("-std=gnu++17"), |
341 | #ifndef Q_OS_WIN |
342 | QLatin1String("-fPIC"), |
343 | #endif |
344 | }; |
345 | |
346 | #if defined(Q_OS_MACOS) && QT_CONFIG(framework) |
347 | const QString installPath = QLibraryInfo::path(QLibraryInfo::LibrariesPath); |
348 | QString arg = QLatin1String("-F") + installPath; |
349 | args.push_back(arg); |
350 | #endif |
351 | |
352 | for (const QString &path : cd.m_includePath) { |
353 | QString arg = QLatin1String("-I") + path; |
354 | args.push_back(t: std::move(arg)); |
355 | } |
356 | |
357 | obj[QLatin1String("arguments")] = args; |
358 | commandObjects.append(value: obj); |
359 | |
360 | QJsonDocument doc(commandObjects); |
361 | QFile file(outputFilePath); |
362 | if (!file.open(flags: QIODevice::WriteOnly)) |
363 | return false; |
364 | file.write(data: doc.toJson()); |
365 | return true; |
366 | } |
367 | |
368 | // Sort messages in such a way that they appear in the same order like in the given file list. |
369 | static void sortMessagesByFileOrder(ClangCppParser::TranslatorMessageVector &messages, |
370 | const QStringList &files) |
371 | { |
372 | // first sort messages by line number |
373 | std::stable_sort(first: messages.begin(), last: messages.end(), |
374 | comp: [&](const TranslatorMessage &lhs, const TranslatorMessage &rhs) { |
375 | auto i = lhs.lineNumber(); |
376 | auto k = rhs.lineNumber(); |
377 | return i < k; |
378 | }); |
379 | |
380 | QHash<QString, QStringList::size_type> indexByPath; |
381 | for (const TranslatorMessage &m : messages) |
382 | indexByPath[m.fileName()] = std::numeric_limits<QStringList::size_type>::max(); |
383 | |
384 | for (QStringList::size_type i = 0; i < files.size(); ++i) |
385 | indexByPath[files[i]] = i; |
386 | |
387 | std::stable_sort(first: messages.begin(), last: messages.end(), |
388 | comp: [&](const TranslatorMessage &lhs, const TranslatorMessage &rhs) { |
389 | auto i = indexByPath.value(key: lhs.fileName()); |
390 | auto k = indexByPath.value(key: rhs.fileName()); |
391 | return i < k; |
392 | }); |
393 | } |
394 | |
395 | bool ClangCppParser::hasAliases() |
396 | { |
397 | QStringList listAlias = trFunctionAliasManager.listAliases(); |
398 | if (listAlias.size() > 0) |
399 | return true; |
400 | return false; |
401 | } |
402 | |
403 | void ClangCppParser::loadCPP(Translator &translator, const QStringList &files, ConversionData &cd, |
404 | bool *fail) |
405 | { |
406 | FileSignificanceCheck::create(); |
407 | auto cleanup = qScopeGuard(f&: FileSignificanceCheck::destroy); |
408 | FileSignificanceCheck::the()->setExclusionRegExes(cd.m_excludes); |
409 | if (cd.m_rootDirs.size() > 0) |
410 | FileSignificanceCheck::the()->setRootDirectories(cd.m_rootDirs); |
411 | else |
412 | FileSignificanceCheck::the()->setRootDirectories(getProjectDirsFromEnvironment()); |
413 | |
414 | if (hasAliases()) |
415 | aliasDefinition = getAliasFunctionDefinition(); |
416 | |
417 | // pre-process the files by a simple text search if there is any occurrence |
418 | // of things we are interested in |
419 | qCDebug(lcClang) << "Load CPP \n"; |
420 | std::vector<std::string> sources; |
421 | for (const QString &filename : files) { |
422 | qCDebug(lcClang) << "File: "<< filename << " \n"; |
423 | sources.emplace_back(args: filename.toStdString()); |
424 | } |
425 | |
426 | std::string errorMessage; |
427 | std::unique_ptr<CompilationDatabase> db; |
428 | if (cd.m_compilationDatabaseDir.isEmpty()) { |
429 | db = CompilationDatabase::autoDetectFromDirectory(SourceDir: ".", ErrorMessage&: errorMessage); |
430 | if (!db && !files.isEmpty()) { |
431 | db = CompilationDatabase::autoDetectFromSource(SourceFile: files.first().toStdString(), |
432 | ErrorMessage&: errorMessage); |
433 | } |
434 | } else { |
435 | db = CompilationDatabase::autoDetectFromDirectory(SourceDir: cd.m_compilationDatabaseDir.toStdString(), |
436 | ErrorMessage&: errorMessage); |
437 | } |
438 | |
439 | if (!db) { |
440 | const QString dbFilePath = QStringLiteral("compile_commands.json"); |
441 | qCDebug(lcClang) << "Generating compilation database"<< dbFilePath; |
442 | if (!generateCompilationDatabase(outputFilePath: dbFilePath, cd)) { |
443 | *fail = true; |
444 | cd.appendError(error: u"Cannot generate compilation database."_s); |
445 | return; |
446 | } |
447 | errorMessage.clear(); |
448 | db = CompilationDatabase::loadFromDirectory(BuildDirectory: ".", ErrorMessage&: errorMessage); |
449 | } |
450 | |
451 | if (!db) { |
452 | *fail = true; |
453 | cd.appendError(error: QString::fromStdString(s: errorMessage)); |
454 | return; |
455 | } |
456 | |
457 | TranslationStores ast, qdecl, qnoop; |
458 | Stores stores(ast, qdecl, qnoop); |
459 | |
460 | std::vector<std::thread> producers; |
461 | ReadSynchronizedRef<std::string> ppSources(sources); |
462 | WriteSynchronizedRef<TranslationRelatedStore> ppStore(stores.Preprocessor); |
463 | size_t idealProducerCount = std::min(a: ppSources.size(), b: size_t(std::thread::hardware_concurrency())); |
464 | clang::tooling::ArgumentsAdjuster argumentsAdjusterSyntaxOnly = |
465 | clang::tooling::getClangSyntaxOnlyAdjuster(); |
466 | clang::tooling::ArgumentsAdjuster argumentsAdjusterLocal = getClangArgumentAdjuster(); |
467 | clang::tooling::ArgumentsAdjuster argumentsAdjuster = |
468 | clang::tooling::combineAdjusters(First: argumentsAdjusterLocal, Second: argumentsAdjusterSyntaxOnly); |
469 | |
470 | for (size_t i = 0; i < idealProducerCount; ++i) { |
471 | std::thread producer([&ppSources, &db, &ppStore, &argumentsAdjuster]() { |
472 | std::string file; |
473 | while (ppSources.next(value: &file)) { |
474 | clang::tooling::ClangTool tool(*db, file); |
475 | tool.appendArgumentsAdjuster(Adjuster: argumentsAdjuster); |
476 | tool.run(Action: new LupdatePreprocessorActionFactory(&ppStore)); |
477 | } |
478 | }); |
479 | producers.emplace_back(args: std::move(producer)); |
480 | } |
481 | for (auto &producer : producers) |
482 | producer.join(); |
483 | producers.clear(); |
484 | |
485 | ReadSynchronizedRef<std::string> astSources(sources); |
486 | idealProducerCount = std::min(a: astSources.size(), b: size_t(std::thread::hardware_concurrency())); |
487 | for (size_t i = 0; i < idealProducerCount; ++i) { |
488 | std::thread producer([&astSources, &db, &stores, &argumentsAdjuster]() { |
489 | std::string file; |
490 | while (astSources.next(value: &file)) { |
491 | clang::tooling::ClangTool tool(*db, file); |
492 | tool.appendArgumentsAdjuster(Adjuster: argumentsAdjuster); |
493 | tool.run(Action: new LupdateToolActionFactory(&stores)); |
494 | } |
495 | }); |
496 | producers.emplace_back(args: std::move(producer)); |
497 | } |
498 | for (auto &producer : producers) |
499 | producer.join(); |
500 | producers.clear(); |
501 | |
502 | TranslationStores finalStores; |
503 | WriteSynchronizedRef<TranslationRelatedStore> wsv(finalStores); |
504 | |
505 | ReadSynchronizedRef<TranslationRelatedStore> rsv(ast); |
506 | ClangCppParser::correctAstTranslationContext(ast&: rsv, newAst&: wsv, qDecl: qdecl); |
507 | |
508 | ReadSynchronizedRef<TranslationRelatedStore> rsvQNoop(qnoop); |
509 | //unlike ast translation context, qnoop context don't need to be corrected |
510 | //(because Q_DECLARE_TR_FUNCTION context is already applied). |
511 | ClangCppParser::finalize(ast&: rsvQNoop, newAst&: wsv); |
512 | |
513 | TranslatorMessageVector messages; |
514 | for (auto &store : finalStores) |
515 | ClangCppParser::collectMessages(result&: messages, store); |
516 | |
517 | sortMessagesByFileOrder(messages, files); |
518 | |
519 | for (TranslatorMessage &msg : messages) { |
520 | if (!msg.warning().isEmpty()) { |
521 | std::cerr << qPrintable(msg.warning()); |
522 | if (msg.warningOnly() == true) |
523 | continue; |
524 | } |
525 | translator.extend(msg: std::move(msg), cd); |
526 | } |
527 | } |
528 | |
529 | void ClangCppParser::collectMessages(TranslatorMessageVector &result, |
530 | TranslationRelatedStore &store) |
531 | { |
532 | if (!store.isValid(printwarning: true)) { |
533 | if (store.lupdateWarning.isEmpty()) |
534 | return; |
535 | // The message needs to be added to the results so that the warning can be ordered |
536 | // and printed in a consistent way. |
537 | // the message won't appear in the .ts file |
538 | result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural: false, isID: false, isWarningOnly: true)); |
539 | return; |
540 | } |
541 | |
542 | qCDebug(lcClang) << "---------------------------------------------------------------Filling translator for "<< store.funcName; |
543 | qCDebug(lcClang) << " contextRetrieved "<< store.contextRetrieved; |
544 | qCDebug(lcClang) << " source "<< store.lupdateSource; |
545 | |
546 | bool plural = false; |
547 | switch (trFunctionAliasManager.trFunctionByName(trFunctionName: store.funcName)) { |
548 | // handle tr |
549 | case TrFunctionAliasManager::Function_QT_TR_N_NOOP: |
550 | plural = true; |
551 | Q_FALLTHROUGH(); |
552 | case TrFunctionAliasManager::Function_tr: |
553 | case TrFunctionAliasManager::Function_trUtf8: |
554 | case TrFunctionAliasManager::Function_QT_TR_NOOP: |
555 | case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8: |
556 | if (!store.lupdateSourceWhenId.isEmpty()) { |
557 | std::stringstream warning; |
558 | warning << qPrintable(store.lupdateLocationFile) << ":" |
559 | << store.lupdateLocationLine << ":" |
560 | << store.locationCol << ": " |
561 | << "//% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n"; |
562 | store.lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
563 | qCDebug(lcClang) << "//% is ignored when using tr function\n"; |
564 | } |
565 | if (store.contextRetrieved.isEmpty() && store.contextArg.isEmpty()) { |
566 | std::stringstream warning; |
567 | warning << qPrintable(store.lupdateLocationFile) << ":" |
568 | << store.lupdateLocationLine << ":" |
569 | << store.locationCol << ": " |
570 | << qPrintable(store.funcName) << " cannot be called without context." |
571 | << " The call is ignored (missing Q_OBJECT maybe?)\n"; |
572 | store.lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
573 | qCDebug(lcClang) << "tr() cannot be called without context \n"; |
574 | // The message need to be added to the results so that the warning can be ordered |
575 | // and printed in a consistent way. |
576 | // the message won't appear in the .ts file |
577 | result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false, isWarningOnly: true)); |
578 | } else |
579 | result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false)); |
580 | break; |
581 | |
582 | // handle translate and findMessage |
583 | case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: |
584 | case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: |
585 | plural = true; |
586 | Q_FALLTHROUGH(); |
587 | case TrFunctionAliasManager::Function_translate: |
588 | case TrFunctionAliasManager::Function_findMessage: |
589 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: |
590 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: |
591 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: |
592 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: |
593 | if (!store.lupdateSourceWhenId.isEmpty()) { |
594 | std::stringstream warning; |
595 | warning << qPrintable(store.lupdateLocationFile) << ":" |
596 | << store.lupdateLocationLine << ":" |
597 | << store.locationCol << ": " |
598 | << "//% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n"; |
599 | store.lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
600 | qCDebug(lcClang) << "//% is ignored when using translate function\n"; |
601 | } |
602 | result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false)); |
603 | break; |
604 | |
605 | // handle qtTrId |
606 | case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: |
607 | plural = true; |
608 | Q_FALLTHROUGH(); |
609 | case TrFunctionAliasManager::Function_qtTrId: |
610 | case TrFunctionAliasManager::Function_QT_TRID_NOOP: |
611 | if (!store.lupdateIdMetaData.isEmpty()) { |
612 | std::stringstream warning; |
613 | warning << qPrintable(store.lupdateLocationFile) << ":" |
614 | << store.lupdateLocationLine << ":" |
615 | << store.locationCol << ": " |
616 | << "//= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n"; |
617 | store.lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
618 | qCDebug(lcClang) << "//= is ignored when using qtTrId function \n"; |
619 | } |
620 | result.push_back(x: translatorMessage(store, id: store.lupdateId, plural, isID: true)); |
621 | break; |
622 | default: |
623 | if (store.funcName == QStringLiteral("TRANSLATOR")) |
624 | result.push_back(x: translatorMessage(store, id: store.lupdateIdMetaData, plural, isID: false)); |
625 | } |
626 | } |
627 | |
628 | static QString ensureCanonicalPath(const QString &filePath) |
629 | { |
630 | QFileInfo fi(filePath); |
631 | if (fi.isRelative()) |
632 | fi.setFile(QDir::current().absoluteFilePath(fileName: filePath)); |
633 | return fi.canonicalFilePath(); |
634 | } |
635 | |
636 | TranslatorMessage ClangCppParser::translatorMessage(const TranslationRelatedStore &store, |
637 | const QString &id, bool plural, bool isId, bool isWarningOnly) |
638 | { |
639 | if (isWarningOnly) { |
640 | TranslatorMessage msg; |
641 | // msg filled with file name and line number should be enough for the message ordering |
642 | msg.setFileName(ensureCanonicalPath(filePath: store.lupdateLocationFile)); |
643 | msg.setLineNumber(store.lupdateLocationLine); |
644 | msg.setWarning(store.lupdateWarning); |
645 | msg.setWarningOnly(isWarningOnly); |
646 | return msg; |
647 | } |
648 | |
649 | QString context; |
650 | if (!isId) { |
651 | context = ParserTool::transcode(str: store.contextArg.isEmpty() ? store.contextRetrieved |
652 | : store.contextArg); |
653 | } |
654 | |
655 | TranslatorMessage msg(context, |
656 | ParserTool::transcode(str: isId ? store.lupdateSourceWhenId |
657 | : store.lupdateSource), |
658 | ParserTool::transcode(str: store.lupdateComment), |
659 | QString(), |
660 | ensureCanonicalPath(filePath: store.lupdateLocationFile), |
661 | store.lupdateLocationLine, |
662 | QStringList(), |
663 | TranslatorMessage::Type::Unfinished, |
664 | (plural ? plural : !store.lupdatePlural.isEmpty())); |
665 | |
666 | if (!store.lupdateAllMagicMetaData.empty()) |
667 | msg.setExtras(store.lupdateAllMagicMetaData); |
668 | msg.setExtraComment(ParserTool::transcode(str: store.lupdateExtraComment)); |
669 | msg.setId(ParserTool::transcode(str: id)); |
670 | if (!store.lupdateWarning.isEmpty()) |
671 | msg.setWarning(store.lupdateWarning); |
672 | return msg; |
673 | } |
674 | |
675 | #define START_THREADS(RSV, WSV) \ |
676 | std::vector<std::thread> producers; \ |
677 | const size_t idealProducerCount = std::min(RSV.size(), size_t(std::thread::hardware_concurrency())); \ |
678 | \ |
679 | for (size_t i = 0; i < idealProducerCount; ++i) { \ |
680 | std::thread producer([&]() { \ |
681 | TranslationRelatedStore store; \ |
682 | while (RSV.next(&store)) { \ |
683 | if (!store.contextArg.isEmpty()) { \ |
684 | WSV.emplace_back(std::move(store)); \ |
685 | continue; \ |
686 | } |
687 | |
688 | #define JOIN_THREADS(WSV) \ |
689 | WSV.emplace_back(std::move(store)); \ |
690 | } \ |
691 | }); \ |
692 | producers.emplace_back(std::move(producer)); \ |
693 | } \ |
694 | \ |
695 | for (auto &producer : producers) \ |
696 | producer.join(); |
697 | |
698 | void ClangCppParser::finalize(ReadSynchronizedRef<TranslationRelatedStore> &ast, |
699 | WriteSynchronizedRef<TranslationRelatedStore> &newAst) |
700 | { |
701 | START_THREADS(ast, newAst) |
702 | JOIN_THREADS(newAst) |
703 | } |
704 | |
705 | void ClangCppParser::correctAstTranslationContext(ReadSynchronizedRef<TranslationRelatedStore> &ast, |
706 | WriteSynchronizedRef<TranslationRelatedStore> &newAst, const TranslationStores &qDecl) |
707 | { |
708 | START_THREADS(ast, newAst) |
709 | |
710 | // If there is a Q_DECLARE_TR_FUNCTION the context given there takes |
711 | // priority over the retrieved context. The retrieved context for |
712 | // Q_DECLARE_TR_FUNCTION (where the macro was) has to fit the retrieved |
713 | // context of the tr function if there is already a argument giving the |
714 | // context, it has priority |
715 | for (const auto &declareStore : qDecl) { |
716 | qCDebug(lcClang) << "----------------------------"; |
717 | qCDebug(lcClang) << "Tr call context retrieved "<< store.contextRetrieved; |
718 | qCDebug(lcClang) << "Tr call source "<< store.lupdateSource; |
719 | qCDebug(lcClang) << "- DECLARE context retrieved "<< declareStore.contextRetrieved; |
720 | qCDebug(lcClang) << "- DECLARE context Arg "<< declareStore.contextArg; |
721 | if (declareStore.contextRetrieved.isEmpty()) |
722 | continue; |
723 | if (!declareStore.contextRetrieved.startsWith(s: store.contextRetrieved)) |
724 | continue; |
725 | if (store.contextRetrieved == declareStore.contextRetrieved) { |
726 | qCDebug(lcClang) << "* Tr call context retrieved "<< store.contextRetrieved; |
727 | qCDebug(lcClang) << "* Tr call source "<< store.lupdateSource; |
728 | qCDebug(lcClang) << "* DECLARE context retrieved "<< declareStore.contextRetrieved; |
729 | qCDebug(lcClang) << "* DECLARE context Arg "<< declareStore.contextArg; |
730 | store.contextRetrieved = declareStore.contextArg; |
731 | // store.contextArg should never be overwritten. |
732 | break; |
733 | } |
734 | } |
735 | |
736 | JOIN_THREADS(newAst) |
737 | } |
738 | |
739 | #undef START_THREADS |
740 | #undef JOIN_THREADS |
741 | |
742 | QT_END_NAMESPACE |
743 |
Definitions
- lcClang
- getSysCompiler
- getMSVCIncludePathsFromEnvironment
- getProjectDirsFromEnvironment
- frameworkSuffix
- getIncludePathsFromCompiler
- getAliasFunctionDefinition
- aliasDefinition
- getClangArgumentAdjuster
- stringContainsTranslationInformation
- generateCompilationDatabase
- sortMessagesByFileOrder
- hasAliases
- loadCPP
- collectMessages
- ensureCanonicalPath
- translatorMessage
- finalize
Learn Advanced QML with KDAB
Find out more