1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "qmljsplugindumper.h"
35 #include "qmljsmodelmanager.h"
37 #include <qmljs/qmljsdocument.h>
38 #include <qmljs/qmljsinterpreter.h>
39 #include <projectexplorer/filewatcher.h>
40 #include <projectexplorer/projectexplorer.h>
41 #include <coreplugin/messagemanager.h>
43 #include <QtCore/QDir>
45 using namespace LanguageUtils;
46 using namespace QmlJS;
47 using namespace QmlJSTools;
48 using namespace QmlJSTools::Internal;
50 PluginDumper::PluginDumper(ModelManager *modelManager)
51 : QObject(modelManager)
52 , m_modelManager(modelManager)
53 , m_pluginWatcher(new ProjectExplorer::FileWatcher(this))
55 connect(m_pluginWatcher, SIGNAL(fileChanged(QString)), SLOT(pluginChanged(QString)));
58 void PluginDumper::loadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion)
60 // move to the owning thread
61 metaObject()->invokeMethod(this, "onLoadPluginTypes",
62 Q_ARG(QString, libraryPath),
63 Q_ARG(QString, importPath),
64 Q_ARG(QString, importUri),
65 Q_ARG(QString, importVersion));
68 void PluginDumper::onLoadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion)
70 const QString canonicalLibraryPath = QDir::cleanPath(libraryPath);
71 if (m_runningQmldumps.values().contains(canonicalLibraryPath))
73 const Snapshot snapshot = m_modelManager->snapshot();
74 const LibraryInfo libraryInfo = snapshot.libraryInfo(canonicalLibraryPath);
75 if (libraryInfo.dumpStatus() != LibraryInfo::DumpNotStartedOrRunning)
78 // avoid inserting the same plugin twice
80 for (index = 0; index < m_plugins.size(); ++index) {
81 if (m_plugins.at(index).qmldirPath == libraryPath)
84 if (index == m_plugins.size())
85 m_plugins.append(Plugin());
87 Plugin &plugin = m_plugins[index];
88 plugin.qmldirPath = canonicalLibraryPath;
89 plugin.importPath = importPath;
90 plugin.importUri = importUri;
91 plugin.importVersion = importVersion;
93 // watch plugin libraries
94 foreach (const QmlDirParser::Plugin &plugin, snapshot.libraryInfo(canonicalLibraryPath).plugins()) {
95 const QString pluginLibrary = resolvePlugin(canonicalLibraryPath, plugin.path, plugin.name);
96 m_pluginWatcher->addFile(pluginLibrary);
97 m_libraryToPluginIndex.insert(pluginLibrary, index);
100 // watch library xml file
101 if (plugin.hasPredumpedQmlTypesFile()) {
102 const QString &path = plugin.predumpedQmlTypesFilePath();
103 m_pluginWatcher->addFile(path);
104 m_libraryToPluginIndex.insert(path, index);
110 void PluginDumper::scheduleCompleteRedump()
112 metaObject()->invokeMethod(this, "dumpAllPlugins", Qt::QueuedConnection);
115 void PluginDumper::dumpAllPlugins()
117 foreach (const Plugin &plugin, m_plugins)
121 static QString qmldumpErrorMessage(const QString &libraryPath, const QString &error)
123 return PluginDumper::tr("Type dump of QML plugin in %1 failed.\nErrors:\n%2\n").
124 arg(libraryPath, error);
127 static QString qmldumpFailedMessage(const QString &error)
130 QStringList(error.split(QLatin1Char('\n')).mid(0, 10)).join(QLatin1String("\n"));
131 return PluginDumper::tr("Type dump of C++ plugin failed.\n"
132 "First 10 lines or errors:\n"
136 "Check 'General Messages' output pane for details."
140 static QList<FakeMetaObject::ConstPtr> parseHelper(const QByteArray &qmlTypeDescriptions, QString *error)
142 QList<FakeMetaObject::ConstPtr> ret;
143 QHash<QString, FakeMetaObject::ConstPtr> newObjects;
144 *error = Interpreter::CppQmlTypesLoader::parseQmlTypeDescriptions(qmlTypeDescriptions, &newObjects);
146 if (error->isEmpty()) {
147 ret = newObjects.values();
152 void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
154 QProcess *process = qobject_cast<QProcess *>(sender());
157 process->deleteLater();
159 const QString libraryPath = m_runningQmldumps.take(process);
160 const Snapshot snapshot = m_modelManager->snapshot();
161 LibraryInfo libraryInfo = snapshot.libraryInfo(libraryPath);
164 Core::MessageManager *messageManager = Core::MessageManager::instance();
165 const QString errorMessages = process->readAllStandardError();
166 messageManager->printToOutputPane(qmldumpErrorMessage(libraryPath, errorMessages));
167 libraryInfo.setDumpStatus(LibraryInfo::DumpError, qmldumpFailedMessage(errorMessages));
170 const QByteArray output = process->readAllStandardOutput();
172 QList<FakeMetaObject::ConstPtr> objectsList = parseHelper(output, &error);
173 if (exitCode == 0 && !error.isEmpty()) {
174 libraryInfo.setDumpStatus(LibraryInfo::DumpError, tr("Type dump of C++ plugin failed. Parse error:\n'%1'").arg(error));
177 if (exitCode == 0 && error.isEmpty()) {
178 libraryInfo.setMetaObjects(objectsList);
179 // ### disabled code path for running qmldump to get Qt's builtins
180 // if (libraryPath.isEmpty())
181 // Interpreter::CppQmlTypesLoader::builtinObjects.append(objectsList);
182 libraryInfo.setDumpStatus(LibraryInfo::DumpDone);
185 if (!libraryPath.isEmpty())
186 m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
189 void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError)
191 QProcess *process = qobject_cast<QProcess *>(sender());
194 process->deleteLater();
196 const QString libraryPath = m_runningQmldumps.take(process);
198 Core::MessageManager *messageManager = Core::MessageManager::instance();
199 const QString errorMessages = process->readAllStandardError();
200 messageManager->printToOutputPane(qmldumpErrorMessage(libraryPath, errorMessages));
202 if (!libraryPath.isEmpty()) {
203 const Snapshot snapshot = m_modelManager->snapshot();
204 LibraryInfo libraryInfo = snapshot.libraryInfo(libraryPath);
205 libraryInfo.setDumpStatus(LibraryInfo::DumpError, qmldumpFailedMessage(errorMessages));
206 m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
210 void PluginDumper::pluginChanged(const QString &pluginLibrary)
212 const int pluginIndex = m_libraryToPluginIndex.value(pluginLibrary, -1);
213 if (pluginIndex == -1)
216 const Plugin &plugin = m_plugins.at(pluginIndex);
220 void PluginDumper::dump(const Plugin &plugin)
222 if (plugin.hasPredumpedQmlTypesFile()) {
223 const Snapshot snapshot = m_modelManager->snapshot();
224 LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath);
225 if (!libraryInfo.isValid())
228 const QString &path = plugin.predumpedQmlTypesFilePath();
229 QFile libraryQmlTypesFile(path);
230 if (!libraryQmlTypesFile.open(QFile::ReadOnly | QFile::Text)) {
231 libraryInfo.setDumpStatus(LibraryInfo::DumpError,
232 tr("Could not open file '%1' for reading.").arg(path));
233 m_modelManager->updateLibraryInfo(plugin.qmldirPath, libraryInfo);
237 const QByteArray qmlTypeDescriptions = libraryQmlTypesFile.readAll();
238 libraryQmlTypesFile.close();
241 const QList<FakeMetaObject::ConstPtr> objectsList = parseHelper(qmlTypeDescriptions, &error);
243 if (error.isEmpty()) {
244 libraryInfo.setMetaObjects(objectsList);
245 libraryInfo.setDumpStatus(LibraryInfo::DumpDone);
247 libraryInfo.setDumpStatus(LibraryInfo::DumpError,
248 tr("Failed to parse '%1'.\nError: %2").arg(path, error));
250 m_modelManager->updateLibraryInfo(plugin.qmldirPath, libraryInfo);
254 ProjectExplorer::Project *activeProject = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
258 ModelManagerInterface::ProjectInfo info = m_modelManager->projectInfo(activeProject);
260 if (info.qmlDumpPath.isEmpty()) {
261 const Snapshot snapshot = m_modelManager->snapshot();
262 LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath);
263 if (!libraryInfo.isValid())
266 libraryInfo.setDumpStatus(LibraryInfo::DumpError,
267 tr("Could not locate the helper application for dumping type information from C++ plugins.\n"
268 "Please build the debugging helpers on the Qt version options page."));
269 m_modelManager->updateLibraryInfo(plugin.qmldirPath, libraryInfo);
273 QProcess *process = new QProcess(this);
274 process->setEnvironment(info.qmlDumpEnvironment.toStringList());
275 connect(process, SIGNAL(finished(int)), SLOT(qmlPluginTypeDumpDone(int)));
276 connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(qmlPluginTypeDumpError(QProcess::ProcessError)));
278 args << QLatin1String("--notrelocatable");
279 if (plugin.importUri.isEmpty()) {
280 args << QLatin1String("--path");
281 args << plugin.importPath;
282 if (ComponentVersion(plugin.importVersion).isValid())
283 args << plugin.importVersion;
285 args << plugin.importUri;
286 args << plugin.importVersion;
287 args << plugin.importPath;
289 process->start(info.qmlDumpPath, args);
290 m_runningQmldumps.insert(process, plugin.qmldirPath);
294 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
295 The \a prefix must contain the dot.
297 \a qmldirPath is the location of the qmldir file.
299 Adapted from QDeclarativeImportDatabase::resolvePlugin.
301 QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
302 const QString &baseName, const QStringList &suffixes,
303 const QString &prefix)
305 QStringList searchPaths;
306 searchPaths.append(QLatin1String("."));
308 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
309 if (!qmldirPluginPathIsRelative)
310 searchPaths.prepend(qmldirPluginPath);
312 foreach (const QString &pluginPath, searchPaths) {
314 QString resolvedPath;
316 if (pluginPath == QLatin1String(".")) {
317 if (qmldirPluginPathIsRelative)
318 resolvedPath = qmldirPath.absoluteFilePath(qmldirPluginPath);
320 resolvedPath = qmldirPath.absolutePath();
322 resolvedPath = pluginPath;
325 QDir dir(resolvedPath);
326 foreach (const QString &suffix, suffixes) {
327 QString pluginFileName = prefix;
329 pluginFileName += baseName;
330 pluginFileName += suffix;
332 QFileInfo fileInfo(dir, pluginFileName);
334 if (fileInfo.exists())
335 return fileInfo.absoluteFilePath();
343 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
345 Adapted from QDeclarativeImportDatabase::resolvePlugin.
348 \header \i Platform \i Valid suffixes
349 \row \i Windows \i \c .dll
350 \row \i Unix/Linux \i \c .so
352 \row \i HP-UX \i \c .sl, \c .so (HP-UXi)
353 \row \i Mac OS X \i \c .dylib, \c .bundle, \c .so
354 \row \i Symbian \i \c .dll
357 Version number on unix are ignored.
359 QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
360 const QString &baseName)
362 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
363 return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
365 << QLatin1String("d.dll") // try a qmake-style debug build first
366 << QLatin1String(".dll"));
367 #elif defined(Q_OS_DARWIN)
368 return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
370 << QLatin1String("_debug.dylib") // try a qmake-style debug build first
371 << QLatin1String(".dylib")
372 << QLatin1String(".so")
373 << QLatin1String(".bundle"),
374 QLatin1String("lib"));
375 #else // Generic Unix
376 QStringList validSuffixList;
378 # if defined(Q_OS_HPUX)
380 See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
381 "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
382 the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
384 validSuffixList << QLatin1String(".sl");
386 validSuffixList << QLatin1String(".so");
388 # elif defined(Q_OS_AIX)
389 validSuffixList << QLatin1String(".a") << QLatin1String(".so");
390 # elif defined(Q_OS_UNIX)
391 validSuffixList << QLatin1String(".so");
394 // Examples of valid library names:
397 return resolvePlugin(qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
401 bool PluginDumper::Plugin::hasPredumpedQmlTypesFile() const
403 return QFileInfo(predumpedQmlTypesFilePath()).isFile();
406 QString PluginDumper::Plugin::predumpedQmlTypesFilePath() const
408 return QString("%1%2plugins.qmltypes").arg(qmldirPath, QDir::separator());