forked from qt-creator/qt-creator
More in line with QFileInfo terminonlogy which appears to be best-of-breed within Qt. Change-Id: I1d051ff1c8363ebd4ee56376451df45216c4c9ab Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
455 lines
16 KiB
C++
455 lines
16 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 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 "debuggerkitinformation.h"
|
|
|
|
#include "debuggeritemmanager.h"
|
|
#include "debuggeritem.h"
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
#include <projectexplorer/toolchain.h>
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
|
#include <projectexplorer/runcontrol.h>
|
|
|
|
#include <utils/environment.h>
|
|
#include <utils/fileutils.h>
|
|
#include <utils/macroexpander.h>
|
|
#include <utils/qtcassert.h>
|
|
|
|
#include <QComboBox>
|
|
#include <QFileInfo>
|
|
#include <QPushButton>
|
|
#include <utility>
|
|
|
|
using namespace ProjectExplorer;
|
|
using namespace Utils;
|
|
|
|
namespace Debugger {
|
|
|
|
// --------------------------------------------------------------------------
|
|
// DebuggerKitAspect
|
|
// --------------------------------------------------------------------------
|
|
|
|
namespace Internal {
|
|
|
|
class DebuggerKitAspectWidget : public KitAspectWidget
|
|
{
|
|
Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerKitAspect)
|
|
|
|
public:
|
|
DebuggerKitAspectWidget(Kit *workingCopy, const KitAspect *ki)
|
|
: KitAspectWidget(workingCopy, ki)
|
|
{
|
|
m_comboBox = new QComboBox;
|
|
m_comboBox->setSizePolicy(QSizePolicy::Ignored, m_comboBox->sizePolicy().verticalPolicy());
|
|
m_comboBox->setEnabled(true);
|
|
|
|
refresh();
|
|
m_comboBox->setToolTip(ki->description());
|
|
connect(m_comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
this, &DebuggerKitAspectWidget::currentDebuggerChanged);
|
|
|
|
m_manageButton = new QPushButton(KitAspectWidget::msgManage());
|
|
m_manageButton->setContentsMargins(0, 0, 0, 0);
|
|
connect(m_manageButton, &QAbstractButton::clicked,
|
|
this, &DebuggerKitAspectWidget::manageDebuggers);
|
|
}
|
|
|
|
~DebuggerKitAspectWidget() override
|
|
{
|
|
delete m_comboBox;
|
|
delete m_manageButton;
|
|
}
|
|
|
|
private:
|
|
QWidget *buttonWidget() const override { return m_manageButton; }
|
|
QWidget *mainWidget() const override { return m_comboBox; }
|
|
|
|
void makeReadOnly() override
|
|
{
|
|
m_manageButton->setEnabled(false);
|
|
m_comboBox->setEnabled(false);
|
|
}
|
|
|
|
void refresh() override
|
|
{
|
|
m_ignoreChanges = true;
|
|
m_comboBox->clear();
|
|
m_comboBox->addItem(tr("None"), QString());
|
|
for (const DebuggerItem &item : DebuggerItemManager::debuggers())
|
|
m_comboBox->addItem(item.displayName(), item.id());
|
|
|
|
const DebuggerItem *item = DebuggerKitAspect::debugger(m_kit);
|
|
updateComboBox(item ? item->id() : QVariant());
|
|
m_ignoreChanges = false;
|
|
}
|
|
|
|
void manageDebuggers()
|
|
{
|
|
Core::ICore::showOptionsDialog(ProjectExplorer::Constants::DEBUGGER_SETTINGS_PAGE_ID,
|
|
buttonWidget());
|
|
}
|
|
|
|
void currentDebuggerChanged(int idx)
|
|
{
|
|
Q_UNUSED(idx);
|
|
if (m_ignoreChanges)
|
|
return;
|
|
|
|
int currentIndex = m_comboBox->currentIndex();
|
|
QVariant id = m_comboBox->itemData(currentIndex);
|
|
m_kit->setValue(DebuggerKitAspect::id(), id);
|
|
}
|
|
|
|
QVariant currentId() const { return m_comboBox->itemData(m_comboBox->currentIndex()); }
|
|
|
|
void updateComboBox(const QVariant &id)
|
|
{
|
|
for (int i = 0; i < m_comboBox->count(); ++i) {
|
|
if (id == m_comboBox->itemData(i)) {
|
|
m_comboBox->setCurrentIndex(i);
|
|
return;
|
|
}
|
|
}
|
|
m_comboBox->setCurrentIndex(0);
|
|
}
|
|
|
|
bool m_ignoreChanges = false;
|
|
QComboBox *m_comboBox;
|
|
QPushButton *m_manageButton;
|
|
};
|
|
} // namespace Internal
|
|
|
|
DebuggerKitAspect::DebuggerKitAspect()
|
|
{
|
|
setObjectName("DebuggerKitAspect");
|
|
setId(DebuggerKitAspect::id());
|
|
setDisplayName(tr("Debugger"));
|
|
setDescription(tr("The debugger to use for this kit."));
|
|
setPriority(28000);
|
|
}
|
|
|
|
void DebuggerKitAspect::setup(Kit *k)
|
|
{
|
|
QTC_ASSERT(k, return);
|
|
|
|
// This can be anything (Id, binary path, "auto")
|
|
// With 3.0 we have:
|
|
// <value type="QString" key="Debugger.Information">{75ecf347-f221-44c3-b613-ea1d29929cd4}</value>
|
|
// Before we had:
|
|
// <valuemap type="QVariantMap" key="Debugger.Information">
|
|
// <value type="QString" key="Binary">/data/dev/debugger/gdb-git/gdb/gdb</value>
|
|
// <value type="int" key="EngineType">1</value>
|
|
// </valuemap>
|
|
// Or for force auto-detected CDB
|
|
// <valuemap type="QVariantMap" key="Debugger.Information">
|
|
// <value type="QString" key="Binary">auto</value>
|
|
// <value type="int" key="EngineType">4</value>
|
|
// </valuemap>
|
|
const QVariant rawId = k->value(DebuggerKitAspect::id());
|
|
|
|
const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
|
|
|
|
// Get the best of the available debugger matching the kit's toolchain.
|
|
// The general idea is to find an item that exactly matches what
|
|
// is stored in the kit information, but also accept item based
|
|
// on toolchain matching as fallback with a lower priority.
|
|
|
|
DebuggerItem bestItem;
|
|
DebuggerItem::MatchLevel bestLevel = DebuggerItem::DoesNotMatch;
|
|
const Environment systemEnvironment = Environment::systemEnvironment();
|
|
for (const DebuggerItem &item : DebuggerItemManager::debuggers()) {
|
|
DebuggerItem::MatchLevel level = DebuggerItem::DoesNotMatch;
|
|
|
|
if (rawId.isNull()) {
|
|
// Initial setup of a kit.
|
|
level = item.matchTarget(tcAbi);
|
|
// Hack to prefer a debugger from PATH (e.g. autodetected) over other matches.
|
|
// This improves the situation a bit if a cross-compilation tool chain has the
|
|
// same ABI as the host.
|
|
if (level == DebuggerItem::MatchesPerfectly
|
|
&& systemEnvironment.path().contains(item.command().parentDir())) {
|
|
level = DebuggerItem::MatchesPerfectlyInPath;
|
|
}
|
|
} else if (rawId.type() == QVariant::String) {
|
|
// New structure.
|
|
if (item.id() == rawId) {
|
|
// Detected by ID.
|
|
level = DebuggerItem::MatchesPerfectly;
|
|
} else {
|
|
// This item does not match by ID, and is an unlikely candidate.
|
|
// However, consider using it as fallback if the tool chain fits.
|
|
level = std::min(item.matchTarget(tcAbi), DebuggerItem::MatchesSomewhat);
|
|
}
|
|
} else {
|
|
// Old structure.
|
|
const QMap<QString, QVariant> map = rawId.toMap();
|
|
QString binary = map.value("Binary").toString();
|
|
if (binary == "auto") {
|
|
// This is close to the "new kit" case, except that we know
|
|
// an engine type.
|
|
DebuggerEngineType autoEngine = DebuggerEngineType(map.value("EngineType").toInt());
|
|
if (item.engineType() == autoEngine) {
|
|
// Use item if host toolchain fits, but only as fallback.
|
|
level = std::min(item.matchTarget(tcAbi), DebuggerItem::MatchesSomewhat);
|
|
}
|
|
} else {
|
|
// We have an executable path.
|
|
FilePath fileName = FilePath::fromUserInput(binary);
|
|
if (item.command() == fileName) {
|
|
// And it's is the path of this item.
|
|
level = std::min(item.matchTarget(tcAbi), DebuggerItem::MatchesSomewhat);
|
|
} else {
|
|
// This item does not match by filename, and is an unlikely candidate.
|
|
// However, consider using it as fallback if the tool chain fits.
|
|
level = std::min(item.matchTarget(tcAbi), DebuggerItem::MatchesSomewhat);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (level > bestLevel) {
|
|
bestLevel = level;
|
|
bestItem = item;
|
|
}
|
|
}
|
|
|
|
// Use the best id we found, or an invalid one.
|
|
k->setValue(DebuggerKitAspect::id(), bestLevel != DebuggerItem::DoesNotMatch ? bestItem.id() : QVariant());
|
|
}
|
|
|
|
|
|
// This handles the upgrade path from 2.8 to 3.0
|
|
void DebuggerKitAspect::fix(Kit *k)
|
|
{
|
|
QTC_ASSERT(k, return);
|
|
|
|
// This can be Id, binary path, but not "auto" anymore.
|
|
const QVariant rawId = k->value(DebuggerKitAspect::id());
|
|
|
|
if (rawId.isNull()) // No debugger set, that is fine.
|
|
return;
|
|
|
|
if (rawId.type() == QVariant::String) {
|
|
if (!DebuggerItemManager::findById(rawId)) {
|
|
qWarning("Unknown debugger id %s in kit %s",
|
|
qPrintable(rawId.toString()), qPrintable(k->displayName()));
|
|
k->setValue(DebuggerKitAspect::id(), QVariant());
|
|
}
|
|
return; // All fine (now).
|
|
}
|
|
|
|
QMap<QString, QVariant> map = rawId.toMap();
|
|
QString binary = map.value("Binary").toString();
|
|
if (binary == "auto") {
|
|
// This should not happen as "auto" is handled by setup() already.
|
|
QTC_CHECK(false);
|
|
k->setValue(DebuggerKitAspect::id(), QVariant());
|
|
return;
|
|
}
|
|
|
|
FilePath fileName = FilePath::fromUserInput(binary);
|
|
const DebuggerItem *item = DebuggerItemManager::findByCommand(fileName);
|
|
if (!item) {
|
|
qWarning("Debugger command %s invalid in kit %s",
|
|
qPrintable(binary), qPrintable(k->displayName()));
|
|
k->setValue(DebuggerKitAspect::id(), QVariant());
|
|
return;
|
|
}
|
|
|
|
k->setValue(DebuggerKitAspect::id(), item->id());
|
|
}
|
|
|
|
// Check the configuration errors and return a flag mask. Provide a quick check and
|
|
// a verbose one with a list of errors.
|
|
|
|
DebuggerKitAspect::ConfigurationErrors DebuggerKitAspect::configurationErrors(const Kit *k)
|
|
{
|
|
QTC_ASSERT(k, return NoDebugger);
|
|
|
|
const DebuggerItem *item = DebuggerKitAspect::debugger(k);
|
|
if (!item)
|
|
return NoDebugger;
|
|
|
|
if (item->command().isEmpty())
|
|
return NoDebugger;
|
|
|
|
ConfigurationErrors result = NoConfigurationError;
|
|
const QFileInfo fi = item->command().toFileInfo();
|
|
if (!fi.exists() || fi.isDir())
|
|
result |= DebuggerNotFound;
|
|
else if (!fi.isExecutable())
|
|
result |= DebuggerNotExecutable;
|
|
|
|
const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
|
|
if (item->matchTarget(tcAbi) == DebuggerItem::DoesNotMatch) {
|
|
// currently restricting the check to desktop devices, may be extended to all device types
|
|
const IDevice::ConstPtr device = DeviceKitAspect::device(k);
|
|
if (device && device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
|
|
result |= DebuggerDoesNotMatch;
|
|
}
|
|
|
|
if (!fi.exists() || fi.isDir()) {
|
|
if (item->engineType() == NoEngineType)
|
|
return NoDebugger;
|
|
|
|
// We need an absolute path to be able to locate Python on Windows.
|
|
if (item->engineType() == GdbEngineType) {
|
|
if (tcAbi.os() == Abi::WindowsOS && !fi.isAbsolute())
|
|
result |= DebuggerNeedsAbsolutePath;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const DebuggerItem *DebuggerKitAspect::debugger(const Kit *kit)
|
|
{
|
|
QTC_ASSERT(kit, return nullptr);
|
|
const QVariant id = kit->value(DebuggerKitAspect::id());
|
|
return DebuggerItemManager::findById(id);
|
|
}
|
|
|
|
Runnable DebuggerKitAspect::runnable(const Kit *kit)
|
|
{
|
|
Runnable runnable;
|
|
if (const DebuggerItem *item = debugger(kit)) {
|
|
runnable.executable = item->command().toString();
|
|
runnable.workingDirectory = item->workingDirectory().toString();
|
|
runnable.environment = Utils::Environment::systemEnvironment();
|
|
runnable.environment.set("LC_NUMERIC", "C");
|
|
}
|
|
return runnable;
|
|
}
|
|
|
|
Tasks DebuggerKitAspect::validateDebugger(const Kit *k)
|
|
{
|
|
Tasks result;
|
|
|
|
const ConfigurationErrors errors = configurationErrors(k);
|
|
if (errors == NoConfigurationError)
|
|
return result;
|
|
|
|
QString path;
|
|
if (const DebuggerItem *item = debugger(k))
|
|
path = item->command().toUserOutput();
|
|
|
|
const Core::Id id = ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM;
|
|
if (errors & NoDebugger)
|
|
result << Task(Task::Warning, tr("No debugger set up."), FilePath(), -1, id);
|
|
|
|
if (errors & DebuggerNotFound)
|
|
result << Task(Task::Error, tr("Debugger \"%1\" not found.").arg(path),
|
|
FilePath(), -1, id);
|
|
if (errors & DebuggerNotExecutable)
|
|
result << Task(Task::Error, tr("Debugger \"%1\" not executable.").arg(path), FilePath(), -1, id);
|
|
|
|
if (errors & DebuggerNeedsAbsolutePath) {
|
|
const QString message =
|
|
tr("The debugger location must be given as an "
|
|
"absolute path (%1).").arg(path);
|
|
result << Task(Task::Error, message, FilePath(), -1, id);
|
|
}
|
|
|
|
if (errors & DebuggerDoesNotMatch) {
|
|
const QString message = tr("The ABI of the selected debugger does not "
|
|
"match the toolchain ABI.");
|
|
result << Task(Task::Warning, message, FilePath(), -1, id);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
KitAspectWidget *DebuggerKitAspect::createConfigWidget(Kit *k) const
|
|
{
|
|
return new Internal::DebuggerKitAspectWidget(k, this);
|
|
}
|
|
|
|
void DebuggerKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const
|
|
{
|
|
QTC_ASSERT(kit, return);
|
|
expander->registerVariable("Debugger:Name", tr("Name of Debugger"),
|
|
[kit]() -> QString {
|
|
const DebuggerItem *item = debugger(kit);
|
|
return item ? item->displayName() : tr("Unknown debugger");
|
|
});
|
|
|
|
expander->registerVariable("Debugger:Type", tr("Type of Debugger Backend"),
|
|
[kit]() -> QString {
|
|
const DebuggerItem *item = debugger(kit);
|
|
return item ? item->engineTypeName() : tr("Unknown debugger type");
|
|
});
|
|
|
|
expander->registerVariable("Debugger:Version", tr("Debugger"),
|
|
[kit]() -> QString {
|
|
const DebuggerItem *item = debugger(kit);
|
|
return item && !item->version().isEmpty()
|
|
? item->version() : tr("Unknown debugger version");
|
|
});
|
|
|
|
expander->registerVariable("Debugger:Abi", tr("Debugger"),
|
|
[kit]() -> QString {
|
|
const DebuggerItem *item = debugger(kit);
|
|
return item && !item->abis().isEmpty()
|
|
? item->abiNames().join(' ')
|
|
: tr("Unknown debugger ABI");
|
|
});
|
|
}
|
|
|
|
KitAspect::ItemList DebuggerKitAspect::toUserOutput(const Kit *k) const
|
|
{
|
|
return {{tr("Debugger"), displayString(k)}};
|
|
}
|
|
|
|
DebuggerEngineType DebuggerKitAspect::engineType(const Kit *k)
|
|
{
|
|
const DebuggerItem *item = debugger(k);
|
|
QTC_ASSERT(item, return NoEngineType);
|
|
return item->engineType();
|
|
}
|
|
|
|
QString DebuggerKitAspect::displayString(const Kit *k)
|
|
{
|
|
const DebuggerItem *item = debugger(k);
|
|
if (!item)
|
|
return tr("No Debugger");
|
|
QString binary = item->command().toUserOutput();
|
|
QString name = tr("%1 Engine").arg(item->engineTypeName());
|
|
return binary.isEmpty() ? tr("%1 <None>").arg(name) : tr("%1 using \"%2\"").arg(name, binary);
|
|
}
|
|
|
|
void DebuggerKitAspect::setDebugger(Kit *k, const QVariant &id)
|
|
{
|
|
// Only register reasonably complete debuggers.
|
|
QTC_ASSERT(DebuggerItemManager::findById(id), return);
|
|
QTC_ASSERT(k, return);
|
|
k->setValue(DebuggerKitAspect::id(), id);
|
|
}
|
|
|
|
Core::Id DebuggerKitAspect::id()
|
|
{
|
|
return "Debugger.Information";
|
|
}
|
|
|
|
} // namespace Debugger
|