OSDN Git Service

c4cc381f8a1a680be0d258805670dcab1db8d733
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljstools / qmljsplugindumper.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "qmljsplugindumper.h"
35 #include "qmljsmodelmanager.h"
36
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>
42
43 #include <QtCore/QDir>
44
45 using namespace LanguageUtils;
46 using namespace QmlJS;
47 using namespace QmlJSTools;
48 using namespace QmlJSTools::Internal;
49
50 PluginDumper::PluginDumper(ModelManager *modelManager)
51     : QObject(modelManager)
52     , m_modelManager(modelManager)
53     , m_pluginWatcher(new ProjectExplorer::FileWatcher(this))
54 {
55     connect(m_pluginWatcher, SIGNAL(fileChanged(QString)), SLOT(pluginChanged(QString)));
56 }
57
58 void PluginDumper::loadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion)
59 {
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));
66 }
67
68 void PluginDumper::onLoadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion)
69 {
70     const QString canonicalLibraryPath = QDir::cleanPath(libraryPath);
71     if (m_runningQmldumps.values().contains(canonicalLibraryPath))
72         return;
73     const Snapshot snapshot = m_modelManager->snapshot();
74     const LibraryInfo libraryInfo = snapshot.libraryInfo(canonicalLibraryPath);
75     if (libraryInfo.dumpStatus() != LibraryInfo::DumpNotStartedOrRunning)
76         return;
77
78     // avoid inserting the same plugin twice
79     int index;
80     for (index = 0; index < m_plugins.size(); ++index) {
81         if (m_plugins.at(index).qmldirPath == libraryPath)
82             break;
83     }
84     if (index == m_plugins.size())
85         m_plugins.append(Plugin());
86
87     Plugin &plugin = m_plugins[index];
88     plugin.qmldirPath = canonicalLibraryPath;
89     plugin.importPath = importPath;
90     plugin.importUri = importUri;
91     plugin.importVersion = importVersion;
92
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);
98     }
99
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);
105     }
106
107     dump(plugin);
108 }
109
110 void PluginDumper::scheduleCompleteRedump()
111 {
112     metaObject()->invokeMethod(this, "dumpAllPlugins", Qt::QueuedConnection);
113 }
114
115 void PluginDumper::dumpAllPlugins()
116 {
117     foreach (const Plugin &plugin, m_plugins)
118         dump(plugin);
119 }
120
121 static QString qmldumpErrorMessage(const QString &libraryPath, const QString &error)
122 {
123     return PluginDumper::tr("Type dump of QML plugin in %1 failed.\nErrors:\n%2\n").
124            arg(libraryPath, error);
125 }
126
127 static QString qmldumpFailedMessage(const QString &error)
128 {
129     QString firstLines =
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"
133                             "\n"
134                             "%1"
135                             "\n"
136                             "Check 'General Messages' output pane for details."
137                             ).arg(firstLines);
138 }
139
140 static QList<FakeMetaObject::ConstPtr> parseHelper(const QByteArray &qmlTypeDescriptions, QString *error)
141 {
142     QList<FakeMetaObject::ConstPtr> ret;
143     QHash<QString, FakeMetaObject::ConstPtr> newObjects;
144     *error = Interpreter::CppQmlTypesLoader::parseQmlTypeDescriptions(qmlTypeDescriptions, &newObjects);
145
146     if (error->isEmpty()) {
147         ret = newObjects.values();
148     }
149     return ret;
150 }
151
152 void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
153 {
154     QProcess *process = qobject_cast<QProcess *>(sender());
155     if (!process)
156         return;
157     process->deleteLater();
158
159     const QString libraryPath = m_runningQmldumps.take(process);
160     const Snapshot snapshot = m_modelManager->snapshot();
161     LibraryInfo libraryInfo = snapshot.libraryInfo(libraryPath);
162
163     if (exitCode != 0) {
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));
168     }
169
170     const QByteArray output = process->readAllStandardOutput();
171     QString error;
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));
175     }
176
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);
183     }
184
185     if (!libraryPath.isEmpty())
186         m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
187 }
188
189 void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError)
190 {
191     QProcess *process = qobject_cast<QProcess *>(sender());
192     if (!process)
193         return;
194     process->deleteLater();
195
196     const QString libraryPath = m_runningQmldumps.take(process);
197
198     Core::MessageManager *messageManager = Core::MessageManager::instance();
199     const QString errorMessages = process->readAllStandardError();
200     messageManager->printToOutputPane(qmldumpErrorMessage(libraryPath, errorMessages));
201
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);
207     }
208 }
209
210 void PluginDumper::pluginChanged(const QString &pluginLibrary)
211 {
212     const int pluginIndex = m_libraryToPluginIndex.value(pluginLibrary, -1);
213     if (pluginIndex == -1)
214         return;
215
216     const Plugin &plugin = m_plugins.at(pluginIndex);
217     dump(plugin);
218 }
219
220 void PluginDumper::dump(const Plugin &plugin)
221 {
222     if (plugin.hasPredumpedQmlTypesFile()) {
223         const Snapshot snapshot = m_modelManager->snapshot();
224         LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath);
225         if (!libraryInfo.isValid())
226             return;
227
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);
234             return;
235         }
236
237         const QByteArray qmlTypeDescriptions = libraryQmlTypesFile.readAll();
238         libraryQmlTypesFile.close();
239
240         QString error;
241         const QList<FakeMetaObject::ConstPtr> objectsList = parseHelper(qmlTypeDescriptions, &error);
242
243         if (error.isEmpty()) {
244             libraryInfo.setMetaObjects(objectsList);
245             libraryInfo.setDumpStatus(LibraryInfo::DumpDone);
246         } else {
247             libraryInfo.setDumpStatus(LibraryInfo::DumpError,
248                                       tr("Failed to parse '%1'.\nError: %2").arg(path, error));
249         }
250         m_modelManager->updateLibraryInfo(plugin.qmldirPath, libraryInfo);
251         return;
252     }
253
254     ProjectExplorer::Project *activeProject = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
255     if (!activeProject)
256         return;
257
258     ModelManagerInterface::ProjectInfo info = m_modelManager->projectInfo(activeProject);
259
260     if (info.qmlDumpPath.isEmpty()) {
261         const Snapshot snapshot = m_modelManager->snapshot();
262         LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath);
263         if (!libraryInfo.isValid())
264             return;
265
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);
270         return;
271     }
272
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)));
277     QStringList args;
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;
284     } else {
285         args << plugin.importUri;
286         args << plugin.importVersion;
287         args << plugin.importPath;
288     }
289     process->start(info.qmlDumpPath, args);
290     m_runningQmldumps.insert(process, plugin.qmldirPath);
291 }
292
293 /*!
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.
296
297   \a qmldirPath is the location of the qmldir file.
298
299   Adapted from QDeclarativeImportDatabase::resolvePlugin.
300 */
301 QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
302                                     const QString &baseName, const QStringList &suffixes,
303                                     const QString &prefix)
304 {
305     QStringList searchPaths;
306     searchPaths.append(QLatin1String("."));
307
308     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
309     if (!qmldirPluginPathIsRelative)
310         searchPaths.prepend(qmldirPluginPath);
311
312     foreach (const QString &pluginPath, searchPaths) {
313
314         QString resolvedPath;
315
316         if (pluginPath == QLatin1String(".")) {
317             if (qmldirPluginPathIsRelative)
318                 resolvedPath = qmldirPath.absoluteFilePath(qmldirPluginPath);
319             else
320                 resolvedPath = qmldirPath.absolutePath();
321         } else {
322             resolvedPath = pluginPath;
323         }
324
325         QDir dir(resolvedPath);
326         foreach (const QString &suffix, suffixes) {
327             QString pluginFileName = prefix;
328
329             pluginFileName += baseName;
330             pluginFileName += suffix;
331
332             QFileInfo fileInfo(dir, pluginFileName);
333
334             if (fileInfo.exists())
335                 return fileInfo.absoluteFilePath();
336         }
337     }
338
339     return QString();
340 }
341
342 /*!
343   Returns the result of the merge of \a baseName with \a dir and the platform suffix.
344
345   Adapted from QDeclarativeImportDatabase::resolvePlugin.
346
347   \table
348   \header \i Platform \i Valid suffixes
349   \row \i Windows     \i \c .dll
350   \row \i Unix/Linux  \i \c .so
351   \row \i AIX  \i \c .a
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
355   \endtable
356
357   Version number on unix are ignored.
358 */
359 QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
360                                     const QString &baseName)
361 {
362 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
363     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
364                          QStringList()
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,
369                          QStringList()
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;
377
378 #  if defined(Q_OS_HPUX)
379 /*
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."
383  */
384     validSuffixList << QLatin1String(".sl");
385 #   if defined __ia64
386     validSuffixList << QLatin1String(".so");
387 #   endif
388 #  elif defined(Q_OS_AIX)
389     validSuffixList << QLatin1String(".a") << QLatin1String(".so");
390 #  elif defined(Q_OS_UNIX)
391     validSuffixList << QLatin1String(".so");
392 #  endif
393
394     // Examples of valid library names:
395     //  libfoo.so
396
397     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
398 #endif
399 }
400
401 bool PluginDumper::Plugin::hasPredumpedQmlTypesFile() const
402 {
403     return QFileInfo(predumpedQmlTypesFilePath()).isFile();
404 }
405
406 QString PluginDumper::Plugin::predumpedQmlTypesFilePath() const
407 {
408     return QString("%1%2plugins.qmltypes").arg(qmldirPath, QDir::separator());
409 }