// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dockerdevicewidget.h" #include "dockerapi.h" #include "dockerdevice.h" #include "dockertr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace Docker::Internal { DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device) : IDeviceWidget(device) { auto dockerDevice = std::dynamic_pointer_cast(device); QTC_ASSERT(dockerDevice, return); using namespace Layouting; auto daemonStateLabel = new QLabel(Tr::tr("Daemon state:")); m_daemonReset = new QToolButton; m_daemonReset->setToolTip(Tr::tr("Clears detected daemon state. " "It will be automatically re-evaluated next time access is needed.")); m_daemonState = new QLabel; connect(DockerApi::instance(), &DockerApi::dockerDaemonAvailableChanged, this, [this]{ updateDaemonStateTexts(); }); updateDaemonStateTexts(); connect(m_daemonReset, &QToolButton::clicked, this, [] { DockerApi::recheckDockerDaemon(); }); auto pathListLabel = new InfoLabel(Tr::tr("Paths to mount:")); pathListLabel->setAdditionalToolTip(Tr::tr("Source directory list should not be empty.")); auto markupMounts = [dockerDevice, pathListLabel] { const bool isEmpty = dockerDevice->mounts.volatileValue().isEmpty(); pathListLabel->setType(isEmpty ? InfoLabel::Warning : InfoLabel::None); }; markupMounts(); connect(&dockerDevice->mounts, &FilePathListAspect::volatileValueChanged, this, markupMounts); auto logView = new QTextBrowser; auto autoDetectButton = new QPushButton(Tr::tr("Auto-detect Kit Items")); auto undoAutoDetectButton = new QPushButton(Tr::tr("Remove Auto-Detected Kit Items")); auto listAutoDetectedButton = new QPushButton(Tr::tr("List Auto-Detected Kit Items")); connect(&m_detectionRunner, &Tasking::TaskTreeRunner::aboutToStart, [=] { autoDetectButton->setEnabled(false); undoAutoDetectButton->setEnabled(false); listAutoDetectedButton->setEnabled(false); logView->append(Tr::tr("Starting auto-detection...")); }); connect(&m_detectionRunner, &Tasking::TaskTreeRunner::done, [=] { autoDetectButton->setEnabled(true); undoAutoDetectButton->setEnabled(true); listAutoDetectedButton->setEnabled(true); logView->append(Tr::tr("Done.")); }); auto searchDirsComboBox = new QComboBox; searchDirsComboBox->addItem(Tr::tr("Search in PATH")); searchDirsComboBox->addItem(Tr::tr("Search in Selected Directories")); searchDirsComboBox->addItem(Tr::tr("Search in PATH and Additional Directories")); auto searchDirsLineEdit = new FancyLineEdit; searchDirsLineEdit->setPlaceholderText(Tr::tr("Semicolon-separated list of directories")); searchDirsLineEdit->setToolTip( Tr::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; const int idx = searchDirsComboBox->currentIndex(); if (idx == 0 || idx == 2) paths += dockerDevice->systemEnvironment().path(); if (idx == 1 || idx == 2) { for (const QString &path : searchDirsLineEdit->text().split(';')) paths.append(FilePath::fromString(path.trimmed())); } paths = Utils::transform(paths, [dockerDevice](const FilePath &path) { return dockerDevice->filePath(path.path()); }); return paths; }; connect(autoDetectButton, &QPushButton::clicked, this, [this, logView, dockerDevice, searchPaths] { logView->clear(); Result<> startResult = dockerDevice->updateContainerAccess(); if (!startResult) { logView->append(Tr::tr("Failed to start container.")); logView->append(startResult.error()); return; } const FilePath clangdPath = dockerDevice->filePath("clangd") .searchInPath({}, FilePath::AppendToPath, [](const FilePath &clangd) { return Utils::checkClangdVersion(clangd); }); if (!clangdPath.isEmpty()) dockerDevice->clangdExecutableAspect.setValue(clangdPath); const auto log = [logView](const QString &msg) { logView->append(msg); }; // clang-format off Tasking::Group recipe { ProjectExplorer::removeDetectedKitsRecipe(dockerDevice, log), ProjectExplorer::kitDetectionRecipe(dockerDevice, log) }; // clang-format on m_detectionRunner.start(recipe); if (DockerApi::instance()->dockerDaemonAvailable().value_or(false) == false) logView->append(Tr::tr("Docker daemon appears to be stopped.")); else logView->append(Tr::tr("Docker daemon appears to be running.")); updateDaemonStateTexts(); }); connect(undoAutoDetectButton, &QPushButton::clicked, this, [this, logView, device] { logView->clear(); m_detectionRunner.start( ProjectExplorer::removeDetectedKitsRecipe(device, [logView](const QString &msg) { logView->append(msg); })); }); connect(listAutoDetectedButton, &QPushButton::clicked, this, [logView, device] { logView->clear(); listAutoDetected(device, [logView](const QString &msg) { logView->append(msg); }); }); auto createLineLabel = new QLabel(dockerDevice->createCommandLine().toUserOutput()); createLineLabel->setWordWrap(true); using namespace Layouting; // clang-format off Column detectionControls { Space(20), Row { Tr::tr("Search Locations:"), searchDirsComboBox, searchDirsLineEdit }, Row { autoDetectButton, undoAutoDetectButton, listAutoDetectedButton, st, }, Tr::tr("Detection log:"), logView }; Column { noMargin, Form { noMargin, dockerDevice->repo, br, dockerDevice->tag, br, dockerDevice->imageId, br, daemonStateLabel, m_daemonReset, m_daemonState, br, Tr::tr("Container state:"), dockerDevice->containerStatus, br, dockerDevice->useLocalUidGid, br, dockerDevice->keepEntryPoint, br, dockerDevice->enableLldbFlags, br, dockerDevice->mountCmdBridge, br, dockerDevice->clangdExecutableAspect, br, dockerDevice->network, br, dockerDevice->extraArgs, br, dockerDevice->environment, br, Column { pathListLabel, dockerDevice->mounts, }, br, dockerDevice->portMappings, br, If (!dockerDevice->isAutoDetected()) >> Then { detectionControls }, }, br, Tr::tr("Command line:"), createLineLabel, br, }.attachTo(this); // clang-format on searchDirsLineEdit->setVisible(false); auto updateDirectoriesLineEdit = [searchDirsLineEdit](int index) { searchDirsLineEdit->setVisible(index == 1 || index == 2); if (index == 1 || index == 2) searchDirsLineEdit->setFocus(); }; QObject::connect(searchDirsComboBox, &QComboBox::activated, this, updateDirectoriesLineEdit); connect(&*dockerDevice, &AspectContainer::applied, this, [createLineLabel, dockerDevice] { createLineLabel->setText(dockerDevice->createCommandLine().toUserOutput()); }); } void DockerDeviceWidget::updateDaemonStateTexts() { std::optional daemonState = DockerApi::instance()->dockerDaemonAvailable(); if (!daemonState.has_value()) { m_daemonReset->setIcon(Icons::INFO.icon()); m_daemonState->setText(Tr::tr("Daemon state not evaluated.")); } else if (daemonState.value()) { m_daemonReset->setIcon(Icons::OK.icon()); m_daemonState->setText(Tr::tr("Docker daemon running.")); } else { m_daemonReset->setIcon(Icons::CRITICAL.icon()); m_daemonState->setText(Tr::tr("Docker daemon not running.")); } } } // namespace Docker::Internal