| 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 | #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 ("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 | |