From b057ea1ab81cbb74b05e763afa61974920886810 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 3 Nov 2020 00:20:43 +0100 Subject: [PATCH] WebAssembly: Revamp Emscripten SDK registration and toolchain handling Qt Creator's ability to register Emscripten toolchains depended on an SDK being activated "globally", and on the presence of the ~/.emscripten file. Qt Creator would parse that file in order to determine the location of the compiler and the necessary environment variables that have to be set. As of recently, the Emscripten SDK does neither activate gobally anymore, nor is the ~/.emscripten generated. This change here addresses the new situation in a couple of ways: - Instead of trying to silently detect everything, add a UI (IOptionsPageWidget) that lets the user select the Emscripten SDK root. - Instead of parsing the ~/.emscripten file, parse the output of the emsdk_env tool to determine the toolchain environment (ToolChain::addToEnvironment). The parsing is cached. A test for the parsing is included. - Instead of registering the underlying clang as compiler, register the emcc/em++ wrapper scripts, which are (also as of recently) compatible with Qt Creator's way of determining gcc's predefined macros and built- in header paths. One Emscripten SDK is registered globally in Qt Creator. When changing that, the previous Emscripten toolchains are removed, the new ones are registered and the kit are "fixed" to use those. On startup, an InfoBar entry is shown if Qt for Webassembly kits exist and no Emscripten toolchains are present. That's the case for first-time use after installing Qt for Webassembly via Qt SDK installer. The InfoBar entry opens up the IOptionsPageWidget. Qt 5.15.0 for WebAssembly and Emscripten SDK 1.39.0 are the minimum supported versions. The new UI will show warnings accordingly. Task-number: QTCREATORBUG-24811 Fixes: QTCREATORBUG-24822 Fixes: QTCREATORBUG-24814 Fixes: QTCREATORBUG-23741 Fixes: QTCREATORBUG-23561 Fixes: QTCREATORBUG-23160 Fixes: QTCREATORBUG-23126 Change-Id: I017c61586b17e815bb20a90e3f305a6bf705da36 Reviewed-by: hjk --- src/plugins/webassembly/CMakeLists.txt | 2 + src/plugins/webassembly/webassembly.pro | 4 + src/plugins/webassembly/webassembly.qbs | 4 + .../webassembly/webassemblyconstants.h | 7 +- src/plugins/webassembly/webassemblyemsdk.cpp | 181 +++++++++++++++ src/plugins/webassembly/webassemblyemsdk.h | 52 +++++ .../webassembly/webassemblyoptionspage.cpp | 192 ++++++++++++++++ .../webassembly/webassemblyoptionspage.h | 40 ++++ src/plugins/webassembly/webassemblyplugin.cpp | 31 ++- src/plugins/webassembly/webassemblyplugin.h | 8 +- .../webassembly/webassemblyqtversion.cpp | 48 +++- .../webassembly/webassemblyqtversion.h | 9 +- .../webassemblyrunconfiguration.cpp | 18 +- .../webassembly/webassemblytoolchain.cpp | 213 ++++++++++-------- .../webassembly/webassemblytoolchain.h | 13 +- 15 files changed, 712 insertions(+), 110 deletions(-) create mode 100644 src/plugins/webassembly/webassemblyemsdk.cpp create mode 100644 src/plugins/webassembly/webassemblyemsdk.h create mode 100644 src/plugins/webassembly/webassemblyoptionspage.cpp create mode 100644 src/plugins/webassembly/webassemblyoptionspage.h diff --git a/src/plugins/webassembly/CMakeLists.txt b/src/plugins/webassembly/CMakeLists.txt index f56c5067311..8bb1aa9e115 100644 --- a/src/plugins/webassembly/CMakeLists.txt +++ b/src/plugins/webassembly/CMakeLists.txt @@ -6,6 +6,8 @@ add_qtc_plugin(WebAssembly webassembly_global.h webassemblyconstants.h webassemblydevice.cpp webassemblydevice.h + webassemblyemsdk.cpp webassemblyemsdk.h + webassemblyoptionspage.cpp webassemblyoptionspage.h webassemblyplugin.cpp webassemblyplugin.h webassemblyqtversion.cpp webassemblyqtversion.h webassemblyrunconfigurationaspects.cpp webassemblyrunconfigurationaspects.h diff --git a/src/plugins/webassembly/webassembly.pro b/src/plugins/webassembly/webassembly.pro index 84b9d7f6b2d..0e35a9b8334 100644 --- a/src/plugins/webassembly/webassembly.pro +++ b/src/plugins/webassembly/webassembly.pro @@ -4,6 +4,8 @@ HEADERS += \ webassembly_global.h \ webassemblyconstants.h \ webassemblydevice.h \ + webassemblyemsdk.h \ + webassemblyoptionspage.h \ webassemblyplugin.h \ webassemblyqtversion.h \ webassemblyrunconfigurationaspects.h \ @@ -12,6 +14,8 @@ HEADERS += \ SOURCES += \ webassemblydevice.cpp \ + webassemblyemsdk.cpp \ + webassemblyoptionspage.cpp \ webassemblyplugin.cpp \ webassemblyqtversion.cpp \ webassemblyrunconfigurationaspects.cpp \ diff --git a/src/plugins/webassembly/webassembly.qbs b/src/plugins/webassembly/webassembly.qbs index 2fa5a677253..34862beaa68 100644 --- a/src/plugins/webassembly/webassembly.qbs +++ b/src/plugins/webassembly/webassembly.qbs @@ -17,6 +17,10 @@ QtcPlugin { "webassemblyconstants.h", "webassemblydevice.cpp", "webassemblydevice.h", + "webassemblyemsdk.cpp", + "webassemblyemsdk.h", + "webassemblyoptionspage.cpp", + "webassemblyoptionspage.h", "webassemblyplugin.cpp", "webassemblyplugin.h", "webassemblyqtversion.cpp", diff --git a/src/plugins/webassembly/webassemblyconstants.h b/src/plugins/webassembly/webassemblyconstants.h index 0da71952225..6951d4050a6 100644 --- a/src/plugins/webassembly/webassemblyconstants.h +++ b/src/plugins/webassembly/webassemblyconstants.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -28,11 +28,16 @@ namespace WebAssembly { namespace Constants { +const char SETTINGS_ID[] = "CC.WebAssembly.Configuration"; + const char WEBASSEMBLY_TOOLCHAIN_TYPEID[] = "WebAssembly.ToolChain.Emscripten"; const char WEBASSEMBLY_DEVICE_TYPE[] = "WebAssemblyDeviceType"; const char WEBASSEMBLY_DEVICE_DEVICE_ID[] = "WebAssembly Device"; const char WEBASSEMBLY_QT_VERSION[] = "Qt4ProjectManager.QtVersion.WebAssembly"; const char WEBASSEMBLY_RUNCONFIGURATION_EMRUN[] = "WebAssembly.RunConfiguration.Emrun"; +const char SETTINGS_GROUP[] = "WebAssembly"; +const char SETTINGS_KEY_EMSDK[] = "EmSdk"; + } // namespace WebAssembly } // namespace Constants diff --git a/src/plugins/webassembly/webassemblyemsdk.cpp b/src/plugins/webassembly/webassemblyemsdk.cpp new file mode 100644 index 00000000000..2e114bb2e7c --- /dev/null +++ b/src/plugins/webassembly/webassemblyemsdk.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "webassemblyconstants.h" +#include "webassemblyemsdk.h" + +#include +#include +#include +#include + +#include + +#ifdef WITH_TESTS +# include +# include "webassemblyplugin.h" +#endif // WITH_TESTS + +using namespace Utils; + +namespace WebAssembly { +namespace Internal { + +using EmSdkEnvCache = QCache; +Q_GLOBAL_STATIC_WITH_ARGS(EmSdkEnvCache, emSdkEnvCache, (10)) +using EmSdkVersionCache = QCache; +Q_GLOBAL_STATIC_WITH_ARGS(EmSdkVersionCache, emSdkVersionCache, (10)) + +static QByteArray emSdkEnvOutput(const FilePath &sdkRoot) +{ + const QString cacheKey = sdkRoot.toString(); + if (!emSdkEnvCache()->contains(cacheKey)) { + const QString scriptFile = sdkRoot.pathAppended(QLatin1String("emsdk_env") + + (HostOsInfo::isWindowsHost() ? ".bat" : ".sh")).toString(); + QtcProcess emSdkEnv; + if (HostOsInfo::isWindowsHost()) { + emSdkEnv.setCommand(CommandLine(scriptFile)); + } else { + // File needs to be source'd, not executed. + emSdkEnv.setCommand({"bash", {"-c", ". " + scriptFile}}); + } + emSdkEnv.start(); + if (!emSdkEnv.waitForFinished()) + return {}; + emSdkEnvCache()->insert(cacheKey, new QByteArray(emSdkEnv.readAllStandardError())); + } + return *emSdkEnvCache()->object(cacheKey); +} + +static void parseEmSdkEnvOutputAndAddToEnv(const QByteArray &output, Environment &env) +{ + const QStringList lines = QString::fromLocal8Bit(output).split('\n'); + + for (const QString &line : lines) { + const QStringList prependParts = line.trimmed().split(" += "); + if (prependParts.count() == 2) + env.prependOrSetPath(prependParts.last()); + + const QStringList setParts = line.trimmed().split(" = "); + if (setParts.count() == 2) { + if (setParts.first() != "PATH") // Path was already extended above + env.set(setParts.first(), setParts.last()); + continue; + } + } +} + +bool WebAssemblyEmSdk::isValid(const FilePath &sdkRoot) +{ + return !version(sdkRoot).isNull(); +} + +void WebAssemblyEmSdk::addToEnvironment(const FilePath &sdkRoot, Environment &env) +{ + if (sdkRoot.exists()) + parseEmSdkEnvOutputAndAddToEnv(emSdkEnvOutput(sdkRoot), env); +} + +QVersionNumber WebAssemblyEmSdk::version(const FilePath &sdkRoot) +{ + if (!sdkRoot.exists()) + return {}; + const QString cacheKey = sdkRoot.toString(); + if (!emSdkVersionCache()->contains(cacheKey)) { + Environment env; + // Non-Windows: Need python in path (not provided by emsdk), thus use systemEnvironment + if (!HostOsInfo::isWindowsHost()) + env = Environment::systemEnvironment(); + WebAssemblyEmSdk::addToEnvironment(sdkRoot, env); + const QString scriptFile = + QLatin1String("emcc") + QLatin1String(HostOsInfo::isWindowsHost() ? ".bat" : ""); + const CommandLine command(env.searchInPath(scriptFile), {"-dumpversion"}); + QtcProcess emcc; + emcc.setCommand(command); + emcc.setEnvironment(env); + emcc.start(); + if (!emcc.waitForFinished()) + return {}; + const QString version = QLatin1String(emcc.readAllStandardOutput()); + emSdkVersionCache()->insert(cacheKey, + new QVersionNumber(QVersionNumber::fromString(version))); + } + return *emSdkVersionCache()->object(cacheKey); +} + +void WebAssemblyEmSdk::registerEmSdk(const FilePath &sdkRoot) +{ + QSettings *s = Core::ICore::settings(); + s->setValue(QLatin1String(Constants::SETTINGS_GROUP) + '/' + + QLatin1String(Constants::SETTINGS_KEY_EMSDK), sdkRoot.toString()); +} + +FilePath WebAssemblyEmSdk::registeredEmSdk() +{ + QSettings *s = Core::ICore::settings(); + const QString path = s->value(QLatin1String(Constants::SETTINGS_GROUP) + '/' + + QLatin1String(Constants::SETTINGS_KEY_EMSDK)).toString(); + return FilePath::fromUserInput(path); +} + +void WebAssemblyEmSdk::clearCaches() +{ + emSdkEnvCache()->clear(); + emSdkVersionCache()->clear(); +} + +// Unit tests: +#ifdef WITH_TESTS +void WebAssemblyPlugin::testEmSdkEnvParsing() +{ + // Output of "emsdk_env" + const QByteArray emSdkEnvOutput = R"( +Adding directories to PATH: +PATH += C:\Users\user\dev\emsdk +PATH += C:\Users\user\dev\emsdk\upstream\emscripten +PATH += C:\Users\user\dev\emsdk\node\12.18.1_64bit\bin +PATH += C:\Users\user\dev\emsdk\python\3.7.4-pywin32_64bit +PATH += C:\Users\user\dev\emsdk\java\8.152_64bit\bin + +Setting environment variables: +PATH = C:\Users\user\dev\emsdk;C:\Users\user\dev\emsdk\upstream\emscripten;C:\Users\user\dev\emsdk\node\12.18.1_64bit\bin;C:\Users\user\dev\emsdk\python\3.7.4-pywin32_64bit;C:\Users\user\dev\emsdk\java\8.152_64bit\bin;...other_stuff... +EMSDK = C:/Users/user/dev/emsdk +EM_CONFIG = C:\Users\user\dev\emsdk\.emscripten +EM_CACHE = C:/Users/user/dev/emsdk/upstream/emscripten\cache +EMSDK_NODE = C:\Users\user\dev\emsdk\node\12.18.1_64bit\bin\node.exe +EMSDK_PYTHON = C:\Users\user\dev\emsdk\python\3.7.4-pywin32_64bit\python.exe +JAVA_HOME = C:\Users\user\dev\emsdk\java\8.152_64bit + )"; + Environment env; + parseEmSdkEnvOutputAndAddToEnv(emSdkEnvOutput, env); + + QVERIFY(env.path().count() == 5); + QCOMPARE(env.value("EMSDK"), "C:/Users/user/dev/emsdk"); + QCOMPARE(env.value("EM_CONFIG"), "C:\\Users\\user\\dev\\emsdk\\.emscripten"); +} +#endif // WITH_TESTS + +} // namespace Internal +} // namespace WebAssembly diff --git a/src/plugins/webassembly/webassemblyemsdk.h b/src/plugins/webassembly/webassemblyemsdk.h new file mode 100644 index 00000000000..0e183491576 --- /dev/null +++ b/src/plugins/webassembly/webassemblyemsdk.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include + +namespace Utils { +class Environment; +} + +namespace WebAssembly { +namespace Internal { + +class WebAssemblyEmSdk +{ +public: + static bool isValid(const Utils::FilePath &sdkRoot); + static void addToEnvironment(const Utils::FilePath &sdkRoot, Utils::Environment &env); + static QVersionNumber version(const Utils::FilePath &sdkRoot); + static void registerEmSdk(const Utils::FilePath &sdkRoot); + static Utils::FilePath registeredEmSdk(); + static void clearCaches(); +}; + +} // namespace Internal +} // namespace WebAssembly diff --git a/src/plugins/webassembly/webassemblyoptionspage.cpp b/src/plugins/webassembly/webassemblyoptionspage.cpp new file mode 100644 index 00000000000..a840f2d4b66 --- /dev/null +++ b/src/plugins/webassembly/webassemblyoptionspage.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "webassemblyconstants.h" +#include "webassemblyemsdk.h" +#include "webassemblyoptionspage.h" +#include "webassemblyqtversion.h" +#include "webassemblytoolchain.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace WebAssembly { +namespace Internal { + +class WebAssemblyOptionsWidget : public Core::IOptionsPageWidget +{ + Q_DECLARE_TR_FUNCTIONS(WebAssembly::Internal::WebAssemblyOptionsWidget) + +public: + WebAssemblyOptionsWidget(); + + void updateStatus(); + +private: + void apply() final; + void showEvent(QShowEvent *event) final; + + PathChooser *m_emSdkPathChooser; + InfoLabel *m_emSdkVersionDisplay; + QGroupBox *m_emSdkEnvGroupBox; + QTextBrowser *m_emSdkEnvDisplay; + InfoLabel *m_qtVersionDisplay; +}; + +WebAssemblyOptionsWidget::WebAssemblyOptionsWidget() +{ + auto mainLayout = new QVBoxLayout(this); + + { + auto pathChooserBox = new QGroupBox(tr("Emscripten SDK path:")); + pathChooserBox->setFlat(true); + auto layout = new QVBoxLayout(pathChooserBox); + auto instruction = new QLabel( + tr("Select the root directory of an " + "" + "installed Emscripten SDK. Ensure that the activated SDK version is " + "" + "compatible with the Qt version that you plan to develop against.")); + instruction->setOpenExternalLinks(true); + instruction->setWordWrap(true); + layout->addWidget(instruction); + m_emSdkPathChooser = new PathChooser(this); + m_emSdkPathChooser->setExpectedKind(PathChooser::Directory); + m_emSdkPathChooser->setInitialBrowsePathBackup(QDir::homePath()); + m_emSdkPathChooser->setFilePath(WebAssemblyEmSdk::registeredEmSdk()); + connect(m_emSdkPathChooser, &PathChooser::pathChanged, [this](){ updateStatus(); }); + layout->addWidget(m_emSdkPathChooser); + m_emSdkVersionDisplay = new InfoLabel(this); + m_emSdkVersionDisplay->setElideMode(Qt::ElideNone); + m_emSdkVersionDisplay->setWordWrap(true); + layout->addWidget(m_emSdkVersionDisplay); + mainLayout->addWidget(pathChooserBox); + } + + { + m_emSdkEnvGroupBox = new QGroupBox(tr("Emscripten SDK environment:")); + m_emSdkEnvGroupBox->setFlat(true); + m_emSdkEnvGroupBox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); + auto layout = new QVBoxLayout(m_emSdkEnvGroupBox); + m_emSdkEnvDisplay = new QTextBrowser; + m_emSdkEnvDisplay->setLineWrapMode(QTextBrowser::NoWrap); + m_emSdkEnvDisplay->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + layout->addWidget(m_emSdkEnvDisplay); + mainLayout->addWidget(m_emSdkEnvGroupBox, 1); + } + + mainLayout->addStretch(); + + { + const QString minimumSupportedQtVersion = QString::fromLatin1("%1.%2") + .arg(WebAssemblyQtVersion::minimumSupportedQtVersion().majorVersion) + .arg(WebAssemblyQtVersion::minimumSupportedQtVersion().minorVersion); + m_qtVersionDisplay = new InfoLabel( + tr("Note: %1 supports Qt %2 for WebAssembly and higher. " + "Your installed lower version(s) are not supported.") + .arg(Core::ICore::versionString(), minimumSupportedQtVersion), + InfoLabel::Warning); + m_qtVersionDisplay->setElideMode(Qt::ElideNone); + m_qtVersionDisplay->setWordWrap(true); + mainLayout->addWidget(m_qtVersionDisplay); + } +} + +static QString environmentDisplay(const FilePath &sdkRoot) +{ + Environment env; + WebAssemblyEmSdk::addToEnvironment(sdkRoot, env); + QString result; + result.append(WebAssemblyOptionsWidget::tr("

Adding directories to PATH:

")); + result.append(env.value("PATH").replace(HostOsInfo::pathListSeparator(), "
")); + result.append(WebAssemblyOptionsWidget::tr("

Setting environment variables:

")); + for (const QString &envVar : env.toStringList()) { + if (!envVar.startsWith("PATH")) // Path was already printed out above + result.append(envVar + "
"); + } + return result; +} + +void WebAssemblyOptionsWidget::updateStatus() +{ + WebAssemblyEmSdk::clearCaches(); + + const FilePath sdkPath = m_emSdkPathChooser->filePath(); + const bool sdkValid = sdkPath.exists() && WebAssemblyEmSdk::isValid(sdkPath); + + m_emSdkVersionDisplay->setVisible(sdkValid); + m_emSdkEnvGroupBox->setVisible(sdkValid); + + if (sdkValid) { + const QVersionNumber sdkVersion = WebAssemblyEmSdk::version(sdkPath); + const QVersionNumber minVersion = WebAssemblyToolChain::minimumSupportedEmSdkVersion(); + const bool versionTooLow = sdkVersion < minVersion; + m_emSdkVersionDisplay->setType(versionTooLow ? InfoLabel::NotOk : InfoLabel::Ok); + m_emSdkVersionDisplay->setText( + versionTooLow ? tr("The activated version %1 is not supported by %2." + "
Activate version %3 or higher.") + .arg(sdkVersion.toString(), Core::ICore::versionString(), + minVersion.toString()) + : tr("Activated version: %1").arg(sdkVersion.toString())); + m_emSdkEnvDisplay->setText(environmentDisplay(sdkPath)); + } + + m_qtVersionDisplay->setVisible(WebAssemblyQtVersion::isUnsupportedQtVersionInstalled()); +} + +void WebAssemblyOptionsWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event) + updateStatus(); +} + +void WebAssemblyOptionsWidget::apply() +{ + const FilePath sdkPath = m_emSdkPathChooser->filePath(); + if (!WebAssemblyEmSdk::isValid(sdkPath)) + return; + WebAssemblyEmSdk::registerEmSdk(sdkPath); + WebAssemblyToolChain::registerToolChains(); +} + +WebAssemblyOptionsPage::WebAssemblyOptionsPage() +{ + setId(Id(Constants::SETTINGS_ID)); + setDisplayName(WebAssemblyOptionsWidget::tr("WebAssembly")); + setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); + setWidgetCreator([] { return new WebAssemblyOptionsWidget; }); +} + +} // Internal +} // WebAssembly diff --git a/src/plugins/webassembly/webassemblyoptionspage.h b/src/plugins/webassembly/webassemblyoptionspage.h new file mode 100644 index 00000000000..04ebd9efb40 --- /dev/null +++ b/src/plugins/webassembly/webassemblyoptionspage.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace WebAssembly { +namespace Internal { + +class WebAssemblyOptionsPage final : public Core::IOptionsPage +{ +public: + WebAssemblyOptionsPage(); +}; + +} // namespace Internal +} // namespace WebAssmbly diff --git a/src/plugins/webassembly/webassemblyplugin.cpp b/src/plugins/webassembly/webassemblyplugin.cpp index 497efeb7222..527e6d8f549 100644 --- a/src/plugins/webassembly/webassemblyplugin.cpp +++ b/src/plugins/webassembly/webassemblyplugin.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -26,6 +26,7 @@ #include "webassemblyplugin.h" #include "webassemblyconstants.h" #include "webassemblydevice.h" +#include "webassemblyoptionspage.h" #include "webassemblyqtversion.h" #include "webassemblyrunconfiguration.h" #include "webassemblytoolchain.h" @@ -37,7 +38,13 @@ #include #include +#include + +#include + +using namespace Core; using namespace ProjectExplorer; +using namespace Utils; namespace WebAssembly { namespace Internal { @@ -54,6 +61,7 @@ public: {ProjectExplorer::Constants::NORMAL_RUN_MODE}, {Constants::WEBASSEMBLY_RUNCONFIGURATION_EMRUN} }; + WebAssemblyOptionsPage optionsPage; }; static WebAssemblyPluginPrivate *dd = nullptr; @@ -83,8 +91,29 @@ void WebAssemblyPlugin::extensionsInitialized() { connect(KitManager::instance(), &KitManager::kitsLoaded, this, [] { DeviceManager::instance()->addDevice(WebAssemblyDevice::create()); + askUserAboutEmSdkSetup(); }); } +void WebAssemblyPlugin::askUserAboutEmSdkSetup() +{ + const char setupWebAssemblyEmSdk[] = "SetupWebAssemblyEmSdk"; + + if (!ICore::infoBar()->canInfoBeAdded(setupWebAssemblyEmSdk) + || !WebAssemblyQtVersion::isQtVersionInstalled() + || WebAssemblyToolChain::areToolChainsRegistered()) + return; + + InfoBarEntry info(setupWebAssemblyEmSdk, + tr("Setup Emscripten SDK for WebAssembly? " + "To do it later, select Options > Devices > WebAssembly."), + InfoBarEntry::GlobalSuppression::Enabled); + info.setCustomButtonInfo(tr("Setup Emscripten SDK"), [setupWebAssemblyEmSdk] { + ICore::infoBar()->removeInfo(setupWebAssemblyEmSdk); + QTimer::singleShot(0, []() { ICore::showOptionsDialog(Constants::SETTINGS_ID); }); + }); + ICore::infoBar()->addInfo(info); +} + } // namespace Internal } // namespace WebAssembly diff --git a/src/plugins/webassembly/webassemblyplugin.h b/src/plugins/webassembly/webassemblyplugin.h index a6bbf1ce5c9..eedeb89d0a6 100644 --- a/src/plugins/webassembly/webassemblyplugin.h +++ b/src/plugins/webassembly/webassemblyplugin.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -43,6 +43,12 @@ public: bool initialize(const QStringList &arguments, QString *errorString) override; void extensionsInitialized() override; + static void askUserAboutEmSdkSetup(); + +#ifdef WITH_TESTS +private slots: + void testEmSdkEnvParsing(); +#endif // WITH_TESTS }; } // namespace Internal diff --git a/src/plugins/webassembly/webassemblyqtversion.cpp b/src/plugins/webassembly/webassemblyqtversion.cpp index f0d616a6e9d..14422c3ed98 100644 --- a/src/plugins/webassembly/webassemblyqtversion.cpp +++ b/src/plugins/webassembly/webassemblyqtversion.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -37,6 +39,10 @@ #include #include +#include + +using namespace QtSupport; +using namespace Utils; namespace WebAssembly { namespace Internal { @@ -49,7 +55,7 @@ QString WebAssemblyQtVersion::description() const "Qt Version is meant for WebAssembly"); } -QSet WebAssemblyQtVersion::targetDeviceTypes() const +QSet WebAssemblyQtVersion::targetDeviceTypes() const { return {Constants::WEBASSEMBLY_DEVICE_TYPE}; } @@ -64,5 +70,43 @@ WebAssemblyQtVersionFactory::WebAssemblyQtVersionFactory() }); } +bool WebAssemblyQtVersion::isValid() const +{ + return BaseQtVersion::isValid() && qtVersion() >= minimumSupportedQtVersion(); +} + +QString WebAssemblyQtVersion::invalidReason() const +{ + const QString baseReason = BaseQtVersion::invalidReason(); + if (!baseReason.isEmpty()) + return baseReason; + + return tr("%1 does not support Qt for WebAssembly below version %2.") + .arg(Core::ICore::versionString()) + .arg(QVersionNumber(minimumSupportedQtVersion().majorVersion, + minimumSupportedQtVersion().minorVersion).toString()); +} + +const QtVersionNumber &WebAssemblyQtVersion::minimumSupportedQtVersion() +{ + const static QtVersionNumber number(5, 15); + return number; +} + +bool WebAssemblyQtVersion::isQtVersionInstalled() +{ + return anyOf(QtVersionManager::versions(), [](const BaseQtVersion *v) { + return v->type() == Constants::WEBASSEMBLY_QT_VERSION; + }); +} + +bool WebAssemblyQtVersion::isUnsupportedQtVersionInstalled() +{ + return anyOf(QtVersionManager::versions(), [](const BaseQtVersion *v) { + return v->type() == Constants::WEBASSEMBLY_QT_VERSION + && v->qtVersion() < WebAssemblyQtVersion::minimumSupportedQtVersion(); + }); +} + } // namespace Internal } // namespace WebAssembly diff --git a/src/plugins/webassembly/webassemblyqtversion.h b/src/plugins/webassembly/webassemblyqtversion.h index 529ff07a05c..2d00aa719e1 100644 --- a/src/plugins/webassembly/webassemblyqtversion.h +++ b/src/plugins/webassembly/webassemblyqtversion.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -39,6 +39,13 @@ public: QString description() const override; QSet targetDeviceTypes() const override; + + bool isValid() const override; + QString invalidReason() const override; + + static const QtSupport::QtVersionNumber &minimumSupportedQtVersion(); + static bool isQtVersionInstalled(); + static bool isUnsupportedQtVersionInstalled(); }; class WebAssemblyQtVersionFactory : public QtSupport::QtVersionFactory diff --git a/src/plugins/webassembly/webassemblyrunconfiguration.cpp b/src/plugins/webassembly/webassemblyrunconfiguration.cpp index d2629342c96..8bef1249dcc 100644 --- a/src/plugins/webassembly/webassemblyrunconfiguration.cpp +++ b/src/plugins/webassembly/webassemblyrunconfiguration.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -43,11 +43,21 @@ namespace Internal { static CommandLine emrunCommand(Target *target, const QString &browser, const QString &port) { if (BuildConfiguration *bc = target->activeBuildConfiguration()) { - const QFileInfo emrunScript = bc->environment().searchInPath("emrun").toFileInfo(); + const QFileInfo emrun = bc->environment().searchInPath("emrun").toFileInfo(); auto html = bc->buildDirectory().pathAppended(target->project()->displayName() + ".html"); - return CommandLine(bc->environment().searchInPath("python"), { - emrunScript.absolutePath() + "/" + emrunScript.baseName() + ".py", + // On Windows, we need to use the python interpreter (it comes with the emsdk) to ensure + // that the web server is killed when the application is stopped in Qt Creator. + // On Non-windows, we prefer using the shell script, because that knows how to find the + // right python (not part of emsdk). The shell script stays attached to the server process. + const FilePath interpreter = bc->environment().searchInPath( + QLatin1String(HostOsInfo::isWindowsHost() ? "python" : "sh")); + const QString emrunLaunchScript = HostOsInfo::isWindowsHost() + ? emrun.absolutePath() + "/" + emrun.baseName() + ".py" + : emrun.absoluteFilePath(); + + return CommandLine(interpreter, { + emrunLaunchScript, "--browser", browser, "--port", port, "--no_emrun_detect", diff --git a/src/plugins/webassembly/webassemblytoolchain.cpp b/src/plugins/webassembly/webassemblytoolchain.cpp index 8369a91675f..c01c4aa06e0 100644 --- a/src/plugins/webassembly/webassemblytoolchain.cpp +++ b/src/plugins/webassembly/webassemblytoolchain.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -25,97 +25,42 @@ #include "webassemblytoolchain.h" #include "webassemblyconstants.h" +#include "webassemblyemsdk.h" -#include -#include -#include - -#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include #include +using namespace ProjectExplorer; +using namespace QtSupport; +using namespace Utils; + namespace WebAssembly { namespace Internal { -// See https://emscripten.org/docs/tools_reference/emsdk.html#compiler-configuration-file -struct CompilerConfiguration +static const Abi &toolChainAbi() { - Utils::FilePath emSdk; - Utils::FilePath llvmRoot; - Utils::FilePath emConfig; - Utils::FilePath emscriptenNativeOptimizer; - Utils::FilePath binaryEnRoot; - Utils::FilePath emSdkNode; - Utils::FilePath emSdkPython; - Utils::FilePath javaHome; - Utils::FilePath emScripten; -}; - -static Utils::FilePath compilerConfigurationFile() -{ - return Utils::FilePath::fromString(QDir::homePath() + "/.emscripten"); + static const Abi abi( + Abi::AsmJsArchitecture, + Abi::UnknownOS, + Abi::UnknownFlavor, + Abi::EmscriptenFormat, + 32); + return abi; } -static CompilerConfiguration compilerConfiguration() +static void addRegisteredMinGWToEnvironment(Environment &env) { - const QSettings configuration(compilerConfigurationFile().toString(), QSettings::IniFormat); - auto configPath = [&configuration](const QString &key){ - return Utils::FilePath::fromString(configuration.value(key).toString().remove('\'')); - }; - const Utils::FilePath llvmRoot = configPath("LLVM_ROOT"); - return { - llvmRoot.parentDir().parentDir(), - llvmRoot, - compilerConfigurationFile(), - configPath("EMSCRIPTEN_NATIVE_OPTIMIZER"), - configPath("BINARYEN_ROOT"), - configPath("NODE_JS"), - configPath("PYTHON"), - configPath("JAVA").parentDir().parentDir(), - configPath("EMSCRIPTEN_ROOT") - }; -} - -static ProjectExplorer::Abi toolChainAbi() -{ - return { - ProjectExplorer::Abi::AsmJsArchitecture, - ProjectExplorer::Abi::UnknownOS, - ProjectExplorer::Abi::UnknownFlavor, - ProjectExplorer::Abi::EmscriptenFormat, - 32 - }; -} - -static void addEmscriptenToEnvironment(Utils::Environment &env) -{ - const CompilerConfiguration configuration = compilerConfiguration(); - - env.prependOrSetPath(configuration.emScripten.toUserOutput()); - env.prependOrSetPath(configuration.javaHome.toUserOutput() + "/bin"); - env.prependOrSetPath(configuration.emSdkPython.parentDir().toUserOutput()); - env.prependOrSetPath(configuration.emSdkNode.parentDir().toUserOutput()); - env.prependOrSetPath(configuration.llvmRoot.toUserOutput()); - env.prependOrSetPath(configuration.emSdk.toUserOutput()); - - env.set("EMSDK", configuration.emSdk.toUserOutput()); - env.set("EM_CONFIG", configuration.emConfig.toUserOutput()); - env.set("LLVM_ROOT", configuration.llvmRoot.toUserOutput()); - env.set("EMSCRIPTEN_NATIVE_OPTIMIZER", configuration.emscriptenNativeOptimizer.toUserOutput()); - env.set("BINARYEN_ROOT", configuration.binaryEnRoot.toUserOutput()); - env.set("EMSDK_NODE", configuration.emSdkNode.toUserOutput()); - env.set("EMSDK_PYTHON", configuration.emSdkPython.toUserOutput()); - env.set("JAVA_HOME", configuration.javaHome.toUserOutput()); - env.set("EMSCRIPTEN", configuration.emScripten.toUserOutput()); -} - -static void addRegisteredMinGWToEnvironment(Utils::Environment &env) -{ - using namespace ProjectExplorer; const ToolChain *toolChain = ToolChainManager::toolChain([](const ToolChain *t){ return t->typeId() == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID; }); @@ -125,30 +70,86 @@ static void addRegisteredMinGWToEnvironment(Utils::Environment &env) } } -void WebAssemblyToolChain::addToEnvironment(Utils::Environment &env) const +void WebAssemblyToolChain::addToEnvironment(Environment &env) const { - addEmscriptenToEnvironment(env); - if (Utils::HostOsInfo::isWindowsHost()) + WebAssemblyEmSdk::addToEnvironment(WebAssemblyEmSdk::registeredEmSdk(), env); + if (HostOsInfo::isWindowsHost()) addRegisteredMinGWToEnvironment(env); } WebAssemblyToolChain::WebAssemblyToolChain() : - ClangToolChain(Constants::WEBASSEMBLY_TOOLCHAIN_TYPEID) + GccToolChain(Constants::WEBASSEMBLY_TOOLCHAIN_TYPEID) { - const CompilerConfiguration configuration = compilerConfiguration(); - const QString command = configuration.llvmRoot.toString() - + Utils::HostOsInfo::withExecutableSuffix("/clang"); - setCompilerCommand(Utils::FilePath::fromString(command)); setSupportedAbis({toolChainAbi()}); setTargetAbi(toolChainAbi()); - const QString typeAndDisplayName = tr("Emscripten Compiler"); - setDisplayName(typeAndDisplayName); - setTypeDisplayName(typeAndDisplayName); + setTypeDisplayName(tr("Emscripten Compiler")); +} + +FilePath WebAssemblyToolChain::makeCommand(const Environment &environment) const +{ + // Diverged duplicate of ClangToolChain::makeCommand and MingwToolChain::makeCommand + const QStringList makes + = HostOsInfo::isWindowsHost() ? QStringList({"mingw32-make.exe", "make.exe"}) + : QStringList({"make"}); + + FilePath tmp; + for (const QString &make : makes) { + tmp = environment.searchInPath(make); + if (!tmp.isEmpty()) + return tmp; + } + return FilePath::fromString(makes.first()); +} + +bool WebAssemblyToolChain::isValid() const +{ + return GccToolChain::isValid() + && QVersionNumber::fromString(version()) >= minimumSupportedEmSdkVersion(); +} + +const QVersionNumber &WebAssemblyToolChain::minimumSupportedEmSdkVersion() +{ + static const QVersionNumber number(1, 39); + return number; +} + +void WebAssemblyToolChain::registerToolChains() +{ + // Remove old toolchains + for (ToolChain *tc : ToolChainManager::findToolChains(toolChainAbi())) { + if (tc->detection() != ToolChain::AutoDetection) + continue; + ToolChainManager::deregisterToolChain(tc); + }; + + // Create new toolchains and register them + ToolChainFactory *factory = + findOrDefault(ToolChainFactory::allToolChainFactories(), [](ToolChainFactory *f){ + return f->supportedToolChainType() == Constants::WEBASSEMBLY_TOOLCHAIN_TYPEID; + }); + QTC_ASSERT(factory, return); + for (auto toolChain : factory->autoDetect({})) + ToolChainManager::registerToolChain(toolChain); + + // Let kits pick up the new toolchains + for (Kit *kit : KitManager::kits()) { + if (!kit->isAutoDetected()) + continue; + const BaseQtVersion *qtVersion = QtKitAspect::qtVersion(kit); + if (!qtVersion || qtVersion->type() != Constants::WEBASSEMBLY_QT_VERSION) + continue; + kit->fix(); + } +} + +bool WebAssemblyToolChain::areToolChainsRegistered() +{ + return !ToolChainManager::findToolChains(toolChainAbi()).isEmpty(); } WebAssemblyToolChainFactory::WebAssemblyToolChainFactory() { - setDisplayName(WebAssemblyToolChain::tr("WebAssembly")); + setDisplayName(WebAssemblyToolChain::tr("Emscripten")); setSupportedToolChainType(Constants::WEBASSEMBLY_TOOLCHAIN_TYPEID); setSupportedLanguages({ProjectExplorer::Constants::C_LANGUAGE_ID, ProjectExplorer::Constants::CXX_LANGUAGE_ID}); @@ -156,20 +157,36 @@ WebAssemblyToolChainFactory::WebAssemblyToolChainFactory() setUserCreatable(true); } -QList WebAssemblyToolChainFactory::autoDetect( - const QList &alreadyKnown) +QList WebAssemblyToolChainFactory::autoDetect( + const QList &alreadyKnown) { Q_UNUSED(alreadyKnown) - auto cToolChain = new WebAssemblyToolChain; - cToolChain->setLanguage(ProjectExplorer::Constants::C_LANGUAGE_ID); - cToolChain->setDetection(ProjectExplorer::ToolChain::AutoDetection); + const FilePath sdk = WebAssemblyEmSdk::registeredEmSdk(); + if (!WebAssemblyEmSdk::isValid(sdk)) + return {}; - auto cxxToolChain = new WebAssemblyToolChain; - cxxToolChain->setLanguage(ProjectExplorer::Constants::CXX_LANGUAGE_ID); - cxxToolChain->setDetection(ProjectExplorer::ToolChain::AutoDetection); + Environment env; + WebAssemblyEmSdk::addToEnvironment(sdk, env); - return {cToolChain, cxxToolChain}; + QList result; + for (auto languageId : {ProjectExplorer::Constants::C_LANGUAGE_ID, + ProjectExplorer::Constants::CXX_LANGUAGE_ID}) { + auto toolChain = new WebAssemblyToolChain; + toolChain->setLanguage(languageId); + toolChain->setDetection(ToolChain::AutoDetection); + const bool cLanguage = languageId == ProjectExplorer::Constants::C_LANGUAGE_ID; + const QString scriptFile = QLatin1String(cLanguage ? "emcc" : "em++") + + QLatin1String(HostOsInfo::isWindowsHost() ? ".bat" : ""); + toolChain->setCompilerCommand(env.searchInPath(scriptFile)); + + const QString displayName = WebAssemblyToolChain::tr("Emscripten Compiler %1 for %2") + .arg(toolChain->version(), QLatin1String(cLanguage ? "C" : "C++")); + toolChain->setDisplayName(displayName); + result.append(toolChain); + } + + return result; } } // namespace Internal diff --git a/src/plugins/webassembly/webassemblytoolchain.h b/src/plugins/webassembly/webassemblytoolchain.h index 9e01de8827c..efabcafdb48 100644 --- a/src/plugins/webassembly/webassemblytoolchain.h +++ b/src/plugins/webassembly/webassemblytoolchain.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -27,16 +27,25 @@ #include +#include + namespace WebAssembly { namespace Internal { -class WebAssemblyToolChain final : public ProjectExplorer::ClangToolChain +class WebAssemblyToolChain final : public ProjectExplorer::GccToolChain { Q_DECLARE_TR_FUNCTIONS(WebAssembly::Internal::WebAssemblyToolChain) public: void addToEnvironment(Utils::Environment &env) const override; + Utils::FilePath makeCommand(const Utils::Environment &environment) const override; + bool isValid() const override; + + static const QVersionNumber &minimumSupportedEmSdkVersion(); + static void registerToolChains(); + static bool areToolChainsRegistered(); + private: WebAssemblyToolChain();