File indexing completed on 2024-05-19 15:46:43
0001 /* 0002 SPDX-FileCopyrightText: 2013 Sven Brauch <svenbrauch@googlemail.com> 0003 SPDX-FileCopyrightText: 2014 Denis Steckelmacher <steckdenis@yahoo.fr> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "cache.h" 0009 #include "debug.h" 0010 0011 #include <QString> 0012 #include <QProcess> 0013 #include <QDir> 0014 #include <QStandardPaths> 0015 #include <QCryptographicHash> 0016 #include <QCoreApplication> 0017 0018 QmlJS::Cache::Cache() 0019 { 0020 // qmlplugindump from Qt4 and Qt5. They will be tried in order when dumping 0021 // a binary QML file. 0022 m_pluginDumpExecutables 0023 << PluginDumpExecutable(QStringLiteral("qmlplugindump"), QStringLiteral("1.0")) 0024 << PluginDumpExecutable(QStringLiteral("qmlplugindump-qt4"), QStringLiteral("1.0")) 0025 << PluginDumpExecutable(QStringLiteral("qmlplugindump-qt5"), QStringLiteral("2.0")) 0026 << PluginDumpExecutable(QStringLiteral("qml1plugindump-qt5"), QStringLiteral("1.0")); 0027 } 0028 0029 QmlJS::Cache& QmlJS::Cache::instance() 0030 { 0031 static Cache *c = nullptr; 0032 0033 if (!c) { 0034 c = new Cache(); 0035 } 0036 0037 return *c; 0038 } 0039 0040 KDevelop::Path::List QmlJS::Cache::libraryPaths(const KDevelop::IndexedString& baseFile) const 0041 { 0042 QMutexLocker lock(&m_mutex); 0043 return libraryPaths_internal(baseFile); 0044 } 0045 0046 KDevelop::Path::List QmlJS::Cache::libraryPaths_internal(const KDevelop::IndexedString& baseFile) const 0047 { 0048 Q_ASSERT(!m_mutex.try_lock()); 0049 0050 KDevelop::Path::List paths; 0051 0052 const auto& libraryPaths = QCoreApplication::instance()->libraryPaths(); 0053 for (auto& path : libraryPaths) { 0054 KDevelop::Path p(path); 0055 0056 // Change /path/to/qt5/plugins to /path/to/qt5/{qml,imports} 0057 paths << p.cd(QStringLiteral("../qml")); 0058 paths << p.cd(QStringLiteral("../imports")); 0059 } 0060 0061 paths << m_includeDirs[baseFile]; 0062 return paths; 0063 } 0064 0065 QString QmlJS::Cache::modulePath(const KDevelop::IndexedString& baseFile, const QString& uri, const QString& version) 0066 { 0067 QMutexLocker lock(&m_mutex); 0068 QString cacheKey = uri + version; 0069 QString path = m_modulePaths.value(cacheKey, QString()); 0070 0071 if (!path.isEmpty()) { 0072 return path; 0073 } 0074 0075 // Find the path for which <path>/u/r/i exists 0076 QString fragment = QString(uri).replace(QLatin1Char('.'), QDir::separator()); 0077 bool isVersion1 = version.startsWith(QLatin1String("1.")); 0078 bool isQtQuick = (uri == QLatin1String("QtQuick")); 0079 0080 const QStringList modulesWithoutVersionSuffix{ 0081 QStringLiteral("QtQml"), 0082 QStringLiteral("QtMultimedia"), 0083 QStringLiteral("QtQuick.LocalStorage"), 0084 QStringLiteral("QtQuick.XmlListModel"), 0085 }; 0086 0087 if (!isVersion1 && !fragment.isEmpty() && !fragment.endsWith(QLatin1Char('/')) && !version.isEmpty() 0088 && !modulesWithoutVersionSuffix.contains(uri)) { 0089 // Modules having a version greater or equal to 2 are stored in a directory 0090 // name like QtQuick.2 0091 fragment += QLatin1Char('.') + version.section(QLatin1Char('.'), 0, 0); 0092 } 0093 0094 const auto paths = libraryPaths_internal(baseFile); 0095 for (auto& p : paths) { 0096 QString pathString = p.cd(fragment).path(); 0097 0098 // HACK: QtQuick 1.0 is put in $LIB/qt5/imports/builtins.qmltypes. The "QtQuick" 0099 // identifier appears nowhere. 0100 if (isQtQuick && isVersion1) { 0101 if (QFile::exists(p.cd(QStringLiteral("builtins.qmltypes")).path())) { 0102 path = p.path(); 0103 break; 0104 } 0105 } else if (QFile::exists(pathString + QLatin1String("/plugins.qmltypes"))) { 0106 path = pathString; 0107 break; 0108 } 0109 } 0110 0111 m_modulePaths.insert(cacheKey, path); 0112 return path; 0113 } 0114 0115 QStringList QmlJS::Cache::getFileNames(const QFileInfoList& fileInfos) 0116 { 0117 QStringList result; 0118 0119 for (const QFileInfo& fileInfo : fileInfos) { 0120 QString filePath = fileInfo.canonicalFilePath(); 0121 0122 // If the module directory contains a plugins.qmltypes files, use it 0123 // and skip everything else 0124 if (filePath.endsWith(QLatin1String("plugins.qmltypes"))) { 0125 return QStringList() << filePath; 0126 } else if (fileInfo.dir().exists(QStringLiteral("plugins.qmltypes"))) { 0127 return {fileInfo.dir().filePath(QStringLiteral("plugins.qmltypes"))}; 0128 } 0129 0130 // Non-so files don't need any treatment 0131 if (!filePath.endsWith(QLatin1String(".so"))) { 0132 result.append(filePath); 0133 continue; 0134 } 0135 0136 // Use the cache to speed-up reparses 0137 { 0138 QMutexLocker lock(&m_mutex); 0139 0140 const auto modulePathIt = m_modulePaths.constFind(filePath); 0141 if (modulePathIt != m_modulePaths.constEnd()) { 0142 const QString& cachedFilePath = *modulePathIt; 0143 0144 if (!cachedFilePath.isEmpty()) { 0145 result.append(cachedFilePath); 0146 } 0147 0148 continue; 0149 } 0150 } 0151 0152 // Locate an existing dump of the file 0153 QString dumpFile = QStringLiteral("kdevqmljssupport/%1.qml").arg( 0154 QString::fromLatin1(QCryptographicHash::hash(filePath.toUtf8(), QCryptographicHash::Md5).toHex()) 0155 ); 0156 QString dumpPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0157 dumpFile 0158 ); 0159 0160 if (!dumpPath.isEmpty()) { 0161 QMutexLocker lock(&m_mutex); 0162 0163 result.append(dumpPath); 0164 m_modulePaths.insert(filePath, dumpPath); 0165 continue; 0166 } 0167 0168 // Create a dump of the file 0169 const QStringList args = {QStringLiteral("-noinstantiate"), QStringLiteral("-path"), filePath}; 0170 0171 for (const PluginDumpExecutable& executable : qAsConst(m_pluginDumpExecutables)) { 0172 QProcess qmlplugindump; 0173 qmlplugindump.setProcessChannelMode(QProcess::SeparateChannels); 0174 qmlplugindump.start(executable.executable, args, QIODevice::ReadOnly); 0175 0176 qCDebug(KDEV_QMLJS_DUCHAIN) << "starting qmlplugindump with args:" << executable.executable << args << qmlplugindump.state() << fileInfo.absolutePath(); 0177 0178 if (!qmlplugindump.waitForFinished(3000)) { 0179 if (qmlplugindump.state() == QProcess::Running) { 0180 qCWarning(KDEV_QMLJS_DUCHAIN) << "qmlplugindump didn't finish in time -- killing"; 0181 qmlplugindump.kill(); 0182 qmlplugindump.waitForFinished(100); 0183 } else { 0184 qCDebug(KDEV_QMLJS_DUCHAIN) << "qmlplugindump attempt failed" << qmlplugindump.program() << qmlplugindump.arguments() << qmlplugindump.readAllStandardError(); 0185 } 0186 continue; 0187 } 0188 0189 if (qmlplugindump.exitCode() != 0) { 0190 qCWarning(KDEV_QMLJS_DUCHAIN) << "qmlplugindump finished with exit code:" << qmlplugindump.exitCode(); 0191 continue; 0192 } 0193 0194 // Open a file in which the dump can be written 0195 QFile dumpFile( 0196 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) 0197 + dumpPath 0198 ); 0199 0200 if (dumpFile.open(QIODevice::WriteOnly)) { 0201 qmlplugindump.readLine(); // Skip "import QtQuick.tooling 1.1" 0202 0203 dumpFile.write("// " + filePath.toUtf8() + '\n'); 0204 dumpFile.write("import QtQuick " + executable.quickVersion.toUtf8() + '\n'); 0205 dumpFile.write(qmlplugindump.readAllStandardOutput()); 0206 dumpFile.close(); 0207 0208 result.append(dumpFile.fileName()); 0209 0210 QMutexLocker lock(&m_mutex); 0211 m_modulePaths.insert(filePath, dumpFile.fileName()); 0212 break; 0213 } 0214 } 0215 } 0216 0217 return result; 0218 } 0219 0220 void QmlJS::Cache::setFileCustomIncludes(const KDevelop::IndexedString& file, const KDevelop::Path::List& dirs) 0221 { 0222 QMutexLocker lock(&m_mutex); 0223 0224 m_includeDirs[file] = dirs; 0225 } 0226 0227 void QmlJS::Cache::addDependency(const KDevelop::IndexedString& file, const KDevelop::IndexedString& dependency) 0228 { 0229 QMutexLocker lock(&m_mutex); 0230 0231 m_dependees[dependency].insert(file); 0232 m_dependencies[file].insert(dependency); 0233 } 0234 0235 QList<KDevelop::IndexedString> QmlJS::Cache::filesThatDependOn(const KDevelop::IndexedString& file) 0236 { 0237 QMutexLocker lock(&m_mutex); 0238 0239 return m_dependees[file].values(); 0240 } 0241 0242 QList<KDevelop::IndexedString> QmlJS::Cache::dependencies(const KDevelop::IndexedString& file) 0243 { 0244 QMutexLocker lock(&m_mutex); 0245 0246 return m_dependencies[file].values(); 0247 } 0248 0249 bool QmlJS::Cache::isUpToDate(const KDevelop::IndexedString& file) 0250 { 0251 QMutexLocker lock(&m_mutex); 0252 0253 return m_isUpToDate.value(file, false); 0254 } 0255 0256 void QmlJS::Cache::setUpToDate(const KDevelop::IndexedString& file, bool upToDate) 0257 { 0258 QMutexLocker lock(&m_mutex); 0259 0260 m_isUpToDate[file] = upToDate; 0261 }