diff --git a/src/plugins/docker/CMakeLists.txt b/src/plugins/docker/CMakeLists.txt index e89c5b5ee23..3153b10bb1c 100644 --- a/src/plugins/docker/CMakeLists.txt +++ b/src/plugins/docker/CMakeLists.txt @@ -5,6 +5,8 @@ add_qtc_plugin(Docker dockerbuildstep.cpp dockerbuildstep.h dockerconstants.h dockerdevice.cpp dockerdevice.h + dockerdevicewidget.cpp dockerdevicewidget.h dockerplugin.cpp dockerplugin.h dockersettings.cpp dockersettings.h + kitdetector.cpp kitdetector.h ) diff --git a/src/plugins/docker/docker.qbs b/src/plugins/docker/docker.qbs index 60c31290630..3c6680604a1 100644 --- a/src/plugins/docker/docker.qbs +++ b/src/plugins/docker/docker.qbs @@ -12,15 +12,19 @@ QtcPlugin { files: [ "docker_global.h", - "dockerbuildstep.h", "dockerbuildstep.cpp", + "dockerbuildstep.h", "dockerconstants.h", - "dockerdevice.h", "dockerdevice.cpp", - "dockerplugin.h", + "dockerdevice.h", + "dockerdevicewidget.cpp", + "dockerdevicewidget.h", "dockerplugin.cpp", + "dockerplugin.h", + "dockersettings.cpp", "dockersettings.h", - "dockersettings.cpp" + "kitdetector.cpp", + "kitdetector.h", ] } diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 20f314a963f..6f2b5f712b4 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -27,6 +27,8 @@ #include "dockerconstants.h" #include "dockerplugin.h" +#include "dockerdevicewidget.h" +#include "kitdetector.h" #include @@ -185,61 +187,7 @@ class DockerPortsGatheringMethod : public PortsGatheringMethod } }; -class KitDetectorPrivate -{ - Q_DECLARE_TR_FUNCTIONS(ProjectExplorer::KitItemDetector) -public: - KitDetectorPrivate(KitDetector *parent, const IDevice::ConstPtr &device) - : q(parent), m_device(device) - {} - - void autoDetect(); - void undoAutoDetect() const; - void listAutoDetected() const; - - void setSharedId(const QString &sharedId) { m_sharedId = sharedId; } - void setSearchPaths(const FilePaths &searchPaths) { m_searchPaths = searchPaths; } - -private: - QtVersions autoDetectQtVersions() const; - QList autoDetectToolChains(); - void autoDetectCMake(); - void autoDetectDebugger(); - - KitDetector *q; - IDevice::ConstPtr m_device; - QString m_sharedId; - FilePaths m_searchPaths; -}; - -KitDetector::KitDetector(const IDevice::ConstPtr &device) - : d(new KitDetectorPrivate(this, device)) -{} - -KitDetector::~KitDetector() -{ - delete d; -} - -void KitDetector::autoDetect(const QString &sharedId, const FilePaths &searchPaths) const -{ - d->setSharedId(sharedId); - d->setSearchPaths(searchPaths); - d->autoDetect(); -} - -void KitDetector::undoAutoDetect(const QString &sharedId) const -{ - d->setSharedId(sharedId); - d->undoAutoDetect(); -} - -void KitDetector::listAutoDetected(const QString &sharedId) const -{ - d->setSharedId(sharedId); - d->listAutoDetected(); -} class DockerDevicePrivate : public QObject { @@ -274,191 +222,6 @@ public: bool m_useFind = true; // prefer find over ls and hacks, but be able to use ls as fallback }; -class DockerDeviceWidget final : public IDeviceWidget -{ - Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice) - -public: - explicit DockerDeviceWidget(const IDevice::Ptr &device) - : IDeviceWidget(device), m_kitItemDetector(device) - { - auto dockerDevice = device.dynamicCast(); - QTC_ASSERT(dockerDevice, return); - - DockerDeviceData &data = dockerDevice->data(); - - auto repoLabel = new QLabel(tr("Repository:")); - m_repoLineEdit = new QLineEdit; - m_repoLineEdit->setText(data.repo); - m_repoLineEdit->setEnabled(false); - - auto tagLabel = new QLabel(tr("Tag:")); - m_tagLineEdit = new QLineEdit; - m_tagLineEdit->setText(data.tag); - m_tagLineEdit->setEnabled(false); - - auto idLabel = new QLabel(tr("Image ID:")); - m_idLineEdit = new QLineEdit; - m_idLineEdit->setText(data.imageId); - m_idLineEdit->setEnabled(false); - - auto daemonStateLabel = new QLabel(tr("Daemon state:")); - m_daemonReset = new QToolButton; - m_daemonReset->setToolTip(tr("Clears detected daemon state. " - "It will be automatically re-evaluated next time access is needed.")); - - m_daemonState = new QLabel; - updateDaemonStateTexts(); - - connect(m_daemonReset, &QToolButton::clicked, this, [this, dockerDevice] { - DockerPlugin::setGlobalDaemonState(Utils::nullopt); - updateDaemonStateTexts(); - }); - - m_runAsOutsideUser = new QCheckBox(tr("Run as outside user")); - m_runAsOutsideUser->setToolTip(tr("Uses user ID and group ID of the user running Qt Creator " - "in the docker container.")); - m_runAsOutsideUser->setChecked(data.useLocalUidGid); - m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); - - connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [&data](bool on) { - data.useLocalUidGid = on; - }); - - m_pathsListLabel = new InfoLabel(tr("Paths to mount:")); - // FIXME: 8.0: use - //m_pathsListLabel->setToolTip(tr("Source directory list should not be empty")); - - m_pathsListEdit = new PathListEditor; - // FIXME: 8.0: use - //m_pathsListEdit->setPlaceholderText(tr("Host directories to mount into the container")); - m_pathsListEdit->setToolTip(tr("Maps paths in this list one-to-one to the " - "docker container.")); - m_pathsListEdit->setPathList(data.mounts); - - auto markupMounts = [this] { - const bool isEmpty = m_pathsListEdit->pathList().isEmpty(); - m_pathsListLabel->setType(isEmpty ? InfoLabel::Warning : InfoLabel::None); - }; - markupMounts(); - - connect(m_pathsListEdit, &PathListEditor::changed, this, [dockerDevice, markupMounts, this] { - dockerDevice->setMounts(m_pathsListEdit->pathList()); - markupMounts(); - }); - - auto logView = new QTextBrowser; - connect(&m_kitItemDetector, &KitDetector::logOutput, - logView, &QTextBrowser::append); - - auto autoDetectButton = new QPushButton(tr("Auto-detect Kit Items")); - auto undoAutoDetectButton = new QPushButton(tr("Remove Auto-Detected Kit Items")); - auto listAutoDetectedButton = new QPushButton(tr("List Auto-Detected Kit Items")); - - auto searchDirsComboBox = new QComboBox; - searchDirsComboBox->addItem(tr("Search in PATH")); - searchDirsComboBox->addItem(tr("Search in Selected Directories")); - - auto searchDirsLineEdit = new FancyLineEdit; - // FIXME: 8.0: use - //searchDirsLineEdit->setPlaceholderText(tr("Semicolon-separated list of directories")); - searchDirsLineEdit->setToolTip( - tr("Select the paths in the docker image that should be scanned for kit entries.")); - searchDirsLineEdit->setHistoryCompleter("DockerMounts", true); - - auto searchPaths = [searchDirsComboBox, searchDirsLineEdit, dockerDevice] { - FilePaths paths; - if (searchDirsComboBox->currentIndex() == 0) { - paths = dockerDevice->systemEnvironment().path(); - } else { - for (const QString &path : searchDirsLineEdit->text().split(';')) - paths.append(FilePath::fromString(path.trimmed())); - } - paths = Utils::transform(paths, [dockerDevice](const FilePath &path) { - return dockerDevice->mapToGlobalPath(path); - }); - return paths; - }; - - connect(autoDetectButton, &QPushButton::clicked, this, - [this, logView, dockerDevice, searchPaths] { - logView->clear(); - dockerDevice->updateContainerAccess(); - - m_kitItemDetector.autoDetect(dockerDevice->id().toString(), searchPaths()); - - if (DockerPlugin::isDaemonRunning().value_or(false) == false) - logView->append(tr("Docker daemon appears to be not running.")); - else - logView->append(tr("Docker daemon appears to be running.")); - updateDaemonStateTexts(); - }); - - connect(undoAutoDetectButton, &QPushButton::clicked, this, [this, logView, device] { - logView->clear(); - m_kitItemDetector.undoAutoDetect(device->id().toString()); - }); - - connect(listAutoDetectedButton, &QPushButton::clicked, this, [this, logView, device] { - logView->clear(); - m_kitItemDetector.listAutoDetected(device->id().toString()); - }); - - using namespace Layouting; - - Form { - repoLabel, m_repoLineEdit, Break(), - tagLabel, m_tagLineEdit, Break(), - idLabel, m_idLineEdit, Break(), - daemonStateLabel, m_daemonReset, m_daemonState, Break(), - m_runAsOutsideUser, Break(), - Column { - m_pathsListLabel, - m_pathsListEdit, - }, Break(), - Column { - Space(20), - Row { - searchDirsComboBox, - searchDirsLineEdit - }, - Row { - autoDetectButton, - undoAutoDetectButton, - listAutoDetectedButton, - Stretch(), - }, - new QLabel(tr("Detection log:")), - logView - } - }.attachTo(this); - - searchDirsLineEdit->setVisible(false); - auto updateDirectoriesLineEdit = [searchDirsLineEdit](int index) { - searchDirsLineEdit->setVisible(index == 1); - if (index == 1) - searchDirsLineEdit->setFocus(); - }; - QObject::connect(searchDirsComboBox, qOverload(&QComboBox::activated), - this, updateDirectoriesLineEdit); - } - - void updateDeviceFromUi() final {} - void updateDaemonStateTexts(); - -private: - QLineEdit *m_repoLineEdit; - QLineEdit *m_tagLineEdit; - QLineEdit *m_idLineEdit; - QToolButton *m_daemonReset; - QLabel *m_daemonState; - QCheckBox *m_runAsOutsideUser; - InfoLabel *m_pathsListLabel; - PathListEditor *m_pathsListEdit; - - KitDetector m_kitItemDetector; -}; - IDeviceWidget *DockerDevice::createWidget() { return new DockerDeviceWidget(sharedFromThis()); @@ -476,7 +239,6 @@ Tasks DockerDevice::validate() const return result; } - // DockerDeviceData QString DockerDeviceData::repoAndTag() const @@ -547,236 +309,7 @@ DockerDeviceData &DockerDevice::data() return d->m_data; } -void KitDetectorPrivate::undoAutoDetect() const -{ - emit q->logOutput(tr("Start removing auto-detected items associated with this docker image.")); - emit q->logOutput('\n' + tr("Removing kits...")); - for (Kit *kit : KitManager::kits()) { - if (kit->autoDetectionSource() == m_sharedId) { - emit q->logOutput(tr("Removed \"%1\"").arg(kit->displayName())); - KitManager::deregisterKit(kit); - } - }; - - emit q->logOutput('\n' + tr("Removing Qt version entries...")); - for (QtVersion *qtVersion : QtVersionManager::versions()) { - if (qtVersion->detectionSource() == m_sharedId) { - emit q->logOutput(tr("Removed \"%1\"").arg(qtVersion->displayName())); - QtVersionManager::removeVersion(qtVersion); - } - }; - - emit q->logOutput('\n' + tr("Removing toolchain entries...")); - const Toolchains toolchains = ToolChainManager::toolchains(); - for (ToolChain *toolChain : toolchains) { - if (toolChain && toolChain->detectionSource() == m_sharedId) { - emit q->logOutput(tr("Removed \"%1\"").arg(toolChain->displayName())); - ToolChainManager::deregisterToolChain(toolChain); - } - }; - - if (QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager")) { - QString logMessage; - const bool res = QMetaObject::invokeMethod(cmakeManager, - "removeDetectedCMake", - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); - } - - if (QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin")) { - QString logMessage; - const bool res = QMetaObject::invokeMethod(debuggerPlugin, - "removeDetectedDebuggers", - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); - } - - emit q->logOutput('\n' + tr("Removal of previously auto-detected kit items finished.") + "\n\n"); -} - -void KitDetectorPrivate::listAutoDetected() const -{ - emit q->logOutput(tr("Start listing auto-detected items associated with this docker image.")); - - emit q->logOutput('\n' + tr("Kits:")); - for (Kit *kit : KitManager::kits()) { - if (kit->autoDetectionSource() == m_sharedId) - emit q->logOutput(kit->displayName()); - }; - - emit q->logOutput('\n' + tr("Qt versions:")); - for (QtVersion *qtVersion : QtVersionManager::versions()) { - if (qtVersion->detectionSource() == m_sharedId) - emit q->logOutput(qtVersion->displayName()); - }; - - emit q->logOutput('\n' + tr("Toolchains:")); - for (ToolChain *toolChain : ToolChainManager::toolchains()) { - if (toolChain->detectionSource() == m_sharedId) - emit q->logOutput(toolChain->displayName()); - }; - - if (QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager")) { - QString logMessage; - const bool res = QMetaObject::invokeMethod(cmakeManager, - "listDetectedCMake", - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); - } - - if (QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin")) { - QString logMessage; - const bool res = QMetaObject::invokeMethod(debuggerPlugin, - "listDetectedDebuggers", - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); - } - - emit q->logOutput('\n' + tr("Listing of previously auto-detected kit items finished.") + "\n\n"); -} - -QtVersions KitDetectorPrivate::autoDetectQtVersions() const -{ - QtVersions qtVersions; - - QString error; - - const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) { - if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake, false, m_sharedId, &error)) { - qtVersions.append(qtVersion); - QtVersionManager::addVersion(qtVersion); - emit q->logOutput(tr("Found \"%1\"").arg(qtVersion->qmakeFilePath().toUserOutput())); - } - return true; - }; - - emit q->logOutput(tr("Searching for qmake executables...")); - - const QStringList candidates = {"qmake-qt6", "qmake-qt5", "qmake"}; - for (const FilePath &searchPath : m_searchPaths) { - searchPath.iterateDirectory(handleQmake, {candidates, QDir::Files | QDir::Executable, - QDirIterator::Subdirectories}); - } - - if (!error.isEmpty()) - emit q->logOutput(tr("Error: %1.").arg(error)); - if (qtVersions.isEmpty()) - emit q->logOutput(tr("No Qt installation found.")); - return qtVersions; -} - -Toolchains KitDetectorPrivate::autoDetectToolChains() -{ - const QList factories = ToolChainFactory::allToolChainFactories(); - - Toolchains alreadyKnown = ToolChainManager::toolchains(); - Toolchains allNewToolChains; - QApplication::processEvents(); - emit q->logOutput('\n' + tr("Searching toolchains...")); - for (ToolChainFactory *factory : factories) { - emit q->logOutput(tr("Searching toolchains of type %1").arg(factory->displayName())); - const ToolchainDetector detector(alreadyKnown, m_device, m_searchPaths); - const Toolchains newToolChains = factory->autoDetect(detector); - for (ToolChain *toolChain : newToolChains) { - emit q->logOutput(tr("Found \"%1\"").arg(toolChain->compilerCommand().toUserOutput())); - toolChain->setDetectionSource(m_sharedId); - ToolChainManager::registerToolChain(toolChain); - alreadyKnown.append(toolChain); - } - allNewToolChains.append(newToolChains); - } - emit q->logOutput(tr("%1 new toolchains found.").arg(allNewToolChains.size())); - - return allNewToolChains; -} - -void KitDetectorPrivate::autoDetectCMake() -{ - QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager"); - if (!cmakeManager) - return; - - QString logMessage; - const bool res = QMetaObject::invokeMethod(cmakeManager, - "autoDetectCMakeForDevice", - Q_ARG(Utils::FilePaths, m_searchPaths), - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); -} - -void KitDetectorPrivate::autoDetectDebugger() -{ - QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin"); - if (!debuggerPlugin) - return; - - QString logMessage; - const bool res = QMetaObject::invokeMethod(debuggerPlugin, - "autoDetectDebuggersForDevice", - Q_ARG(Utils::FilePaths, m_searchPaths), - Q_ARG(QString, m_sharedId), - Q_ARG(QString *, &logMessage)); - QTC_CHECK(res); - emit q->logOutput('\n' + logMessage); -} - -void KitDetectorPrivate::autoDetect() -{ - QApplication::setOverrideCursor(Qt::WaitCursor); - - undoAutoDetect(); - - emit q->logOutput(tr("Starting auto-detection. This will take a while...")); - - const Toolchains toolchains = autoDetectToolChains(); - const QtVersions qtVersions = autoDetectQtVersions(); - - autoDetectCMake(); - autoDetectDebugger(); - - const auto initializeKit = [this, toolchains, qtVersions](Kit *k) { - k->setAutoDetected(false); - k->setAutoDetectionSource(m_sharedId); - k->setUnexpandedDisplayName("%{Device:Name}"); - - DeviceTypeKitAspect::setDeviceTypeId(k, Constants::DOCKER_DEVICE_TYPE); - DeviceKitAspect::setDevice(k, m_device); - - QtVersion *qt = nullptr; - if (!qtVersions.isEmpty()) { - qt = qtVersions.at(0); - QtSupport::QtKitAspect::setQtVersion(k, qt); - } - Toolchains toolchainsToSet; - toolchainsToSet = ToolChainManager::toolchains([qt, this](const ToolChain *tc){ - return tc->detectionSource() == m_sharedId - && (!qt || qt->qtAbis().contains(tc->targetAbi())); - }); - for (ToolChain *toolChain : toolchainsToSet) - ToolChainKitAspect::setToolChain(k, toolChain); - - k->setSticky(ToolChainKitAspect::id(), true); - k->setSticky(QtSupport::QtKitAspect::id(), true); - k->setSticky(DeviceKitAspect::id(), true); - k->setSticky(DeviceTypeKitAspect::id(), true); - }; - - Kit *kit = KitManager::registerKit(initializeKit); - emit q->logOutput('\n' + tr("Registered kit %1").arg(kit->displayName())); - - QApplication::restoreOverrideCursor(); -} void DockerDevice::updateContainerAccess() const { @@ -1662,21 +1195,6 @@ public: QString m_selectedId; }; -void DockerDeviceWidget::updateDaemonStateTexts() -{ - Utils::optional daemonState = DockerPlugin::isDaemonRunning(); - if (!daemonState.has_value()) { - m_daemonReset->setIcon(Icons::INFO.icon()); - m_daemonState->setText(tr("Daemon state not evaluated.")); - } else if (daemonState.value()) { - m_daemonReset->setIcon(Icons::OK.icon()); - m_daemonState->setText(tr("Docker daemon running.")); - } else { - m_daemonReset->setIcon(Icons::CRITICAL.icon()); - m_daemonState->setText(tr("Docker daemon not running.")); - } -} - // Factory DockerDeviceFactory::DockerDeviceFactory() diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 106325cd249..a430d90ebbc 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -129,25 +129,6 @@ private: friend class DockerDeviceWidget; }; -class KitDetector : public QObject -{ - Q_OBJECT - -public: - explicit KitDetector(const ProjectExplorer::IDevice::ConstPtr &device); - ~KitDetector() override; - - void autoDetect(const QString &sharedId, const Utils::FilePaths &selectedPaths) const; - void undoAutoDetect(const QString &sharedId) const; - void listAutoDetected(const QString &sharedId) const; - -signals: - void logOutput(const QString &msg); - -private: - class KitDetectorPrivate *d = nullptr; -}; - class DockerDeviceFactory final : public ProjectExplorer::IDeviceFactory { public: diff --git a/src/plugins/docker/dockerdevicewidget.cpp b/src/plugins/docker/dockerdevicewidget.cpp new file mode 100644 index 00000000000..5409bc31d12 --- /dev/null +++ b/src/plugins/docker/dockerdevicewidget.cpp @@ -0,0 +1,225 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "dockerdevicewidget.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace Docker { +namespace Internal { + +DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device) + : IDeviceWidget(device), m_kitItemDetector(device) +{ + auto dockerDevice = device.dynamicCast(); + QTC_ASSERT(dockerDevice, return); + + DockerDeviceData &data = dockerDevice->data(); + + auto repoLabel = new QLabel(tr("Repository:")); + m_repoLineEdit = new QLineEdit; + m_repoLineEdit->setText(data.repo); + m_repoLineEdit->setEnabled(false); + + auto tagLabel = new QLabel(tr("Tag:")); + m_tagLineEdit = new QLineEdit; + m_tagLineEdit->setText(data.tag); + m_tagLineEdit->setEnabled(false); + + auto idLabel = new QLabel(tr("Image ID:")); + m_idLineEdit = new QLineEdit; + m_idLineEdit->setText(data.imageId); + m_idLineEdit->setEnabled(false); + + auto daemonStateLabel = new QLabel(tr("Daemon state:")); + m_daemonReset = new QToolButton; + m_daemonReset->setToolTip(tr("Clears detected daemon state. " + "It will be automatically re-evaluated next time access is needed.")); + + m_daemonState = new QLabel; + updateDaemonStateTexts(); + + connect(m_daemonReset, &QToolButton::clicked, this, [this, dockerDevice] { + DockerPlugin::setGlobalDaemonState(Utils::nullopt); + updateDaemonStateTexts(); + }); + + m_runAsOutsideUser = new QCheckBox(tr("Run as outside user")); + m_runAsOutsideUser->setToolTip(tr("Uses user ID and group ID of the user running Qt Creator " + "in the docker container.")); + m_runAsOutsideUser->setChecked(data.useLocalUidGid); + m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); + + connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [&data](bool on) { + data.useLocalUidGid = on; + }); + + m_pathsListLabel = new InfoLabel(tr("Paths to mount:")); + // FIXME: 8.0: use + //m_pathsListLabel->setToolTip(tr("Source directory list should not be empty")); + + m_pathsListEdit = new PathListEditor; + // FIXME: 8.0: use + //m_pathsListEdit->setPlaceholderText(tr("Host directories to mount into the container")); + m_pathsListEdit->setToolTip(tr("Maps paths in this list one-to-one to the " + "docker container.")); + m_pathsListEdit->setPathList(data.mounts); + + auto markupMounts = [this] { + const bool isEmpty = m_pathsListEdit->pathList().isEmpty(); + m_pathsListLabel->setType(isEmpty ? InfoLabel::Warning : InfoLabel::None); + }; + markupMounts(); + + connect(m_pathsListEdit, &PathListEditor::changed, this, [dockerDevice, markupMounts, this] { + dockerDevice->setMounts(m_pathsListEdit->pathList()); + markupMounts(); + }); + + auto logView = new QTextBrowser; + connect(&m_kitItemDetector, &KitDetector::logOutput, + logView, &QTextBrowser::append); + + auto autoDetectButton = new QPushButton(tr("Auto-detect Kit Items")); + auto undoAutoDetectButton = new QPushButton(tr("Remove Auto-Detected Kit Items")); + auto listAutoDetectedButton = new QPushButton(tr("List Auto-Detected Kit Items")); + + auto searchDirsComboBox = new QComboBox; + searchDirsComboBox->addItem(tr("Search in PATH")); + searchDirsComboBox->addItem(tr("Search in Selected Directories")); + + auto searchDirsLineEdit = new FancyLineEdit; + // FIXME: 8.0: use + //searchDirsLineEdit->setPlaceholderText(tr("Semicolon-separated list of directories")); + searchDirsLineEdit->setToolTip( + tr("Select the paths in the docker image that should be scanned for kit entries.")); + searchDirsLineEdit->setHistoryCompleter("DockerMounts", true); + + auto searchPaths = [searchDirsComboBox, searchDirsLineEdit, dockerDevice] { + FilePaths paths; + if (searchDirsComboBox->currentIndex() == 0) { + paths = dockerDevice->systemEnvironment().path(); + } else { + for (const QString &path : searchDirsLineEdit->text().split(';')) + paths.append(FilePath::fromString(path.trimmed())); + } + paths = Utils::transform(paths, [dockerDevice](const FilePath &path) { + return dockerDevice->mapToGlobalPath(path); + }); + return paths; + }; + + connect(autoDetectButton, &QPushButton::clicked, this, + [this, logView, dockerDevice, searchPaths] { + logView->clear(); + dockerDevice->updateContainerAccess(); + + m_kitItemDetector.autoDetect(dockerDevice->id().toString(), searchPaths()); + + if (DockerPlugin::isDaemonRunning().value_or(false) == false) + logView->append(tr("Docker daemon appears to be not running.")); + else + logView->append(tr("Docker daemon appears to be running.")); + updateDaemonStateTexts(); + }); + + connect(undoAutoDetectButton, &QPushButton::clicked, this, [this, logView, device] { + logView->clear(); + m_kitItemDetector.undoAutoDetect(device->id().toString()); + }); + + connect(listAutoDetectedButton, &QPushButton::clicked, this, [this, logView, device] { + logView->clear(); + m_kitItemDetector.listAutoDetected(device->id().toString()); + }); + + using namespace Layouting; + + Form { + repoLabel, m_repoLineEdit, Break(), + tagLabel, m_tagLineEdit, Break(), + idLabel, m_idLineEdit, Break(), + daemonStateLabel, m_daemonReset, m_daemonState, Break(), + m_runAsOutsideUser, Break(), + Column { + m_pathsListLabel, + m_pathsListEdit, + }, Break(), + Column { + Space(20), + Row { + searchDirsComboBox, + searchDirsLineEdit + }, + Row { + autoDetectButton, + undoAutoDetectButton, + listAutoDetectedButton, + Stretch(), + }, + new QLabel(tr("Detection log:")), + logView + } + }.attachTo(this); + + searchDirsLineEdit->setVisible(false); + auto updateDirectoriesLineEdit = [searchDirsLineEdit](int index) { + searchDirsLineEdit->setVisible(index == 1); + if (index == 1) + searchDirsLineEdit->setFocus(); + }; + QObject::connect(searchDirsComboBox, qOverload(&QComboBox::activated), + this, updateDirectoriesLineEdit); +} + +void DockerDeviceWidget::updateDaemonStateTexts() +{ + Utils::optional daemonState = DockerPlugin::isDaemonRunning(); + if (!daemonState.has_value()) { + m_daemonReset->setIcon(Icons::INFO.icon()); + m_daemonState->setText(tr("Daemon state not evaluated.")); + } else if (daemonState.value()) { + m_daemonReset->setIcon(Icons::OK.icon()); + m_daemonState->setText(tr("Docker daemon running.")); + } else { + m_daemonReset->setIcon(Icons::CRITICAL.icon()); + m_daemonState->setText(tr("Docker daemon not running.")); + } +} + +} // Internal +} // Docker diff --git a/src/plugins/docker/dockerdevicewidget.h b/src/plugins/docker/dockerdevicewidget.h new file mode 100644 index 00000000000..78dc2d4e6cd --- /dev/null +++ b/src/plugins/docker/dockerdevicewidget.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "dockerplugin.h" +#include "dockerdevice.h" +#include "kitdetector.h" + +#include + +#include + +#include + +namespace Docker { +namespace Internal { + +class DockerDeviceWidget final : public ProjectExplorer::IDeviceWidget +{ + Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice) + +public: + explicit DockerDeviceWidget(const ProjectExplorer::IDevice::Ptr &device); + + void updateDeviceFromUi() final {} + void updateDaemonStateTexts(); + +private: + QLineEdit *m_repoLineEdit; + QLineEdit *m_tagLineEdit; + QLineEdit *m_idLineEdit; + QToolButton *m_daemonReset; + QLabel *m_daemonState; + QCheckBox *m_runAsOutsideUser; + Utils::InfoLabel *m_pathsListLabel; + Utils::PathListEditor *m_pathsListEdit; + + KitDetector m_kitItemDetector; +}; + +} // Internal +} // Docker diff --git a/src/plugins/docker/kitdetector.cpp b/src/plugins/docker/kitdetector.cpp new file mode 100644 index 00000000000..48262cdd8ae --- /dev/null +++ b/src/plugins/docker/kitdetector.cpp @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "kitdetector.h" +#include "dockerconstants.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace ProjectExplorer; +using namespace QtSupport; +using namespace Utils; + +namespace Docker { +namespace Internal { + +class KitDetectorPrivate +{ + Q_DECLARE_TR_FUNCTIONS(ProjectExplorer::KitItemDetector) + +public: + KitDetectorPrivate(KitDetector *parent, const IDevice::ConstPtr &device) + : q(parent), m_device(device) + {} + + void autoDetect(); + void undoAutoDetect() const; + void listAutoDetected() const; + + void setSharedId(const QString &sharedId) { m_sharedId = sharedId; } + void setSearchPaths(const FilePaths &searchPaths) { m_searchPaths = searchPaths; } + +private: + QtVersions autoDetectQtVersions() const; + QList autoDetectToolChains(); + void autoDetectCMake(); + void autoDetectDebugger(); + + KitDetector *q; + IDevice::ConstPtr m_device; + QString m_sharedId; + FilePaths m_searchPaths; +}; + +KitDetector::KitDetector(const IDevice::ConstPtr &device) + : d(new KitDetectorPrivate(this, device)) +{} + +KitDetector::~KitDetector() +{ + delete d; +} + +void KitDetector::autoDetect(const QString &sharedId, const FilePaths &searchPaths) const +{ + d->setSharedId(sharedId); + d->setSearchPaths(searchPaths); + d->autoDetect(); +} + +void KitDetector::undoAutoDetect(const QString &sharedId) const +{ + d->setSharedId(sharedId); + d->undoAutoDetect(); +} + +void KitDetector::listAutoDetected(const QString &sharedId) const +{ + d->setSharedId(sharedId); + d->listAutoDetected(); +} + +void KitDetectorPrivate::undoAutoDetect() const +{ + emit q->logOutput(tr("Start removing auto-detected items associated with this docker image.")); + + emit q->logOutput('\n' + tr("Removing kits...")); + for (Kit *kit : KitManager::kits()) { + if (kit->autoDetectionSource() == m_sharedId) { + emit q->logOutput(tr("Removed \"%1\"").arg(kit->displayName())); + KitManager::deregisterKit(kit); + } + }; + + emit q->logOutput('\n' + tr("Removing Qt version entries...")); + for (QtVersion *qtVersion : QtVersionManager::versions()) { + if (qtVersion->detectionSource() == m_sharedId) { + emit q->logOutput(tr("Removed \"%1\"").arg(qtVersion->displayName())); + QtVersionManager::removeVersion(qtVersion); + } + }; + + emit q->logOutput('\n' + tr("Removing toolchain entries...")); + const Toolchains toolchains = ToolChainManager::toolchains(); + for (ToolChain *toolChain : toolchains) { + if (toolChain && toolChain->detectionSource() == m_sharedId) { + emit q->logOutput(tr("Removed \"%1\"").arg(toolChain->displayName())); + ToolChainManager::deregisterToolChain(toolChain); + } + }; + + if (QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager")) { + QString logMessage; + const bool res = QMetaObject::invokeMethod(cmakeManager, + "removeDetectedCMake", + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); + } + + if (QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin")) { + QString logMessage; + const bool res = QMetaObject::invokeMethod(debuggerPlugin, + "removeDetectedDebuggers", + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); + } + + emit q->logOutput('\n' + tr("Removal of previously auto-detected kit items finished.") + "\n\n"); +} + +void KitDetectorPrivate::listAutoDetected() const +{ + emit q->logOutput(tr("Start listing auto-detected items associated with this docker image.")); + + emit q->logOutput('\n' + tr("Kits:")); + for (Kit *kit : KitManager::kits()) { + if (kit->autoDetectionSource() == m_sharedId) + emit q->logOutput(kit->displayName()); + }; + + emit q->logOutput('\n' + tr("Qt versions:")); + for (QtVersion *qtVersion : QtVersionManager::versions()) { + if (qtVersion->detectionSource() == m_sharedId) + emit q->logOutput(qtVersion->displayName()); + }; + + emit q->logOutput('\n' + tr("Toolchains:")); + for (ToolChain *toolChain : ToolChainManager::toolchains()) { + if (toolChain->detectionSource() == m_sharedId) + emit q->logOutput(toolChain->displayName()); + }; + + if (QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager")) { + QString logMessage; + const bool res = QMetaObject::invokeMethod(cmakeManager, + "listDetectedCMake", + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); + } + + if (QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin")) { + QString logMessage; + const bool res = QMetaObject::invokeMethod(debuggerPlugin, + "listDetectedDebuggers", + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); + } + + emit q->logOutput('\n' + tr("Listing of previously auto-detected kit items finished.") + "\n\n"); +} + +QtVersions KitDetectorPrivate::autoDetectQtVersions() const +{ + QtVersions qtVersions; + + QString error; + + const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) { + if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake, false, m_sharedId, &error)) { + qtVersions.append(qtVersion); + QtVersionManager::addVersion(qtVersion); + emit q->logOutput(tr("Found \"%1\"").arg(qtVersion->qmakeFilePath().toUserOutput())); + } + return true; + }; + + emit q->logOutput(tr("Searching for qmake executables...")); + + const QStringList candidates = {"qmake-qt6", "qmake-qt5", "qmake"}; + for (const FilePath &searchPath : m_searchPaths) { + searchPath.iterateDirectory(handleQmake, {candidates, QDir::Files | QDir::Executable, + QDirIterator::Subdirectories}); + } + + if (!error.isEmpty()) + emit q->logOutput(tr("Error: %1.").arg(error)); + if (qtVersions.isEmpty()) + emit q->logOutput(tr("No Qt installation found.")); + return qtVersions; +} + +Toolchains KitDetectorPrivate::autoDetectToolChains() +{ + const QList factories = ToolChainFactory::allToolChainFactories(); + + Toolchains alreadyKnown = ToolChainManager::toolchains(); + Toolchains allNewToolChains; + QApplication::processEvents(); + emit q->logOutput('\n' + tr("Searching toolchains...")); + for (ToolChainFactory *factory : factories) { + emit q->logOutput(tr("Searching toolchains of type %1").arg(factory->displayName())); + const ToolchainDetector detector(alreadyKnown, m_device, m_searchPaths); + const Toolchains newToolChains = factory->autoDetect(detector); + for (ToolChain *toolChain : newToolChains) { + emit q->logOutput(tr("Found \"%1\"").arg(toolChain->compilerCommand().toUserOutput())); + toolChain->setDetectionSource(m_sharedId); + ToolChainManager::registerToolChain(toolChain); + alreadyKnown.append(toolChain); + } + allNewToolChains.append(newToolChains); + } + emit q->logOutput(tr("%1 new toolchains found.").arg(allNewToolChains.size())); + + return allNewToolChains; +} + +void KitDetectorPrivate::autoDetectCMake() +{ + QObject *cmakeManager = ExtensionSystem::PluginManager::getObjectByName("CMakeToolManager"); + if (!cmakeManager) + return; + + QString logMessage; + const bool res = QMetaObject::invokeMethod(cmakeManager, + "autoDetectCMakeForDevice", + Q_ARG(Utils::FilePaths, m_searchPaths), + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); +} + +void KitDetectorPrivate::autoDetectDebugger() +{ + QObject *debuggerPlugin = ExtensionSystem::PluginManager::getObjectByName("DebuggerPlugin"); + if (!debuggerPlugin) + return; + + QString logMessage; + const bool res = QMetaObject::invokeMethod(debuggerPlugin, + "autoDetectDebuggersForDevice", + Q_ARG(Utils::FilePaths, m_searchPaths), + Q_ARG(QString, m_sharedId), + Q_ARG(QString *, &logMessage)); + QTC_CHECK(res); + emit q->logOutput('\n' + logMessage); +} + +void KitDetectorPrivate::autoDetect() +{ + QApplication::setOverrideCursor(Qt::WaitCursor); + + undoAutoDetect(); + + emit q->logOutput(tr("Starting auto-detection. This will take a while...")); + + const Toolchains toolchains = autoDetectToolChains(); + const QtVersions qtVersions = autoDetectQtVersions(); + + autoDetectCMake(); + autoDetectDebugger(); + + const auto initializeKit = [this, toolchains, qtVersions](Kit *k) { + k->setAutoDetected(false); + k->setAutoDetectionSource(m_sharedId); + k->setUnexpandedDisplayName("%{Device:Name}"); + + DeviceTypeKitAspect::setDeviceTypeId(k, Constants::DOCKER_DEVICE_TYPE); + DeviceKitAspect::setDevice(k, m_device); + + QtVersion *qt = nullptr; + if (!qtVersions.isEmpty()) { + qt = qtVersions.at(0); + QtSupport::QtKitAspect::setQtVersion(k, qt); + } + Toolchains toolchainsToSet; + toolchainsToSet = ToolChainManager::toolchains([qt, this](const ToolChain *tc){ + return tc->detectionSource() == m_sharedId + && (!qt || qt->qtAbis().contains(tc->targetAbi())); + }); + for (ToolChain *toolChain : toolchainsToSet) + ToolChainKitAspect::setToolChain(k, toolChain); + + k->setSticky(ToolChainKitAspect::id(), true); + k->setSticky(QtSupport::QtKitAspect::id(), true); + k->setSticky(DeviceKitAspect::id(), true); + k->setSticky(DeviceTypeKitAspect::id(), true); + }; + + Kit *kit = KitManager::registerKit(initializeKit); + emit q->logOutput('\n' + tr("Registered kit %1").arg(kit->displayName())); + + QApplication::restoreOverrideCursor(); +} + +} // Internal +} // Docker diff --git a/src/plugins/docker/kitdetector.h b/src/plugins/docker/kitdetector.h new file mode 100644 index 00000000000..e31e2dd74d7 --- /dev/null +++ b/src/plugins/docker/kitdetector.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 + +namespace Docker { +namespace Internal { + +class KitDetector : public QObject +{ + Q_OBJECT + +public: + explicit KitDetector(const ProjectExplorer::IDevice::ConstPtr &device); + ~KitDetector() override; + + void autoDetect(const QString &sharedId, const Utils::FilePaths &selectedPaths) const; + void undoAutoDetect(const QString &sharedId) const; + void listAutoDetected(const QString &sharedId) const; + +signals: + void logOutput(const QString &msg); + +private: + class KitDetectorPrivate *d = nullptr; +}; + +} // Internal +} // Docker