forked from qt-creator/qt-creator
Qnx: Re-work item store
This reduces the number of updates from the env* files and overall simplifies the architecture. I actually believe that it would be better if the whole configuration settings page would not exist but be part of the device settings page. Change-Id: I4184b74fc2c9695356752903c861f3758a6d7c73 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -4,8 +4,6 @@ add_qtc_plugin(Qnx
|
||||
SOURCES
|
||||
qnx.qrc
|
||||
qnxanalyzesupport.cpp qnxanalyzesupport.h
|
||||
qnxconfiguration.cpp qnxconfiguration.h
|
||||
qnxconfigurationmanager.cpp qnxconfigurationmanager.h
|
||||
qnxconstants.h
|
||||
qnxdebugsupport.cpp qnxdebugsupport.h
|
||||
qnxdeployqtlibrariesdialog.cpp qnxdeployqtlibrariesdialog.h
|
||||
|
@@ -20,8 +20,6 @@ QtcPlugin {
|
||||
"qnxtoolchain.h",
|
||||
"qnx.qrc",
|
||||
"qnxconstants.h",
|
||||
"qnxconfiguration.cpp",
|
||||
"qnxconfiguration.h",
|
||||
"qnxanalyzesupport.cpp",
|
||||
"qnxanalyzesupport.h",
|
||||
"qnxdebugsupport.cpp",
|
||||
@@ -30,8 +28,6 @@ QtcPlugin {
|
||||
"qnxdevice.h",
|
||||
"qnxdevicetester.cpp",
|
||||
"qnxdevicetester.h",
|
||||
"qnxconfigurationmanager.cpp",
|
||||
"qnxconfigurationmanager.h",
|
||||
"qnxsettingspage.cpp",
|
||||
"qnxsettingspage.h",
|
||||
"qnxtr.h",
|
||||
|
@@ -1,466 +0,0 @@
|
||||
// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "qnxconfiguration.h"
|
||||
|
||||
#include "qnxqtversion.h"
|
||||
#include "qnxutils.h"
|
||||
#include "qnxtoolchain.h"
|
||||
#include "qnxtr.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <debugger/debuggeritem.h>
|
||||
#include <debugger/debuggeritemmanager.h>
|
||||
#include <debugger/debuggerkitinformation.h>
|
||||
|
||||
#include <projectexplorer/toolchainmanager.h>
|
||||
#include <projectexplorer/toolchain.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
|
||||
#include <qtsupport/baseqtversion.h>
|
||||
#include <qtsupport/qtversionmanager.h>
|
||||
#include <qtsupport/qtkitinformation.h>
|
||||
|
||||
#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDomDocument>
|
||||
#include <QMessageBox>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace QtSupport;
|
||||
using namespace Utils;
|
||||
using namespace Debugger;
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
const QLatin1String QNXEnvFileKey("EnvFile");
|
||||
const QLatin1String QNXVersionKey("QNXVersion");
|
||||
// For backward compatibility
|
||||
const QLatin1String SdpEnvFileKey("NDKEnvFile");
|
||||
|
||||
const QLatin1String QNXConfiguration("QNX_CONFIGURATION");
|
||||
const QLatin1String QNXTarget("QNX_TARGET");
|
||||
const QLatin1String QNXHost("QNX_HOST");
|
||||
|
||||
QnxConfiguration::QnxConfiguration() = default;
|
||||
|
||||
QnxConfiguration::QnxConfiguration(const FilePath &sdpEnvFile)
|
||||
{
|
||||
setDefaultConfiguration(sdpEnvFile);
|
||||
readInformation();
|
||||
}
|
||||
|
||||
QnxConfiguration::QnxConfiguration(const QVariantMap &data)
|
||||
{
|
||||
QString envFilePath = data.value(QNXEnvFileKey).toString();
|
||||
if (envFilePath.isEmpty())
|
||||
envFilePath = data.value(SdpEnvFileKey).toString();
|
||||
|
||||
m_version = QnxVersionNumber(data.value(QNXVersionKey).toString());
|
||||
|
||||
setDefaultConfiguration(FilePath::fromString(envFilePath));
|
||||
readInformation();
|
||||
}
|
||||
|
||||
FilePath QnxConfiguration::envFile() const
|
||||
{
|
||||
return m_envFile;
|
||||
}
|
||||
|
||||
FilePath QnxConfiguration::qnxTarget() const
|
||||
{
|
||||
return m_qnxTarget;
|
||||
}
|
||||
|
||||
FilePath QnxConfiguration::qnxHost() const
|
||||
{
|
||||
return m_qnxHost;
|
||||
}
|
||||
|
||||
FilePath QnxConfiguration::qccCompilerPath() const
|
||||
{
|
||||
return m_qccCompiler;
|
||||
}
|
||||
|
||||
EnvironmentItems QnxConfiguration::qnxEnv() const
|
||||
{
|
||||
return m_qnxEnv;
|
||||
}
|
||||
|
||||
QnxVersionNumber QnxConfiguration::version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
QVariantMap QnxConfiguration::toMap() const
|
||||
{
|
||||
QVariantMap data;
|
||||
data.insert(QLatin1String(QNXEnvFileKey), m_envFile.toString());
|
||||
data.insert(QLatin1String(QNXVersionKey), m_version.toString());
|
||||
return data;
|
||||
}
|
||||
|
||||
bool QnxConfiguration::isValid() const
|
||||
{
|
||||
return !m_qccCompiler.isEmpty() && !m_targets.isEmpty();
|
||||
}
|
||||
|
||||
QString QnxConfiguration::displayName() const
|
||||
{
|
||||
return m_configName;
|
||||
}
|
||||
|
||||
bool QnxConfiguration::activate()
|
||||
{
|
||||
if (isActive())
|
||||
return true;
|
||||
|
||||
if (!isValid()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Cannot Set Up QNX Configuration"),
|
||||
validationErrorMessage(), QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Target &target : std::as_const(m_targets))
|
||||
createTools(target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QnxConfiguration::deactivate()
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
const Toolchains toolChainsToRemove =
|
||||
ToolChainManager::toolchains(Utils::equal(&ToolChain::compilerCommand, qccCompilerPath()));
|
||||
|
||||
QList<DebuggerItem> debuggersToRemove;
|
||||
const QList<DebuggerItem> debuggerItems = DebuggerItemManager::debuggers();
|
||||
for (const DebuggerItem &debuggerItem : debuggerItems) {
|
||||
if (findTargetByDebuggerPath(debuggerItem.command()))
|
||||
debuggersToRemove.append(debuggerItem);
|
||||
}
|
||||
|
||||
const QList<Kit *> kits = KitManager::kits();
|
||||
for (Kit *kit : kits) {
|
||||
if (kit->isAutoDetected()
|
||||
&& DeviceTypeKitAspect::deviceTypeId(kit) == Constants::QNX_QNX_OS_TYPE
|
||||
&& toolChainsToRemove.contains(ToolChainKitAspect::cxxToolChain(kit))) {
|
||||
KitManager::deregisterKit(kit);
|
||||
}
|
||||
}
|
||||
|
||||
for (ToolChain *tc : toolChainsToRemove)
|
||||
ToolChainManager::deregisterToolChain(tc);
|
||||
|
||||
for (const DebuggerItem &debuggerItem : std::as_const(debuggersToRemove))
|
||||
DebuggerItemManager::deregisterDebugger(debuggerItem.id());
|
||||
}
|
||||
|
||||
bool QnxConfiguration::isActive() const
|
||||
{
|
||||
const bool hasToolChain = ToolChainManager::toolChain(Utils::equal(&ToolChain::compilerCommand,
|
||||
qccCompilerPath()));
|
||||
const bool hasDebugger = Utils::contains(DebuggerItemManager::debuggers(), [this](const DebuggerItem &di) {
|
||||
return findTargetByDebuggerPath(di.command());
|
||||
});
|
||||
|
||||
return hasToolChain && hasDebugger;
|
||||
}
|
||||
|
||||
FilePath QnxConfiguration::sdpPath() const
|
||||
{
|
||||
return envFile().parentDir();
|
||||
}
|
||||
|
||||
QnxQtVersion *QnxConfiguration::qnxQtVersion(const Target &target) const
|
||||
{
|
||||
const QtVersions versions = QtVersionManager::versions(
|
||||
Utils::equal(&QtVersion::type, QString::fromLatin1(Constants::QNX_QNX_QT)));
|
||||
for (QtVersion *version : versions) {
|
||||
auto qnxQt = dynamic_cast<QnxQtVersion *>(version);
|
||||
if (qnxQt && qnxQt->sdpPath() == sdpPath()) {
|
||||
const Abis abis = version->qtAbis();
|
||||
for (const Abi &qtAbi : abis) {
|
||||
if ((qtAbi == target.m_abi) && (qnxQt->cpuDir() == target.cpuDir()))
|
||||
return qnxQt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QList<ToolChain *> QnxConfiguration::autoDetect(const QList<ToolChain *> &alreadyKnown)
|
||||
{
|
||||
QList<ToolChain *> result;
|
||||
|
||||
for (const Target &target : std::as_const(m_targets))
|
||||
result += findToolChain(alreadyKnown, target.m_abi);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void QnxConfiguration::createTools(const Target &target)
|
||||
{
|
||||
QnxToolChainMap toolchainMap = createToolChain(target);
|
||||
QVariant debuggerId = createDebugger(target);
|
||||
createKit(target, toolchainMap, debuggerId);
|
||||
}
|
||||
|
||||
QVariant QnxConfiguration::createDebugger(const Target &target)
|
||||
{
|
||||
Environment sysEnv = m_qnxHost.deviceEnvironment();
|
||||
sysEnv.modify(qnxEnvironmentItems());
|
||||
|
||||
Debugger::DebuggerItem debugger;
|
||||
debugger.setCommand(target.m_debuggerPath);
|
||||
debugger.reinitializeFromFile(nullptr, &sysEnv);
|
||||
debugger.setUnexpandedDisplayName(Tr::tr("Debugger for %1 (%2)")
|
||||
.arg(displayName())
|
||||
.arg(target.shortDescription()));
|
||||
return Debugger::DebuggerItemManager::registerDebugger(debugger);
|
||||
}
|
||||
|
||||
QnxConfiguration::QnxToolChainMap QnxConfiguration::createToolChain(const Target &target)
|
||||
{
|
||||
QnxToolChainMap toolChainMap;
|
||||
|
||||
for (auto language : { ProjectExplorer::Constants::C_LANGUAGE_ID,
|
||||
ProjectExplorer::Constants::CXX_LANGUAGE_ID}) {
|
||||
auto toolChain = new QnxToolChain;
|
||||
toolChain->setDetection(ToolChain::AutoDetection);
|
||||
toolChain->setLanguage(language);
|
||||
toolChain->setTargetAbi(target.m_abi);
|
||||
toolChain->setDisplayName(Tr::tr("QCC for %1 (%2)")
|
||||
.arg(displayName())
|
||||
.arg(target.shortDescription()));
|
||||
toolChain->setSdpPath(sdpPath());
|
||||
toolChain->setCpuDir(target.cpuDir());
|
||||
toolChain->resetToolChain(qccCompilerPath());
|
||||
ToolChainManager::registerToolChain(toolChain);
|
||||
|
||||
toolChainMap.insert({language, toolChain});
|
||||
}
|
||||
|
||||
return toolChainMap;
|
||||
}
|
||||
|
||||
QList<ToolChain *> QnxConfiguration::findToolChain(const QList<ToolChain *> &alreadyKnown,
|
||||
const Abi &abi)
|
||||
{
|
||||
return Utils::filtered(alreadyKnown, [this, abi](ToolChain *tc) {
|
||||
return tc->typeId() == Constants::QNX_TOOLCHAIN_ID
|
||||
&& tc->targetAbi() == abi
|
||||
&& tc->compilerCommand() == m_qccCompiler;
|
||||
});
|
||||
}
|
||||
|
||||
void QnxConfiguration::createKit(const Target &target, const QnxToolChainMap &toolChainMap,
|
||||
const QVariant &debugger)
|
||||
{
|
||||
QnxQtVersion *qnxQt = qnxQtVersion(target); // nullptr is ok.
|
||||
|
||||
const auto init = [&](Kit *k) {
|
||||
QtKitAspect::setQtVersion(k, qnxQt);
|
||||
ToolChainKitAspect::setToolChain(k, toolChainMap.at(ProjectExplorer::Constants::C_LANGUAGE_ID));
|
||||
ToolChainKitAspect::setToolChain(k, toolChainMap.at(ProjectExplorer::Constants::CXX_LANGUAGE_ID));
|
||||
|
||||
if (debugger.isValid())
|
||||
DebuggerKitAspect::setDebugger(k, debugger);
|
||||
|
||||
DeviceTypeKitAspect::setDeviceTypeId(k, Constants::QNX_QNX_OS_TYPE);
|
||||
// TODO: Add sysroot?
|
||||
|
||||
k->setUnexpandedDisplayName(Tr::tr("Kit for %1 (%2)")
|
||||
.arg(displayName())
|
||||
.arg(target.shortDescription()));
|
||||
|
||||
k->setAutoDetected(false);
|
||||
k->setAutoDetectionSource(envFile().toString());
|
||||
k->setMutable(DeviceKitAspect::id(), true);
|
||||
|
||||
k->setSticky(ToolChainKitAspect::id(), true);
|
||||
k->setSticky(DeviceTypeKitAspect::id(), true);
|
||||
k->setSticky(SysRootKitAspect::id(), true);
|
||||
k->setSticky(DebuggerKitAspect::id(), true);
|
||||
k->setSticky(QmakeProjectManager::Constants::KIT_INFORMATION_ID, true);
|
||||
|
||||
EnvironmentKitAspect::setEnvironmentChanges(k, qnxEnvironmentItems());
|
||||
};
|
||||
|
||||
// add kit with device and qt version not sticky
|
||||
KitManager::registerKit(init);
|
||||
}
|
||||
|
||||
QString QnxConfiguration::validationErrorMessage() const
|
||||
{
|
||||
if (isValid())
|
||||
return {};
|
||||
|
||||
QStringList errorStrings
|
||||
= {Tr::tr("The following errors occurred while activating the QNX configuration:")};
|
||||
if (m_qccCompiler.isEmpty())
|
||||
errorStrings << Tr::tr("- No GCC compiler found.");
|
||||
if (m_targets.isEmpty())
|
||||
errorStrings << Tr::tr("- No targets found.");
|
||||
return errorStrings.join('\n');
|
||||
}
|
||||
|
||||
void QnxConfiguration::setVersion(const QnxVersionNumber &version)
|
||||
{
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
void QnxConfiguration::readInformation()
|
||||
{
|
||||
const FilePath configPath = m_qnxConfiguration / "qconfig";
|
||||
if (!configPath.isDir())
|
||||
return;
|
||||
|
||||
configPath.iterateDirectory([this, configPath](const FilePath &sdpFile) {
|
||||
QFile xmlFile(sdpFile.toFSPathString());
|
||||
if (!xmlFile.open(QIODevice::ReadOnly))
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(&xmlFile)) // Skip error message
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomElement docElt = doc.documentElement();
|
||||
if (docElt.tagName() != QLatin1String("qnxSystemDefinition"))
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomElement childElt = docElt.firstChildElement(QLatin1String("installation"));
|
||||
// The file contains only one installation node
|
||||
if (childElt.isNull()) // The file contains only one base node
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
FilePath host = configPath.withNewPath(
|
||||
childElt.firstChildElement(QLatin1String("host")).text()).canonicalPath();
|
||||
if (m_qnxHost != host)
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
FilePath target = configPath.withNewPath(
|
||||
childElt.firstChildElement(QLatin1String("target")).text()).canonicalPath();
|
||||
if (m_qnxTarget != target)
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
m_configName = childElt.firstChildElement(QLatin1String("name")).text();
|
||||
QString version = childElt.firstChildElement(QLatin1String("version")).text();
|
||||
setVersion(QnxVersionNumber(version));
|
||||
return IterationPolicy::Stop;
|
||||
}, {{"*.xml"}, QDir::Files});
|
||||
}
|
||||
|
||||
void QnxConfiguration::setDefaultConfiguration(const FilePath &envScript)
|
||||
{
|
||||
QTC_ASSERT(!envScript.isEmpty(), return);
|
||||
m_envFile = envScript;
|
||||
m_qnxEnv = QnxUtils::qnxEnvironmentFromEnvFile(m_envFile);
|
||||
for (const EnvironmentItem &item : std::as_const(m_qnxEnv)) {
|
||||
if (item.name == QNXConfiguration)
|
||||
m_qnxConfiguration = envScript.withNewPath(item.value).canonicalPath();
|
||||
else if (item.name == QNXTarget)
|
||||
m_qnxTarget = envScript.withNewPath(item.value).canonicalPath();
|
||||
else if (item.name == QNXHost)
|
||||
m_qnxHost = envScript.withNewPath(item.value).canonicalPath();
|
||||
}
|
||||
|
||||
const FilePath qccPath = m_qnxHost.pathAppended("usr/bin/qcc").withExecutableSuffix();
|
||||
if (qccPath.exists())
|
||||
m_qccCompiler = qccPath;
|
||||
|
||||
// Some fall back in case the qconfig dir with .xml files is not found later
|
||||
if (m_configName.isEmpty())
|
||||
m_configName = QString("%1 - %2").arg(m_qnxHost.fileName(), m_qnxTarget.fileName());
|
||||
|
||||
updateTargets();
|
||||
assignDebuggersToTargets();
|
||||
|
||||
// Remove debuggerless targets.
|
||||
Utils::erase(m_targets, [](const Target &target) {
|
||||
if (target.m_debuggerPath.isEmpty())
|
||||
qWarning() << "No debugger found for" << target.m_path << "... discarded";
|
||||
return target.m_debuggerPath.isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
EnvironmentItems QnxConfiguration::qnxEnvironmentItems() const
|
||||
{
|
||||
Utils::EnvironmentItems envList;
|
||||
envList.push_back(EnvironmentItem(QNXConfiguration, m_qnxConfiguration.path()));
|
||||
envList.push_back(EnvironmentItem(QNXTarget, m_qnxTarget.path()));
|
||||
envList.push_back(EnvironmentItem(QNXHost, m_qnxHost.path()));
|
||||
|
||||
return envList;
|
||||
}
|
||||
|
||||
const QnxConfiguration::Target *QnxConfiguration::findTargetByDebuggerPath(
|
||||
const FilePath &path) const
|
||||
{
|
||||
const auto it = std::find_if(m_targets.begin(), m_targets.end(),
|
||||
[path](const Target &target) { return target.m_debuggerPath == path; });
|
||||
return it == m_targets.end() ? nullptr : &(*it);
|
||||
}
|
||||
|
||||
void QnxConfiguration::updateTargets()
|
||||
{
|
||||
m_targets.clear();
|
||||
const QList<QnxTarget> targets = QnxUtils::findTargets(m_qnxTarget);
|
||||
for (const QnxTarget &target : targets)
|
||||
m_targets.append(Target(target.m_abi, target.m_path));
|
||||
}
|
||||
|
||||
void QnxConfiguration::assignDebuggersToTargets()
|
||||
{
|
||||
const FilePath hostUsrBinDir = m_qnxHost.pathAppended("usr/bin");
|
||||
QString pattern = "nto*-gdb";
|
||||
if (m_qnxHost.osType() == Utils::OsTypeWindows)
|
||||
pattern += ".exe";
|
||||
|
||||
const FilePaths debuggerNames = hostUsrBinDir.dirEntries({{pattern}, QDir::Files});
|
||||
Environment sysEnv = m_qnxHost.deviceEnvironment();
|
||||
sysEnv.modify(qnxEnvironmentItems());
|
||||
|
||||
for (const FilePath &debuggerPath : debuggerNames) {
|
||||
DebuggerItem item;
|
||||
item.setCommand(debuggerPath);
|
||||
item.reinitializeFromFile(nullptr, &sysEnv);
|
||||
bool found = false;
|
||||
for (const Abi &abi : item.abis()) {
|
||||
for (Target &target : m_targets) {
|
||||
if (target.m_abi.isCompatibleWith(abi)) {
|
||||
found = true;
|
||||
|
||||
if (target.m_debuggerPath.isEmpty()) {
|
||||
target.m_debuggerPath = debuggerPath;
|
||||
} else {
|
||||
qWarning() << debuggerPath << "has the same ABI as" << target.m_debuggerPath
|
||||
<< "... discarded";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
qWarning() << "No target found for" << debuggerPath.toUserOutput() << "... discarded";
|
||||
}
|
||||
}
|
||||
|
||||
QString QnxConfiguration::Target::shortDescription() const
|
||||
{
|
||||
return QnxUtils::cpuDirShortDescription(cpuDir());
|
||||
}
|
||||
|
||||
QString QnxConfiguration::Target::cpuDir() const
|
||||
{
|
||||
return m_path.fileName();
|
||||
}
|
||||
|
||||
} // Qnx::Internal
|
@@ -1,114 +0,0 @@
|
||||
// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qnxconstants.h"
|
||||
#include "qnxversionnumber.h"
|
||||
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/environment.h>
|
||||
|
||||
#include <projectexplorer/abi.h>
|
||||
|
||||
#include <debugger/debuggeritemmanager.h>
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace ProjectExplorer
|
||||
{
|
||||
class Kit;
|
||||
class ToolChain;
|
||||
}
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
class QnxToolChain;
|
||||
class QnxQtVersion;
|
||||
|
||||
class QnxConfiguration
|
||||
{
|
||||
public:
|
||||
QnxConfiguration();
|
||||
QnxConfiguration(const Utils::FilePath &sdpEnvFile);
|
||||
QnxConfiguration(const QVariantMap &data);
|
||||
|
||||
Utils::FilePath envFile() const;
|
||||
Utils::FilePath qnxTarget() const;
|
||||
Utils::FilePath qnxHost() const;
|
||||
Utils::FilePath qccCompilerPath() const;
|
||||
Utils::EnvironmentItems qnxEnv() const;
|
||||
QnxVersionNumber version() const;
|
||||
QVariantMap toMap() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
QString displayName() const;
|
||||
bool activate();
|
||||
void deactivate();
|
||||
bool isActive() const;
|
||||
Utils::FilePath sdpPath() const;
|
||||
|
||||
QList<ProjectExplorer::ToolChain *> autoDetect(
|
||||
const QList<ProjectExplorer::ToolChain *> &alreadyKnown);
|
||||
|
||||
private:
|
||||
QList<ProjectExplorer::ToolChain *> findToolChain(
|
||||
const QList<ProjectExplorer::ToolChain *> &alreadyKnown,
|
||||
const ProjectExplorer::Abi &abi);
|
||||
|
||||
QString validationErrorMessage() const;
|
||||
|
||||
void setVersion(const QnxVersionNumber& version);
|
||||
|
||||
void readInformation();
|
||||
|
||||
void setDefaultConfiguration(const Utils::FilePath &envScript);
|
||||
|
||||
Utils::EnvironmentItems qnxEnvironmentItems() const;
|
||||
|
||||
QString m_configName;
|
||||
|
||||
Utils::FilePath m_envFile;
|
||||
Utils::FilePath m_qnxConfiguration;
|
||||
Utils::FilePath m_qnxTarget;
|
||||
Utils::FilePath m_qnxHost;
|
||||
Utils::FilePath m_qccCompiler;
|
||||
Utils::EnvironmentItems m_qnxEnv;
|
||||
QnxVersionNumber m_version;
|
||||
|
||||
class Target
|
||||
{
|
||||
public:
|
||||
Target(const ProjectExplorer::Abi &abi, const Utils::FilePath &path)
|
||||
: m_abi(abi), m_path(path)
|
||||
{
|
||||
}
|
||||
|
||||
QString shortDescription() const;
|
||||
QString cpuDir() const;
|
||||
|
||||
ProjectExplorer::Abi m_abi;
|
||||
Utils::FilePath m_path;
|
||||
Utils::FilePath m_debuggerPath;
|
||||
};
|
||||
|
||||
QList<Target> m_targets;
|
||||
|
||||
QnxQtVersion *qnxQtVersion(const Target &target) const;
|
||||
|
||||
void createTools(const Target &target);
|
||||
QVariant createDebugger(const Target &target);
|
||||
|
||||
using QnxToolChainMap = std::map<const char*, QnxToolChain*>;
|
||||
|
||||
QnxToolChainMap createToolChain(const Target &target);
|
||||
void createKit(const Target &target, const QnxToolChainMap &toolChain, const QVariant &debugger);
|
||||
|
||||
const Target *findTargetByDebuggerPath(const Utils::FilePath &path) const;
|
||||
|
||||
void updateTargets();
|
||||
void assignDebuggersToTargets();
|
||||
};
|
||||
|
||||
} // Qnx::Internal
|
@@ -1,124 +0,0 @@
|
||||
// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "qnxconfigurationmanager.h"
|
||||
|
||||
#include "qnxconfiguration.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <utils/persistentsettings.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
const QLatin1String QNXConfigDataKey("QNXConfiguration.");
|
||||
const QLatin1String QNXConfigCountKey("QNXConfiguration.Count");
|
||||
const QLatin1String QNXConfigsFileVersionKey("Version");
|
||||
|
||||
static FilePath qnxConfigSettingsFileName()
|
||||
{
|
||||
return Core::ICore::userResourcePath("qnx/qnxconfigurations.xml");
|
||||
}
|
||||
|
||||
static QnxConfigurationManager *m_instance = nullptr;
|
||||
|
||||
QnxConfigurationManager::QnxConfigurationManager()
|
||||
{
|
||||
m_instance = this;
|
||||
m_writer = new PersistentSettingsWriter(qnxConfigSettingsFileName(), "QnxConfigurations");
|
||||
connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested,
|
||||
this, &QnxConfigurationManager::saveConfigs);
|
||||
}
|
||||
|
||||
QnxConfigurationManager *QnxConfigurationManager::instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
QnxConfigurationManager::~QnxConfigurationManager()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
qDeleteAll(m_configurations);
|
||||
delete m_writer;
|
||||
}
|
||||
|
||||
QList<QnxConfiguration *> QnxConfigurationManager::configurations() const
|
||||
{
|
||||
return m_configurations;
|
||||
}
|
||||
|
||||
void QnxConfigurationManager::removeConfiguration(QnxConfiguration *config)
|
||||
{
|
||||
if (m_configurations.removeAll(config)) {
|
||||
delete config;
|
||||
emit configurationsListUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
bool QnxConfigurationManager::addConfiguration(QnxConfiguration *config)
|
||||
{
|
||||
if (!config || !config->isValid())
|
||||
return false;
|
||||
|
||||
for (QnxConfiguration *c : std::as_const(m_configurations)) {
|
||||
if (c->envFile() == config->envFile())
|
||||
return false;
|
||||
}
|
||||
|
||||
m_configurations.append(config);
|
||||
emit configurationsListUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
QnxConfiguration *QnxConfigurationManager::configurationFromEnvFile(const FilePath &envFile) const
|
||||
{
|
||||
for (QnxConfiguration *c : m_configurations) {
|
||||
if (c->envFile() == envFile)
|
||||
return c;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QnxConfigurationManager::saveConfigs()
|
||||
{
|
||||
QTC_ASSERT(m_writer, return);
|
||||
QVariantMap data;
|
||||
data.insert(QLatin1String(QNXConfigsFileVersionKey), 1);
|
||||
int count = 0;
|
||||
for (QnxConfiguration *config : std::as_const(m_configurations)) {
|
||||
QVariantMap tmp = config->toMap();
|
||||
if (tmp.isEmpty())
|
||||
continue;
|
||||
|
||||
data.insert(QNXConfigDataKey + QString::number(count), tmp);
|
||||
++count;
|
||||
}
|
||||
|
||||
data.insert(QLatin1String(QNXConfigCountKey), count);
|
||||
m_writer->save(data, Core::ICore::dialogParent());
|
||||
}
|
||||
|
||||
void QnxConfigurationManager::restoreConfigurations()
|
||||
{
|
||||
PersistentSettingsReader reader;
|
||||
if (!reader.load(qnxConfigSettingsFileName()))
|
||||
return;
|
||||
|
||||
QVariantMap data = reader.restoreValues();
|
||||
int count = data.value(QNXConfigCountKey, 0).toInt();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const QString key = QNXConfigDataKey + QString::number(i);
|
||||
if (!data.contains(key))
|
||||
continue;
|
||||
|
||||
const QVariantMap dMap = data.value(key).toMap();
|
||||
auto configuration = new QnxConfiguration(dMap);
|
||||
addConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
} // Qnx::Internal
|
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
namespace Utils { class PersistentSettingsWriter; }
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
class QnxConfiguration;
|
||||
class QnxPlugin;
|
||||
|
||||
class QnxConfigurationManager: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QnxConfigurationManager();
|
||||
~QnxConfigurationManager() override;
|
||||
|
||||
void restoreConfigurations();
|
||||
|
||||
static QnxConfigurationManager *instance();
|
||||
QList<QnxConfiguration*> configurations() const;
|
||||
void removeConfiguration(QnxConfiguration *config);
|
||||
bool addConfiguration(QnxConfiguration *config);
|
||||
QnxConfiguration* configurationFromEnvFile(const Utils::FilePath &envFile) const;
|
||||
|
||||
protected slots:
|
||||
void saveConfigs();
|
||||
|
||||
signals:
|
||||
void configurationsListUpdated();
|
||||
|
||||
private:
|
||||
QList<QnxConfiguration*> m_configurations;
|
||||
Utils::PersistentSettingsWriter *m_writer;
|
||||
};
|
||||
|
||||
} // Qnx::Internal
|
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "qnxanalyzesupport.h"
|
||||
#include "qnxconfigurationmanager.h"
|
||||
#include "qnxconstants.h"
|
||||
#include "qnxdebugsupport.h"
|
||||
#include "qnxdevice.h"
|
||||
@@ -80,7 +79,7 @@ public:
|
||||
QAction *m_debugSeparator = nullptr;
|
||||
QAction m_attachToQnxApplication{Tr::tr("Attach to remote QNX application..."), nullptr};
|
||||
|
||||
QnxConfigurationManager configurationManager;
|
||||
QnxSettingsPage settingsPage;
|
||||
QnxQtVersionFactory qtVersionFactory;
|
||||
QnxDeviceFactory deviceFactory;
|
||||
QnxDeployConfigurationFactory deployConfigFactory;
|
||||
@@ -88,7 +87,6 @@ public:
|
||||
Constants::QNX_DIRECT_UPLOAD_STEP_ID};
|
||||
QnxDeployStepFactory makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId};
|
||||
QnxRunConfigurationFactory runConfigFactory;
|
||||
QnxSettingsPage settingsPage;
|
||||
QnxToolChainFactory toolChainFactory;
|
||||
SimpleTargetRunnerFactory runWorkerFactory{{runConfigFactory.runConfigurationId()}};
|
||||
QnxDebugWorkerFactory debugWorkerFactory;
|
||||
@@ -112,10 +110,6 @@ private:
|
||||
|
||||
void QnxPlugin::extensionsInitialized()
|
||||
{
|
||||
// Can't do yet as not all devices are around.
|
||||
connect(DeviceManager::instance(), &DeviceManager::devicesLoaded,
|
||||
&d->configurationManager, &QnxConfigurationManager::restoreConfigurations);
|
||||
|
||||
// Attach support
|
||||
connect(&d->m_attachToQnxApplication, &QAction::triggered, this, &showAttachToProcessDialog);
|
||||
|
||||
|
@@ -3,29 +3,481 @@
|
||||
|
||||
#include "qnxsettingspage.h"
|
||||
|
||||
#include "qnxconfiguration.h"
|
||||
#include "qnxconfigurationmanager.h"
|
||||
#include "qnxqtversion.h"
|
||||
#include "qnxtoolchain.h"
|
||||
#include "qnxtr.h"
|
||||
#include "qnxutils.h"
|
||||
#include "qnxversionnumber.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <debugger/debuggeritem.h>
|
||||
#include <debugger/debuggeritemmanager.h>
|
||||
#include <debugger/debuggerkitinformation.h>
|
||||
|
||||
#include <projectexplorer/devicesupport/devicemanager.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/toolchainmanager.h>
|
||||
#include <projectexplorer/toolchain.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
|
||||
#include <qtsupport/baseqtversion.h>
|
||||
#include <qtsupport/qtversionmanager.h>
|
||||
#include <qtsupport/qtkitinformation.h>
|
||||
|
||||
#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/persistentsettings.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QDomDocument>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace QtSupport;
|
||||
using namespace Utils;
|
||||
using namespace Debugger;
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
const QLatin1String QNXEnvFileKey("EnvFile");
|
||||
const QLatin1String QNXVersionKey("QNXVersion");
|
||||
// For backward compatibility
|
||||
const QLatin1String SdpEnvFileKey("NDKEnvFile");
|
||||
|
||||
const QLatin1String QNXConfiguration("QNX_CONFIGURATION");
|
||||
const QLatin1String QNXTarget("QNX_TARGET");
|
||||
const QLatin1String QNXHost("QNX_HOST");
|
||||
|
||||
const QLatin1String QNXConfigDataKey("QNXConfiguration.");
|
||||
const QLatin1String QNXConfigCountKey("QNXConfiguration.Count");
|
||||
const QLatin1String QNXConfigsFileVersionKey("Version");
|
||||
|
||||
static FilePath qnxConfigSettingsFileName()
|
||||
{
|
||||
return Core::ICore::userResourcePath("qnx/qnxconfigurations.xml");
|
||||
}
|
||||
|
||||
class QnxConfiguration
|
||||
{
|
||||
public:
|
||||
QnxConfiguration() = default;
|
||||
explicit QnxConfiguration(const FilePath &envFile) { m_envFile = envFile; }
|
||||
|
||||
void fromMap(const QVariantMap &data)
|
||||
{
|
||||
QString envFilePath = data.value(QNXEnvFileKey).toString();
|
||||
if (envFilePath.isEmpty())
|
||||
envFilePath = data.value(SdpEnvFileKey).toString();
|
||||
|
||||
m_version = QnxVersionNumber(data.value(QNXVersionKey).toString());
|
||||
m_envFile = FilePath::fromString(envFilePath);
|
||||
}
|
||||
|
||||
QVariantMap toMap() const
|
||||
{
|
||||
QVariantMap data;
|
||||
data.insert(QLatin1String(QNXEnvFileKey), m_envFile.toString());
|
||||
data.insert(QLatin1String(QNXVersionKey), m_version.toString());
|
||||
return data;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !m_qccCompiler.isEmpty() && !m_targets.isEmpty();
|
||||
}
|
||||
|
||||
bool isActive() const
|
||||
{
|
||||
const bool hasToolChain = ToolChainManager::toolChain(Utils::equal(&ToolChain::compilerCommand,
|
||||
m_qccCompiler));
|
||||
const bool hasDebugger = Utils::contains(DebuggerItemManager::debuggers(), [this](const DebuggerItem &di) {
|
||||
return findTargetByDebuggerPath(di.command());
|
||||
});
|
||||
return hasToolChain && hasDebugger;
|
||||
}
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
|
||||
void ensureContents() const;
|
||||
void mutableEnsureContents();
|
||||
|
||||
EnvironmentItems qnxEnvironmentItems() const;
|
||||
|
||||
QnxQtVersion *qnxQtVersion(const QnxTarget &target) const;
|
||||
|
||||
void createKit(const QnxTarget &target);
|
||||
QVariant createDebugger(const QnxTarget &target);
|
||||
Toolchains createToolChains(const QnxTarget &target);
|
||||
|
||||
const QnxTarget *findTargetByDebuggerPath(const Utils::FilePath &path) const;
|
||||
|
||||
bool m_hasContents = false;
|
||||
QString m_configName;
|
||||
|
||||
FilePath m_envFile;
|
||||
FilePath m_qnxConfiguration;
|
||||
FilePath m_qnxTarget;
|
||||
FilePath m_qnxHost;
|
||||
FilePath m_qccCompiler;
|
||||
EnvironmentItems m_qnxEnv;
|
||||
QnxVersionNumber m_version;
|
||||
|
||||
QList<QnxTarget> m_targets;
|
||||
};
|
||||
|
||||
void QnxConfiguration::activate()
|
||||
{
|
||||
ensureContents();
|
||||
|
||||
if (!isValid()) {
|
||||
QStringList errorStrings
|
||||
= {Tr::tr("The following errors occurred while activating the QNX configuration:")};
|
||||
if (m_qccCompiler.isEmpty())
|
||||
errorStrings << Tr::tr("- No GCC compiler found.");
|
||||
if (m_targets.isEmpty())
|
||||
errorStrings << Tr::tr("- No targets found.");
|
||||
const QString msg = errorStrings.join('\n');
|
||||
|
||||
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Cannot Set Up QNX Configuration"),
|
||||
msg, QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QnxTarget &target : std::as_const(m_targets))
|
||||
createKit(target);
|
||||
}
|
||||
|
||||
void QnxConfiguration::deactivate()
|
||||
{
|
||||
QTC_ASSERT(isActive(), return);
|
||||
|
||||
const Toolchains toolChainsToRemove =
|
||||
ToolChainManager::toolchains(Utils::equal(&ToolChain::compilerCommand, m_qccCompiler));
|
||||
|
||||
QList<DebuggerItem> debuggersToRemove;
|
||||
const QList<DebuggerItem> debuggerItems = DebuggerItemManager::debuggers();
|
||||
for (const DebuggerItem &debuggerItem : debuggerItems) {
|
||||
if (findTargetByDebuggerPath(debuggerItem.command()))
|
||||
debuggersToRemove.append(debuggerItem);
|
||||
}
|
||||
|
||||
const QList<Kit *> kits = KitManager::kits();
|
||||
for (Kit *kit : kits) {
|
||||
if (kit->isAutoDetected()
|
||||
&& DeviceTypeKitAspect::deviceTypeId(kit) == Constants::QNX_QNX_OS_TYPE
|
||||
&& toolChainsToRemove.contains(ToolChainKitAspect::cxxToolChain(kit))) {
|
||||
KitManager::deregisterKit(kit);
|
||||
}
|
||||
}
|
||||
|
||||
for (ToolChain *tc : toolChainsToRemove)
|
||||
ToolChainManager::deregisterToolChain(tc);
|
||||
|
||||
for (const DebuggerItem &debuggerItem : std::as_const(debuggersToRemove))
|
||||
DebuggerItemManager::deregisterDebugger(debuggerItem.id());
|
||||
}
|
||||
|
||||
QnxQtVersion *QnxConfiguration::qnxQtVersion(const QnxTarget &target) const
|
||||
{
|
||||
const QtVersions versions = QtVersionManager::versions(
|
||||
Utils::equal(&QtVersion::type, QString::fromLatin1(Constants::QNX_QNX_QT)));
|
||||
for (QtVersion *version : versions) {
|
||||
auto qnxQt = dynamic_cast<QnxQtVersion *>(version);
|
||||
if (qnxQt && qnxQt->sdpPath() == m_envFile.parentDir()) {
|
||||
const Abis abis = version->qtAbis();
|
||||
for (const Abi &qtAbi : abis) {
|
||||
if (qtAbi == target.m_abi && qnxQt->cpuDir() == target.cpuDir())
|
||||
return qnxQt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVariant QnxConfiguration::createDebugger(const QnxTarget &target)
|
||||
{
|
||||
Environment sysEnv = m_qnxHost.deviceEnvironment();
|
||||
sysEnv.modify(qnxEnvironmentItems());
|
||||
|
||||
Debugger::DebuggerItem debugger;
|
||||
debugger.setCommand(target.m_debuggerPath);
|
||||
debugger.reinitializeFromFile(nullptr, &sysEnv);
|
||||
debugger.setUnexpandedDisplayName(Tr::tr("Debugger for %1 (%2)")
|
||||
.arg(m_configName)
|
||||
.arg(target.shortDescription()));
|
||||
return Debugger::DebuggerItemManager::registerDebugger(debugger);
|
||||
}
|
||||
|
||||
Toolchains QnxConfiguration::createToolChains(const QnxTarget &target)
|
||||
{
|
||||
Toolchains toolChains;
|
||||
|
||||
for (const Id language : {ProjectExplorer::Constants::C_LANGUAGE_ID,
|
||||
ProjectExplorer::Constants::CXX_LANGUAGE_ID}) {
|
||||
auto toolChain = new QnxToolChain;
|
||||
toolChain->setDetection(ToolChain::AutoDetection);
|
||||
toolChain->setLanguage(language);
|
||||
toolChain->setTargetAbi(target.m_abi);
|
||||
toolChain->setDisplayName(Tr::tr("QCC for %1 (%2)")
|
||||
.arg(m_configName)
|
||||
.arg(target.shortDescription()));
|
||||
toolChain->setSdpPath(m_envFile.parentDir());
|
||||
toolChain->setCpuDir(target.cpuDir());
|
||||
toolChain->resetToolChain(m_qccCompiler);
|
||||
ToolChainManager::registerToolChain(toolChain);
|
||||
|
||||
toolChains.append(toolChain);
|
||||
}
|
||||
|
||||
return toolChains;
|
||||
}
|
||||
|
||||
void QnxConfiguration::createKit(const QnxTarget &target)
|
||||
{
|
||||
Toolchains toolChains = createToolChains(target);
|
||||
QVariant debugger = createDebugger(target);
|
||||
|
||||
QnxQtVersion *qnxQt = qnxQtVersion(target); // nullptr is ok.
|
||||
|
||||
const auto init = [&](Kit *k) {
|
||||
QtKitAspect::setQtVersion(k, qnxQt);
|
||||
ToolChainKitAspect::setToolChain(k, toolChains[0]);
|
||||
ToolChainKitAspect::setToolChain(k, toolChains[1]);
|
||||
|
||||
if (debugger.isValid())
|
||||
DebuggerKitAspect::setDebugger(k, debugger);
|
||||
|
||||
DeviceTypeKitAspect::setDeviceTypeId(k, Constants::QNX_QNX_OS_TYPE);
|
||||
// TODO: Add sysroot?
|
||||
|
||||
k->setUnexpandedDisplayName(Tr::tr("Kit for %1 (%2)")
|
||||
.arg(m_configName)
|
||||
.arg(target.shortDescription()));
|
||||
|
||||
k->setAutoDetected(false);
|
||||
k->setAutoDetectionSource(m_envFile.toString());
|
||||
k->setMutable(DeviceKitAspect::id(), true);
|
||||
|
||||
k->setSticky(ToolChainKitAspect::id(), true);
|
||||
k->setSticky(DeviceTypeKitAspect::id(), true);
|
||||
k->setSticky(SysRootKitAspect::id(), true);
|
||||
k->setSticky(DebuggerKitAspect::id(), true);
|
||||
k->setSticky(QmakeProjectManager::Constants::KIT_INFORMATION_ID, true);
|
||||
|
||||
EnvironmentKitAspect::setEnvironmentChanges(k, qnxEnvironmentItems());
|
||||
};
|
||||
|
||||
// add kit with device and qt version not sticky
|
||||
KitManager::registerKit(init);
|
||||
}
|
||||
|
||||
void QnxConfiguration::ensureContents() const
|
||||
{
|
||||
if (!m_hasContents)
|
||||
const_cast<QnxConfiguration *>(this)->mutableEnsureContents();
|
||||
}
|
||||
|
||||
void QnxConfiguration::mutableEnsureContents()
|
||||
{
|
||||
QTC_ASSERT(!m_envFile.isEmpty(), return);
|
||||
m_hasContents = true;
|
||||
|
||||
m_qnxEnv = QnxUtils::qnxEnvironmentFromEnvFile(m_envFile);
|
||||
for (const EnvironmentItem &item : std::as_const(m_qnxEnv)) {
|
||||
if (item.name == QNXConfiguration)
|
||||
m_qnxConfiguration = m_envFile.withNewPath(item.value).canonicalPath();
|
||||
else if (item.name == QNXTarget)
|
||||
m_qnxTarget = m_envFile.withNewPath(item.value).canonicalPath();
|
||||
else if (item.name == QNXHost)
|
||||
m_qnxHost = m_envFile.withNewPath(item.value).canonicalPath();
|
||||
}
|
||||
|
||||
const FilePath qccPath = m_qnxHost.pathAppended("usr/bin/qcc").withExecutableSuffix();
|
||||
if (qccPath.exists())
|
||||
m_qccCompiler = qccPath;
|
||||
|
||||
// Some fallback in case the qconfig dir with .xml files is not found later.
|
||||
if (m_configName.isEmpty())
|
||||
m_configName = QString("%1 - %2").arg(m_qnxHost.fileName(), m_qnxTarget.fileName());
|
||||
|
||||
m_targets = QnxUtils::findTargets(m_qnxTarget);
|
||||
|
||||
// Assign debuggers.
|
||||
const FilePath hostUsrBinDir = m_qnxHost.pathAppended("usr/bin");
|
||||
QString pattern = "nto*-gdb";
|
||||
if (m_qnxHost.osType() == Utils::OsTypeWindows)
|
||||
pattern += ".exe";
|
||||
|
||||
const FilePaths debuggerNames = hostUsrBinDir.dirEntries({{pattern}, QDir::Files});
|
||||
Environment sysEnv = m_qnxHost.deviceEnvironment();
|
||||
sysEnv.modify(qnxEnvironmentItems());
|
||||
|
||||
for (const FilePath &debuggerPath : debuggerNames) {
|
||||
DebuggerItem item;
|
||||
item.setCommand(debuggerPath);
|
||||
item.reinitializeFromFile(nullptr, &sysEnv);
|
||||
bool found = false;
|
||||
for (const Abi &abi : item.abis()) {
|
||||
for (QnxTarget &target : m_targets) {
|
||||
if (target.m_abi.isCompatibleWith(abi)) {
|
||||
found = true;
|
||||
|
||||
if (target.m_debuggerPath.isEmpty()) {
|
||||
target.m_debuggerPath = debuggerPath;
|
||||
} else {
|
||||
qWarning() << debuggerPath << "has the same ABI as" << target.m_debuggerPath
|
||||
<< "... discarded";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
qWarning() << "No target found for" << debuggerPath.toUserOutput() << "... discarded";
|
||||
}
|
||||
|
||||
// Remove debuggerless targets.
|
||||
Utils::erase(m_targets, [](const QnxTarget &target) {
|
||||
if (target.m_debuggerPath.isEmpty())
|
||||
qWarning() << "No debugger found for" << target.m_path << "... discarded";
|
||||
return target.m_debuggerPath.isEmpty();
|
||||
});
|
||||
|
||||
const FilePath configPath = m_qnxConfiguration / "qconfig";
|
||||
if (!configPath.isDir())
|
||||
return;
|
||||
|
||||
configPath.iterateDirectory([this, configPath](const FilePath &sdpFile) {
|
||||
QFile xmlFile(sdpFile.toFSPathString());
|
||||
if (!xmlFile.open(QIODevice::ReadOnly))
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(&xmlFile)) // Skip error message
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomElement docElt = doc.documentElement();
|
||||
if (docElt.tagName() != QLatin1String("qnxSystemDefinition"))
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
QDomElement childElt = docElt.firstChildElement(QLatin1String("installation"));
|
||||
// The file contains only one installation node
|
||||
if (childElt.isNull()) // The file contains only one base node
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
FilePath host = configPath.withNewPath(
|
||||
childElt.firstChildElement(QLatin1String("host")).text()).canonicalPath();
|
||||
if (m_qnxHost != host)
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
FilePath target = configPath.withNewPath(
|
||||
childElt.firstChildElement(QLatin1String("target")).text()).canonicalPath();
|
||||
if (m_qnxTarget != target)
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
m_configName = childElt.firstChildElement(QLatin1String("name")).text();
|
||||
QString version = childElt.firstChildElement(QLatin1String("version")).text();
|
||||
m_version = QnxVersionNumber(version);
|
||||
return IterationPolicy::Stop;
|
||||
}, {{"*.xml"}, QDir::Files});
|
||||
}
|
||||
|
||||
EnvironmentItems QnxConfiguration::qnxEnvironmentItems() const
|
||||
{
|
||||
ensureContents();
|
||||
return {
|
||||
{QNXConfiguration, m_qnxConfiguration.path()},
|
||||
{QNXTarget, m_qnxTarget.path()},
|
||||
{QNXHost, m_qnxHost.path()}
|
||||
};
|
||||
}
|
||||
|
||||
const QnxTarget *QnxConfiguration::findTargetByDebuggerPath(
|
||||
const FilePath &path) const
|
||||
{
|
||||
const auto it = std::find_if(m_targets.begin(), m_targets.end(),
|
||||
[path](const QnxTarget &target) { return target.m_debuggerPath == path; });
|
||||
return it == m_targets.end() ? nullptr : &(*it);
|
||||
}
|
||||
|
||||
|
||||
// QnxSettingsPagePrivate
|
||||
|
||||
class QnxSettingsPagePrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QnxSettingsPagePrivate()
|
||||
{
|
||||
connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested,
|
||||
this, &QnxSettingsPagePrivate::saveConfigs);
|
||||
// Can't do yet as not all devices are around.
|
||||
connect(DeviceManager::instance(), &DeviceManager::devicesLoaded,
|
||||
this, &QnxSettingsPagePrivate::restoreConfigurations);
|
||||
}
|
||||
|
||||
void saveConfigs()
|
||||
{
|
||||
QVariantMap data;
|
||||
data.insert(QLatin1String(QNXConfigsFileVersionKey), 1);
|
||||
int count = 0;
|
||||
for (const QnxConfiguration &config : std::as_const(m_configurations)) {
|
||||
QVariantMap tmp = config.toMap();
|
||||
if (tmp.isEmpty())
|
||||
continue;
|
||||
|
||||
data.insert(QNXConfigDataKey + QString::number(count), tmp);
|
||||
++count;
|
||||
}
|
||||
|
||||
data.insert(QLatin1String(QNXConfigCountKey), count);
|
||||
m_writer.save(data, Core::ICore::dialogParent());
|
||||
}
|
||||
|
||||
void restoreConfigurations()
|
||||
{
|
||||
PersistentSettingsReader reader;
|
||||
if (!reader.load(qnxConfigSettingsFileName()))
|
||||
return;
|
||||
|
||||
QVariantMap data = reader.restoreValues();
|
||||
int count = data.value(QNXConfigCountKey, 0).toInt();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const QString key = QNXConfigDataKey + QString::number(i);
|
||||
if (!data.contains(key))
|
||||
continue;
|
||||
|
||||
QnxConfiguration config;
|
||||
config.fromMap(data.value(key).toMap());
|
||||
m_configurations[config.m_envFile] = config;
|
||||
}
|
||||
}
|
||||
|
||||
QnxConfiguration *configurationFromEnvFile(const FilePath &envFile)
|
||||
{
|
||||
auto it = m_configurations.find(envFile);
|
||||
return it == m_configurations.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
QHash<FilePath, QnxConfiguration> m_configurations;
|
||||
PersistentSettingsWriter m_writer{qnxConfigSettingsFileName(), "QnxConfigurations"};
|
||||
};
|
||||
|
||||
static QnxSettingsPagePrivate *dd = nullptr;
|
||||
|
||||
|
||||
// QnxSettingsWidget
|
||||
|
||||
class QnxSettingsWidget final : public Core::IOptionsPageWidget
|
||||
{
|
||||
public:
|
||||
@@ -42,10 +494,10 @@ public:
|
||||
public:
|
||||
bool operator ==(const ConfigState &cs) const
|
||||
{
|
||||
return config == cs.config && state == cs.state;
|
||||
return envFile == cs.envFile && state == cs.state;
|
||||
}
|
||||
|
||||
QnxConfiguration *config;
|
||||
FilePath envFile;
|
||||
State state;
|
||||
};
|
||||
|
||||
@@ -57,7 +509,7 @@ public:
|
||||
void updateInformation();
|
||||
void populateConfigsCombo();
|
||||
|
||||
void setConfigState(QnxConfiguration *config, State state);
|
||||
void setConfigState(const FilePath &envFile, State state);
|
||||
|
||||
private:
|
||||
QComboBox *m_configsCombo = new QComboBox;
|
||||
@@ -67,7 +519,6 @@ private:
|
||||
QLabel *m_configHost = new QLabel;
|
||||
QLabel *m_configTarget = new QLabel;
|
||||
|
||||
QnxConfigurationManager *m_qnxConfigManager = QnxConfigurationManager::instance();
|
||||
QList<ConfigState> m_changedConfigs;
|
||||
};
|
||||
|
||||
@@ -101,6 +552,7 @@ QnxSettingsWidget::QnxSettingsWidget()
|
||||
}.attachTo(this);
|
||||
|
||||
populateConfigsCombo();
|
||||
|
||||
connect(addButton, &QAbstractButton::clicked,
|
||||
this, &QnxSettingsWidget::addConfiguration);
|
||||
connect(removeButton, &QAbstractButton::clicked,
|
||||
@@ -109,17 +561,14 @@ QnxSettingsWidget::QnxSettingsWidget()
|
||||
this, &QnxSettingsWidget::updateInformation);
|
||||
connect(m_generateKitsCheckBox, &QAbstractButton::toggled,
|
||||
this, &QnxSettingsWidget::generateKits);
|
||||
connect(m_qnxConfigManager, &QnxConfigurationManager::configurationsListUpdated,
|
||||
this, &QnxSettingsWidget::populateConfigsCombo);
|
||||
connect(QtSupport::QtVersionManager::instance(),
|
||||
&QtSupport::QtVersionManager::qtVersionsChanged,
|
||||
connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
|
||||
this, &QnxSettingsWidget::updateInformation);
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::addConfiguration()
|
||||
{
|
||||
QString filter;
|
||||
if (Utils::HostOsInfo::isWindowsHost())
|
||||
if (HostOsInfo::isWindowsHost())
|
||||
filter = "*.bat file";
|
||||
else
|
||||
filter = "*.sh file";
|
||||
@@ -129,82 +578,88 @@ void QnxSettingsWidget::addConfiguration()
|
||||
if (envFile.isEmpty())
|
||||
return;
|
||||
|
||||
QnxConfiguration *config = new QnxConfiguration(envFile);
|
||||
if (m_qnxConfigManager->configurations().contains(config) || !config->isValid()) {
|
||||
if (dd->m_configurations.contains(envFile)) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Warning"),
|
||||
Tr::tr("Configuration already exists or is invalid."));
|
||||
delete config;
|
||||
Tr::tr("Configuration already exists."));
|
||||
return;
|
||||
}
|
||||
|
||||
setConfigState(config, Added);
|
||||
m_configsCombo->addItem(config->displayName(),
|
||||
QVariant::fromValue(static_cast<void*>(config)));
|
||||
// Temporary to be able to check
|
||||
QnxConfiguration config(envFile);
|
||||
config.ensureContents();
|
||||
if (!config.isValid()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Warning"),
|
||||
Tr::tr("Configuration is not valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
setConfigState(envFile, Added);
|
||||
m_configsCombo->addItem(config.m_configName, QVariant::fromValue(envFile));
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::removeConfiguration()
|
||||
{
|
||||
const int currentIndex = m_configsCombo->currentIndex();
|
||||
auto config = static_cast<QnxConfiguration*>(
|
||||
m_configsCombo->itemData(currentIndex).value<void*>());
|
||||
const FilePath envFile = m_configsCombo->currentData().value<FilePath>();
|
||||
QTC_ASSERT(!envFile.isEmpty(), return);
|
||||
|
||||
if (!config)
|
||||
return;
|
||||
QnxConfiguration *config = dd->configurationFromEnvFile(envFile);
|
||||
QTC_ASSERT(config, return);
|
||||
|
||||
config->ensureContents();
|
||||
|
||||
QMessageBox::StandardButton button =
|
||||
QMessageBox::question(Core::ICore::dialogParent(),
|
||||
Tr::tr("Remove QNX Configuration"),
|
||||
Tr::tr("Are you sure you want to remove:\n %1?").arg(config->displayName()),
|
||||
Tr::tr("Are you sure you want to remove:\n %1?")
|
||||
.arg(config->m_configName),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (button == QMessageBox::Yes) {
|
||||
setConfigState(config, Removed);
|
||||
m_configsCombo->removeItem(currentIndex);
|
||||
setConfigState(envFile, Removed);
|
||||
m_configsCombo->removeItem(m_configsCombo->currentIndex());
|
||||
updateInformation();
|
||||
}
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::generateKits(bool checked)
|
||||
{
|
||||
const int currentIndex = m_configsCombo->currentIndex();
|
||||
auto config = static_cast<QnxConfiguration*>(
|
||||
m_configsCombo->itemData(currentIndex).value<void*>());
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
setConfigState(config, checked ? Activated : Deactivated);
|
||||
const FilePath envFile = m_configsCombo->currentData().value<FilePath>();
|
||||
setConfigState(envFile, checked ? Activated : Deactivated);
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::updateInformation()
|
||||
{
|
||||
const int currentIndex = m_configsCombo->currentIndex();
|
||||
const FilePath envFile = m_configsCombo->currentData().value<FilePath>();
|
||||
|
||||
auto config = static_cast<QnxConfiguration*>(
|
||||
m_configsCombo->itemData(currentIndex).value<void*>());
|
||||
|
||||
// update the checkbox
|
||||
m_generateKitsCheckBox->setEnabled(config ? config->isValid() : false);
|
||||
m_generateKitsCheckBox->setChecked(config ? config->isActive() : false);
|
||||
|
||||
// update information
|
||||
m_configName->setText(config ? config->displayName() : QString());
|
||||
m_configVersion->setText(config ? config->version().toString() : QString());
|
||||
m_configHost->setText(config ? config->qnxHost().toString() : QString());
|
||||
m_configTarget->setText(config ? config->qnxTarget().toString() : QString());
|
||||
if (QnxConfiguration *config = dd->configurationFromEnvFile(envFile)) {
|
||||
config->ensureContents();
|
||||
m_generateKitsCheckBox->setEnabled(config->isValid());
|
||||
m_generateKitsCheckBox->setChecked(config->isActive());
|
||||
m_configName->setText(config->m_configName);
|
||||
m_configVersion->setText(config->m_version.toString());
|
||||
m_configHost->setText(config->m_qnxHost.toString());
|
||||
m_configTarget->setText(config->m_qnxTarget.toString());
|
||||
} else {
|
||||
m_generateKitsCheckBox->setEnabled(false);
|
||||
m_generateKitsCheckBox->setChecked(false);
|
||||
m_configName->setText({});
|
||||
m_configVersion->setText({});
|
||||
m_configHost->setText({});
|
||||
m_configTarget->setText({});
|
||||
}
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::populateConfigsCombo()
|
||||
{
|
||||
m_configsCombo->clear();
|
||||
const QList<QnxConfiguration *> configList = m_qnxConfigManager->configurations();
|
||||
for (QnxConfiguration *config : configList) {
|
||||
m_configsCombo->addItem(config->displayName(),
|
||||
QVariant::fromValue(static_cast<void*>(config)));
|
||||
}
|
||||
for (const QnxConfiguration &config : std::as_const(dd->m_configurations))
|
||||
m_configsCombo->addItem(config.m_configName, QVariant::fromValue(config.m_envFile));
|
||||
updateInformation();
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::setConfigState(QnxConfiguration *config, State state)
|
||||
void QnxSettingsWidget::setConfigState(const FilePath &envFile, State state)
|
||||
{
|
||||
State stateToRemove = Activated;
|
||||
switch (state) {
|
||||
@@ -223,45 +678,79 @@ void QnxSettingsWidget::setConfigState(QnxConfiguration *config, State state)
|
||||
}
|
||||
|
||||
for (const ConfigState &configState : std::as_const(m_changedConfigs)) {
|
||||
if (configState.config == config && configState.state == stateToRemove)
|
||||
if (configState.envFile == envFile && configState.state == stateToRemove)
|
||||
m_changedConfigs.removeAll(configState);
|
||||
}
|
||||
|
||||
m_changedConfigs.append(ConfigState{config, state});
|
||||
m_changedConfigs.append(ConfigState{envFile, state});
|
||||
}
|
||||
|
||||
void QnxSettingsWidget::apply()
|
||||
{
|
||||
for (const ConfigState &configState : std::as_const(m_changedConfigs)) {
|
||||
switch (configState.state) {
|
||||
case Activated :
|
||||
configState.config->activate();
|
||||
case Activated: {
|
||||
QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile);
|
||||
QTC_ASSERT(config, break);
|
||||
config->activate();
|
||||
break;
|
||||
case Deactivated:
|
||||
configState.config->deactivate();
|
||||
}
|
||||
case Deactivated: {
|
||||
QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile);
|
||||
QTC_ASSERT(config, break);
|
||||
config->deactivate();
|
||||
break;
|
||||
case Added:
|
||||
m_qnxConfigManager->addConfiguration(configState.config);
|
||||
}
|
||||
case Added: {
|
||||
QnxConfiguration config(configState.envFile);
|
||||
config.ensureContents();
|
||||
dd->m_configurations.insert(configState.envFile, config);
|
||||
break;
|
||||
}
|
||||
case Removed:
|
||||
configState.config->deactivate();
|
||||
m_qnxConfigManager->removeConfiguration(configState.config);
|
||||
QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile);
|
||||
QTC_ASSERT(config, break);
|
||||
config->deactivate();
|
||||
dd->m_configurations.remove(configState.envFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_changedConfigs.clear();
|
||||
populateConfigsCombo();
|
||||
}
|
||||
|
||||
|
||||
// QnxSettingsPage
|
||||
|
||||
QList<ToolChain *> QnxSettingsPage::autoDetect(const QList<ToolChain *> &alreadyKnown)
|
||||
{
|
||||
QList<ToolChain *> result;
|
||||
for (const QnxConfiguration &config : std::as_const(dd->m_configurations)) {
|
||||
config.ensureContents();
|
||||
for (const QnxTarget &target : std::as_const(config.m_targets)) {
|
||||
result += Utils::filtered(alreadyKnown, [config, target](ToolChain *tc) {
|
||||
return tc->typeId() == Constants::QNX_TOOLCHAIN_ID
|
||||
&& tc->targetAbi() == target.m_abi
|
||||
&& tc->compilerCommand() == config.m_qccCompiler;
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QnxSettingsPage::QnxSettingsPage()
|
||||
{
|
||||
setId("DD.Qnx Configuration");
|
||||
setDisplayName(Tr::tr("QNX"));
|
||||
setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY);
|
||||
setWidgetCreator([] { return new QnxSettingsWidget; });
|
||||
|
||||
dd = new QnxSettingsPagePrivate;
|
||||
}
|
||||
|
||||
QnxSettingsPage::~QnxSettingsPage()
|
||||
{
|
||||
delete dd;
|
||||
}
|
||||
|
||||
} // Qnx::Internal
|
||||
|
@@ -5,12 +5,18 @@
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
|
||||
namespace ProjectExplorer { class ToolChain; }
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
class QnxSettingsPage final : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
QnxSettingsPage();
|
||||
~QnxSettingsPage();
|
||||
|
||||
static QList<ProjectExplorer::ToolChain *> autoDetect(
|
||||
const QList<ProjectExplorer::ToolChain *> &alreadyKnown);
|
||||
};
|
||||
|
||||
} // Qnx::Internal
|
||||
|
@@ -3,9 +3,8 @@
|
||||
|
||||
#include "qnxtoolchain.h"
|
||||
|
||||
#include "qnxconfiguration.h"
|
||||
#include "qnxconfigurationmanager.h"
|
||||
#include "qnxconstants.h"
|
||||
#include "qnxsettingspage.h"
|
||||
#include "qnxtr.h"
|
||||
#include "qnxutils.h"
|
||||
|
||||
@@ -222,10 +221,7 @@ Toolchains QnxToolChainFactory::autoDetect(const ToolchainDetector &detector) co
|
||||
if (detector.device)
|
||||
return {};
|
||||
|
||||
Toolchains tcs;
|
||||
const auto configurations = QnxConfigurationManager::instance()->configurations();
|
||||
for (QnxConfiguration *configuration : configurations)
|
||||
tcs += configuration->autoDetect(detector.alreadyKnown);
|
||||
Toolchains tcs = QnxSettingsPage::autoDetect(detector.alreadyKnown);
|
||||
return tcs;
|
||||
}
|
||||
|
||||
|
@@ -16,6 +16,15 @@ using namespace Utils;
|
||||
|
||||
namespace Qnx::Internal {
|
||||
|
||||
QnxTarget::QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi) :
|
||||
m_path(path), m_abi(abi)
|
||||
{}
|
||||
|
||||
QString QnxTarget::shortDescription() const
|
||||
{
|
||||
return QnxUtils::cpuDirShortDescription(cpuDir());
|
||||
}
|
||||
|
||||
QString QnxUtils::cpuDirFromAbi(const Abi &abi)
|
||||
{
|
||||
if (abi.os() != Abi::OS::QnxOS)
|
||||
|
@@ -14,12 +14,14 @@ namespace Qnx::Internal {
|
||||
class QnxTarget
|
||||
{
|
||||
public:
|
||||
QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi) :
|
||||
m_path(path), m_abi(abi)
|
||||
{
|
||||
}
|
||||
QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi);
|
||||
|
||||
QString shortDescription() const;
|
||||
QString cpuDir() const { return m_path.fileName(); }
|
||||
|
||||
Utils::FilePath m_path;
|
||||
ProjectExplorer::Abi m_abi;
|
||||
Utils::FilePath m_debuggerPath;
|
||||
};
|
||||
|
||||
namespace QnxUtils {
|
||||
|
Reference in New Issue
Block a user