puppetcreator.cpp 18.6 KB
Newer Older
1
2
/****************************************************************************
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
5
6
7
8
9
10
11
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
17
18
19
20
21
22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
23
24
25
26
27
28
29
30
31
**
****************************************************************************/

#include "puppetcreator.h"

#include <QProcess>
#include <QTemporaryDir>
#include <QCoreApplication>
#include <QCryptographicHash>
32
#include <QDateTime>
33
#include <QMessageBox>
34
#include <QThread>
35
36
37
38

#include <projectexplorer/kit.h>
#include <projectexplorer/toolchain.h>
#include <utils/environment.h>
39
#include <coreplugin/messagebox.h>
40
41
42
43
#include <coreplugin/icore.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
44
#include <coreplugin/icore.h>
45

46

47
#include <qmldesignerplugin.h>
48
49
#include "puppetbuildprogressdialog.h"

50
51
52

namespace QmlDesigner {

53
QHash<Core::Id, PuppetCreator::PuppetType> PuppetCreator::m_qml2PuppetForKitPuppetHash;
54

55
QByteArray PuppetCreator::qtHash() const
56
{
57
58
59
60
61
    if (m_kit) {
        QtSupport::BaseQtVersion *currentQtVersion = QtSupport::QtKitInformation::qtVersion(m_kit);
        if (currentQtVersion)
            return QCryptographicHash::hash(currentQtVersion->qmakeProperty("QT_INSTALL_DATA").toUtf8(), QCryptographicHash::Sha1).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
    }
62
63
64
65

    return QByteArray();
}

66
67
68
69
70
71
72
73
74
75
76
QDateTime PuppetCreator::qtLastModified() const
{
    if (m_kit) {
        QtSupport::BaseQtVersion *currentQtVersion = QtSupport::QtKitInformation::qtVersion(m_kit);
        if (currentQtVersion)
            return QFileInfo(currentQtVersion->qmakeProperty("QT_INSTALL_LIBS")).lastModified();
    }

    return QDateTime();
}

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
QDateTime PuppetCreator::puppetSourceLastModified() const
{
    QString basePuppetSourcePath = puppetSourceDirectoryPath();
    QStringList sourceDirectoryPathes;
    QDateTime lastModified;

    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/commands"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/container"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/instances"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/interfaces"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/types"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/qmlpuppet"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/qmlpuppet/instances"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/qml2puppet"));
    sourceDirectoryPathes.append(basePuppetSourcePath + QStringLiteral("/qml2puppet/instances"));

    foreach (const QString directoryPath, sourceDirectoryPathes) {
94
95
96
97
98
        foreach (const QFileInfo fileEntry, QDir(directoryPath).entryInfoList()) {
            QDateTime filePathLastModified = fileEntry.lastModified();
            if (lastModified < filePathLastModified)
                lastModified = filePathLastModified;
        }
99
100
101
102
103
    }

    return lastModified;
}

104
105
106
bool PuppetCreator::useOnlyFallbackPuppet() const
{
    DesignerSettings settings = QmlDesignerPlugin::instance()->settings();
107
    return settings.useOnlyFallbackPuppet
Thomas Hartmann's avatar
Thomas Hartmann committed
108
            || !qgetenv("USE_ONLY_FALLBACK_PUPPET").isEmpty() || m_kit == 0 || !m_kit->isValid();
109
110
}

111
PuppetCreator::PuppetCreator(ProjectExplorer::Kit *kit, const QString &qtCreatorVersion, const Model *model)
112
    : m_qtCreatorVersion(qtCreatorVersion),
113
      m_kit(kit),
114
115
      m_availablePuppetType(FallbackPuppet),
      m_model(model),
116
      m_designerSettings(QmlDesignerPlugin::instance()->settings())
117
118
119
{
}

120
121
122
123
PuppetCreator::~PuppetCreator()
{
}

124
void PuppetCreator::createPuppetExecutableIfMissing()
125
{
126
    createQml2PuppetExecutableIfMissing();
127
128
}

129
QProcess *PuppetCreator::createPuppetProcess(const QString &puppetMode, const QString &socketToken, QObject *handlerObject, const char *outputSlot, const char *finishSlot) const
130
{
131
132
    return puppetProcess(qml2PuppetPath(m_availablePuppetType),
                         qmlPuppetDirectory(m_availablePuppetType),
133
134
135
136
137
                         puppetMode,
                         socketToken,
                         handlerObject,
                         outputSlot,
                         finishSlot);
138
139
140
141
}


QProcess *PuppetCreator::puppetProcess(const QString &puppetPath,
142
                                       const QString &workingDirectory,
143
144
145
146
147
148
149
150
151
152
153
                                       const QString &puppetMode,
                                       const QString &socketToken,
                                       QObject *handlerObject,
                                       const char *outputSlot,
                                       const char *finishSlot) const
{
    QProcess *puppetProcess = new QProcess;
    puppetProcess->setObjectName(puppetMode);
    puppetProcess->setProcessEnvironment(processEnvironment());
    QObject::connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), puppetProcess, SLOT(kill()));
    QObject::connect(puppetProcess, SIGNAL(finished(int,QProcess::ExitStatus)), handlerObject, finishSlot);
Tim Jenssen's avatar
Tim Jenssen committed
154
155
    QString forwardOutputMode = QString::fromLatin1(qgetenv("FORWARD_QML_PUPPET_OUTPUT").toLower());
    bool fowardQmlpuppetOutput = forwardOutputMode == puppetMode || forwardOutputMode == QLatin1String("true");
156
157
158
159
    if (fowardQmlpuppetOutput) {
        puppetProcess->setProcessChannelMode(QProcess::MergedChannels);
        QObject::connect(puppetProcess, SIGNAL(readyRead()), handlerObject, outputSlot);
    }
160
    puppetProcess->setWorkingDirectory(workingDirectory);
Tim Jenssen's avatar
Tim Jenssen committed
161
    puppetProcess->start(puppetPath, QStringList() << socketToken << puppetMode << QLatin1String("-graphicssystem raster"));
162

163
164
165
    if (!qgetenv("DEBUG_QML_PUPPET").isEmpty())
        QMessageBox::information(Core::ICore::dialogParent(),
                                 QStringLiteral("Puppet is starting ..."),
166
167
                                 QStringLiteral("You can now attach your debugger to the %1 puppet with process id: %2.").arg(
                                     puppetMode, QString::number(puppetProcess->processId())));
168

169
170
171
    return puppetProcess;
}

172
173
174
175
176
177
178
179
static QString idealProcessCount()
{
    int processCount = QThread::idealThreadCount() + 1;
    if (processCount < 1)
        processCount = 4;

    return QString::number(processCount);
}
180
181
182

bool PuppetCreator::build(const QString &qmlPuppetProjectFilePath) const
{
183
184
    PuppetBuildProgressDialog progressDialog;

185
186
187
188
189
190
191
192
193
194
    m_compileLog.clear();

    QTemporaryDir buildDirectory;

    bool buildSucceeded = false;

    if (qtIsSupported()) {
        if (buildDirectory.isValid()) {
            QStringList qmakeArguments;
            qmakeArguments.append(QStringLiteral("-r"));
195
            qmakeArguments.append(QStringLiteral("-after"));
196
            qmakeArguments.append(QStringLiteral("DESTDIR=") + qmlPuppetDirectory(UserSpacePuppet));
197
198
199
#ifdef QT_DEBUG
            qmakeArguments.append(QStringLiteral("CONFIG+=debug"));
#else
200
201
            qmakeArguments.append(QStringLiteral("CONFIG+=release"));
#endif
202
            qmakeArguments.append(qmlPuppetProjectFilePath);
203
            buildSucceeded = startBuildProcess(buildDirectory.path(), qmakeCommand(), qmakeArguments, &progressDialog);
204
205
            if (buildSucceeded) {
                progressDialog.show();
206
207
                QString buildingCommand = buildCommand();
                QStringList buildArguments;
208
                if (buildingCommand == QStringLiteral("make")) {
209
210
211
212
                    buildArguments.append(QStringLiteral("-j"));
                    buildArguments.append(idealProcessCount());
                }
                buildSucceeded = startBuildProcess(buildDirectory.path(), buildingCommand, buildArguments, &progressDialog);
213
            }
214

215
216
217
218
219
220
221
222
223
            if (!buildSucceeded) {
                progressDialog.setWindowTitle(QCoreApplication::translate("PuppetCreator", "QML Emulation Layer (QML Puppet) Building was Unsuccessful"));
                progressDialog.setErrorMessage(QCoreApplication::translate("PuppetCreator",
                                                                           "The QML emulation layer (QML Puppet) cannot be built. "
                                                                           "The fallback emulation layer, which does not support all features, will be used."
                                                                           ));
                // now we want to keep the dialog open
                progressDialog.exec();
            }
224
        }
225
    } else {
226
        Core::AsynchronousMessageBox::warning(QCoreApplication::translate("PuppetCreator", "Qt Version is not supported"),
227
228
229
230
231
                                               QCoreApplication::translate("PuppetCreator",
                                                                           "The QML emulation layer (QML Puppet) cannot be built because the Qt version is too old "
                                                                           "or it cannot run natively on your computer. "
                                                                           "The fallback emulation layer, which does not support all features, will be used."
                                                                           ));
232
233
234
235
236
    }

    return buildSucceeded;
}

237
238
static void warnAboutInvalidKit()
{
239
    Core::AsynchronousMessageBox::warning(QCoreApplication::translate("PuppetCreator", "Kit is invalid"),
240
241
242
243
244
245
                                           QCoreApplication::translate("PuppetCreator",
                                                                       "The QML emulation layer (QML Puppet) cannot be built because the kit is not configured correctly. "
                                                                       "For example the compiler can be misconfigured. "
                                                                       "Fix the kit configuration and restart Qt Creator. "
                                                                       "Otherwise, the fallback emulation layer, which does not support all features, will be used."
                                                                       ));
246
247
}

248
249
void PuppetCreator::createQml2PuppetExecutableIfMissing()
{
250
    m_availablePuppetType = FallbackPuppet;
251

Thomas Hartmann's avatar
Thomas Hartmann committed
252
    if (!useOnlyFallbackPuppet()) {
Tim Jenssen's avatar
Tim Jenssen committed
253
254
255
256
257
        // check if there was an already failing try to get the UserSpacePuppet
        // -> imagine as result a FallbackPuppet and nothing will happen again
        if (m_qml2PuppetForKitPuppetHash.value(m_kit->id(), UserSpacePuppet) == UserSpacePuppet ) {
            if (checkQml2PuppetIsReady()) {
                m_availablePuppetType = UserSpacePuppet;
258
            } else {
Tim Jenssen's avatar
Tim Jenssen committed
259
260
261
262
263
264
265
266
                if (m_kit->isValid()) {
                    bool buildSucceeded = build(qml2PuppetProjectFile());
                    if (buildSucceeded)
                        m_availablePuppetType = UserSpacePuppet;
                } else {
                    warnAboutInvalidKit();
                }
                m_qml2PuppetForKitPuppetHash.insert(m_kit->id(), m_availablePuppetType);
267
            }
268
269
270
271
        }
    }
}

272
QString PuppetCreator::defaultPuppetToplevelBuildDirectory()
273
{
274
275
276
    return Core::ICore::userResourcePath()
            + QStringLiteral("/qmlpuppet/");
}
277

278
279
280
281
282
283
QString PuppetCreator::qmlPuppetToplevelBuildDirectory() const
{
    if (m_designerSettings.puppetToplevelBuildDirectory.isEmpty())
        return defaultPuppetToplevelBuildDirectory();
    return QmlDesignerPlugin::instance()->settings().puppetToplevelBuildDirectory;
}
284

285
286
287
288
289
QString PuppetCreator::qmlPuppetDirectory(PuppetType puppetType) const
{
    if (puppetType == UserSpacePuppet)
        return qmlPuppetToplevelBuildDirectory() + QStringLiteral("/")
            + QCoreApplication::applicationVersion() + QStringLiteral("/") + QString::fromLatin1(qtHash());
290

291
    return qmlPuppetFallbackDirectory();
292
293
}

294
QString PuppetCreator::defaultPuppetFallbackDirectory()
295
{
Eike Ziller's avatar
Eike Ziller committed
296
297
298
299
    if (Utils::HostOsInfo::isMacHost())
        return Core::ICore::libexecPath() + QLatin1String("/qmldesigner");
    else
        return Core::ICore::libexecPath();
300
301
}

302
303
304
305
306
307
308
QString PuppetCreator::qmlPuppetFallbackDirectory() const
{
    if (m_designerSettings.puppetFallbackDirectory.isEmpty())
        return defaultPuppetFallbackDirectory();
    return QmlDesignerPlugin::instance()->settings().puppetFallbackDirectory;
}

309
QString PuppetCreator::qml2PuppetPath(PuppetType puppetType) const
310
{
311
    return qmlPuppetDirectory(puppetType) + QStringLiteral("/qml2puppet") + QStringLiteral(QTC_HOST_EXE_SUFFIX);
312
313
314
315
}

QProcessEnvironment PuppetCreator::processEnvironment() const
{
316
317
318
319
320
#if defined(Q_OS_WIN)
    static QLatin1String pathSep(";");
#else
    static QLatin1String pathSep(":");
#endif
321
    Utils::Environment environment = Utils::Environment::systemEnvironment();
322
323
    if (!useOnlyFallbackPuppet())
        m_kit->addToEnvironment(environment);
Tim Jenssen's avatar
Tim Jenssen committed
324
325
326
    environment.set(QLatin1String("QML_BAD_GUI_RENDER_LOOP"), QLatin1String("true"));
    environment.set(QLatin1String("QML_USE_MOCKUPS"), QLatin1String("true"));
    environment.set(QLatin1String("QML_PUPPET_MODE"), QLatin1String("true"));
327

328
329
330
331
    const QString controlsStyle = QmlDesignerPlugin::instance()->settings().controlsStyle;
    if (!controlsStyle.isEmpty())
        environment.set(QLatin1String("QT_QUICK_CONTROLS_STYLE"), controlsStyle);

332
333
334
335
    if (!m_qrcMapping.isEmpty()) {
        environment.set(QLatin1String("QMLDESIGNER_RC_PATHS"), m_qrcMapping);
    }

336
    if (m_availablePuppetType != FallbackPuppet) {
337
        environment.appendOrSet("QML2_IMPORT_PATH", m_model->importPaths().join(pathSep), pathSep);
338
    }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    return environment.toProcessEnvironment();
}

QString PuppetCreator::buildCommand() const
{
    Utils::Environment environment = Utils::Environment::systemEnvironment();
    m_kit->addToEnvironment(environment);

    ProjectExplorer::ToolChain *toolChain = ProjectExplorer::ToolChainKitInformation::toolChain(m_kit);

    if (toolChain)
        return toolChain->makeCommand(environment);

    return QString();
}

QString PuppetCreator::qmakeCommand() const
{
    QtSupport::BaseQtVersion *currentQtVersion = QtSupport::QtKitInformation::qtVersion(m_kit);
    if (currentQtVersion)
        return currentQtVersion->qmakeCommand().toString();

    return QString();
}

QString PuppetCreator::compileLog() const
{
    return m_compileLog;
}

369
370
371
372
373
void PuppetCreator::setQrcMappingString(const QString qrcMapping)
{
    m_qrcMapping = qrcMapping;
}

374
375
bool PuppetCreator::startBuildProcess(const QString &buildDirectoryPath,
                                      const QString &command,
376
377
                                      const QStringList &processArguments,
                                      PuppetBuildProgressDialog *progressDialog) const
378
379
380
381
{
    if (command.isEmpty())
        return false;

382
383
384
385
386
    const QString errorOutputFilePath(buildDirectoryPath + QLatin1String("/build_error_output.txt"));
    if (QFile::exists(errorOutputFilePath))
        QFile(errorOutputFilePath).remove();
    progressDialog->setErrorOutputFile(errorOutputFilePath);

387
    QProcess process;
388
    process.setStandardErrorFile(errorOutputFilePath);
389
    process.setProcessChannelMode(QProcess::SeparateChannels);
390
391
392
    process.setProcessEnvironment(processEnvironment());
    process.setWorkingDirectory(buildDirectoryPath);
    process.start(command, processArguments);
393
    process.waitForStarted();
394
    while (process.waitForReadyRead(100) || process.state() == QProcess::Running) {
395
396
397
        if (progressDialog->useFallbackPuppet())
            return false;

Tobias Hunger's avatar
Tobias Hunger committed
398
        QCoreApplication::processEvents(QEventLoop::ExcludeSocketNotifiers);
399

400
        QByteArray newOutput = process.readAllStandardOutput();
401
402
403
        if (!newOutput.isEmpty()) {
            if (progressDialog)
                progressDialog->newBuildOutput(newOutput);
404

405
406
            m_compileLog.append(QString::fromLatin1(newOutput));
        }
407
    }
408

409
    process.waitForFinished();
410

411
    if (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0)
412
        return true;
413
414

    return false;
415
416
417
418
419
420
421
}

QString PuppetCreator::puppetSourceDirectoryPath()
{
    return Core::ICore::resourcePath() + QStringLiteral("/qml/qmlpuppet");
}

422
QString PuppetCreator::qml2PuppetProjectFile()
423
424
425
426
{
    return puppetSourceDirectoryPath() + QStringLiteral("/qml2puppet/qml2puppet.pro");
}

427
QString PuppetCreator::qmlPuppetProjectFile()
428
429
430
431
432
433
434
{
    return puppetSourceDirectoryPath() + QStringLiteral("/qmlpuppet/qmlpuppet.pro");
}

bool PuppetCreator::checkPuppetIsReady(const QString &puppetPath) const
{
    QFileInfo puppetFileInfo(puppetPath);
435
436
437
438
439
    if (puppetFileInfo.exists()) {
        QDateTime puppetExecutableLastModified = puppetFileInfo.lastModified();

        return puppetExecutableLastModified > qtLastModified() && puppetExecutableLastModified > puppetSourceLastModified();
    }
440

441
    return false;
442
443
}

444
bool PuppetCreator::checkQml2PuppetIsReady() const
445
{
446
    return checkPuppetIsReady(qml2PuppetPath(UserSpacePuppet));
447
448
}

Marco Bubke's avatar
Marco Bubke committed
449
450
451
452
453
static bool nonEarlyQt5Version(const QtSupport::QtVersionNumber &currentQtVersionNumber)
{
    return currentQtVersionNumber >= QtSupport::QtVersionNumber(5, 2, 0) || currentQtVersionNumber < QtSupport::QtVersionNumber(5, 0, 0);
}

454
455
456
457
bool PuppetCreator::qtIsSupported() const
{
    QtSupport::BaseQtVersion *currentQtVersion = QtSupport::QtKitInformation::qtVersion(m_kit);

hjk's avatar
hjk committed
458
    return currentQtVersion
459
            && currentQtVersion->isValid()
Marco Bubke's avatar
Marco Bubke committed
460
            && nonEarlyQt5Version(currentQtVersion->qtVersion())
hjk's avatar
hjk committed
461
            && currentQtVersion->type() == QLatin1String(QtSupport::Constants::DESKTOPQT);
462
463
464
465
466
467
}

bool PuppetCreator::checkPuppetVersion(const QString &qmlPuppetPath)
{

    QProcess qmlPuppetVersionProcess;
Tim Jenssen's avatar
Tim Jenssen committed
468
    qmlPuppetVersionProcess.start(qmlPuppetPath, QStringList() << QLatin1String("--version"));
469
470
471
472
473
474
475
476
477
478
479
480
    qmlPuppetVersionProcess.waitForReadyRead(6000);

    QByteArray versionString = qmlPuppetVersionProcess.readAll();

    bool canConvert;
    unsigned int versionNumber = versionString.toUInt(&canConvert);

    return canConvert && versionNumber == 2;

}

} // namespace QmlDesigner