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();