puppetcreator.cpp 18.3 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);
Thomas Hartmann's avatar
Thomas Hartmann committed
154 155
    QString forwardOutputMode = qgetenv("FORWARD_QML_PUPPET_OUTPUT").toLower();
    bool fowardQmlpuppetOutput = forwardOutputMode == puppetMode || forwardOutputMode == "true";
156 157 158 159
    if (fowardQmlpuppetOutput) {
        puppetProcess->setProcessChannelMode(QProcess::MergedChannels);
        QObject::connect(puppetProcess, SIGNAL(readyRead()), handlerObject, outputSlot);
    }
160
    puppetProcess->setWorkingDirectory(workingDirectory);
161 162
    puppetProcess->start(puppetPath, QStringList() << socketToken << puppetMode << "-graphicssystem raster");

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()) {
253 254
        if (m_qml2PuppetForKitPuppetHash.contains(m_kit->id())) {
            m_availablePuppetType = m_qml2PuppetForKitPuppetHash.value(m_kit->id());
255
        } else if (checkQml2PuppetIsReady()) {
256 257
            m_availablePuppetType = UserSpacePuppet;
        } else {
258
            if (m_kit->isValid()) {
259
                bool buildSucceeded = build(qml2PuppetProjectFile());
260 261 262 263 264 265
                if (buildSucceeded)
                    m_availablePuppetType = UserSpacePuppet;
            } else {
                warnAboutInvalidKit();
            }
            m_qml2PuppetForKitPuppetHash.insert(m_kit->id(), m_availablePuppetType);
266 267 268 269
        }
    }
}

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

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

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

289
    return qmlPuppetFallbackDirectory();
290 291
}

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

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

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

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

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

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

334
    if (m_availablePuppetType != FallbackPuppet) {
335
        environment.appendOrSet("QML2_IMPORT_PATH", m_model->importPaths().join(pathSep), pathSep);
336
    }
337 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
    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;
}

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

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

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

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

Tobias Hunger's avatar
Tobias Hunger committed
396
        QCoreApplication::processEvents(QEventLoop::ExcludeSocketNotifiers);
397

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

403 404
            m_compileLog.append(QString::fromLatin1(newOutput));
        }
405
    }
406

407
    process.waitForFinished();
408

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

    return false;
413 414 415 416 417 418 419
}

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

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

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

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

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

439
    return false;
440 441
}

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

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

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

hjk's avatar
hjk committed
456
    return currentQtVersion
457
            && currentQtVersion->isValid()
Marco Bubke's avatar
Marco Bubke committed
458
            && nonEarlyQt5Version(currentQtVersion->qtVersion())
hjk's avatar
hjk committed
459
            && currentQtVersion->type() == QLatin1String(QtSupport::Constants::DESKTOPQT);
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
}

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

    QProcess qmlPuppetVersionProcess;
    qmlPuppetVersionProcess.start(qmlPuppetPath, QStringList() << "--version");
    qmlPuppetVersionProcess.waitForReadyRead(6000);

    QByteArray versionString = qmlPuppetVersionProcess.readAll();

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

    return canConvert && versionNumber == 2;

}

} // namespace QmlDesigner